Spaces:
Running
Running
alessandro trinca tornidor
commited on
Commit
•
74a35d9
1
Parent(s):
e8a1983
refactor: organize project with tests in a package, start following suggestions from pycharm, sonarlint and snyk
Browse files- .gitignore +183 -34
- WordMatching.py → aip_trainer/WordMatching.py +8 -5
- WordMetrics.py → aip_trainer/WordMetrics.py +5 -1
- aip_trainer/__init__.py +16 -0
- aip_trainer/lambdas/__init__.py +0 -0
- data_de_en_2.pickle → aip_trainer/lambdas/data_de_en_2.pickle +0 -0
- lambdaGetSample.py → aip_trainer/lambdas/lambdaGetSample.py +9 -7
- lambdaSpeechToScore.py → aip_trainer/lambdas/lambdaSpeechToScore.py +16 -10
- aip_trainer/lambdas/lambdaTTS.py +48 -0
- AIModels.py → aip_trainer/models/AIModels.py +3 -2
- ModelInterfaces.py → aip_trainer/models/ModelInterfaces.py +1 -1
- RuleBasedModels.py → aip_trainer/models/RuleBasedModels.py +2 -4
- aip_trainer/models/__init__.py +0 -0
- models.py → aip_trainer/models/models.py +0 -3
- pronunciationTrainer.py → aip_trainer/pronunciationTrainer.py +15 -13
- aip_trainer/utils/__init__.py +0 -0
- aip_trainer/utils/session_logger.py +143 -0
- utilsFileIO.py → aip_trainer/utilsFileIO.py +0 -0
- lambdaTTS.py +0 -46
- static/javascript/callbacks.js +1 -1
- tests/__init__.py +0 -0
- unitTests.py → tests/unitTests.py +8 -9
- webApp.py +4 -3
.gitignore
CHANGED
@@ -1,3 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
# Byte-compiled / optimized / DLL files
|
2 |
__pycache__/
|
3 |
*.py[cod]
|
@@ -20,12 +78,9 @@ parts/
|
|
20 |
sdist/
|
21 |
var/
|
22 |
wheels/
|
23 |
-
pip-wheel-metadata/
|
24 |
-
share/python-wheels/
|
25 |
*.egg-info/
|
26 |
.installed.cfg
|
27 |
*.egg
|
28 |
-
MANIFEST
|
29 |
|
30 |
# PyInstaller
|
31 |
# Usually these files are written by a python script from a template
|
@@ -40,27 +95,19 @@ pip-delete-this-directory.txt
|
|
40 |
# Unit test / coverage reports
|
41 |
htmlcov/
|
42 |
.tox/
|
43 |
-
.nox/
|
44 |
.coverage
|
45 |
.coverage.*
|
46 |
.cache
|
|
|
47 |
nosetests.xml
|
48 |
coverage.xml
|
49 |
*.cover
|
50 |
-
*.py,cover
|
51 |
.hypothesis/
|
52 |
-
.pytest_cache/
|
53 |
|
54 |
# Translations
|
55 |
*.mo
|
56 |
*.pot
|
57 |
|
58 |
-
# Django stuff:
|
59 |
-
*.log
|
60 |
-
local_settings.py
|
61 |
-
db.sqlite3
|
62 |
-
db.sqlite3-journal
|
63 |
-
|
64 |
# Flask stuff:
|
65 |
instance/
|
66 |
.webassets-cache
|
@@ -69,7 +116,8 @@ instance/
|
|
69 |
.scrapy
|
70 |
|
71 |
# Sphinx documentation
|
72 |
-
docs/_build/
|
|
|
73 |
|
74 |
# PyBuilder
|
75 |
target/
|
@@ -77,32 +125,18 @@ target/
|
|
77 |
# Jupyter Notebook
|
78 |
.ipynb_checkpoints
|
79 |
|
80 |
-
# IPython
|
81 |
-
profile_default/
|
82 |
-
ipython_config.py
|
83 |
-
|
84 |
# pyenv
|
85 |
.python-version
|
86 |
|
87 |
-
#
|
88 |
-
|
89 |
-
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
90 |
-
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
91 |
-
# install all needed dependencies.
|
92 |
-
#Pipfile.lock
|
93 |
-
|
94 |
-
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
95 |
-
__pypackages__/
|
96 |
-
|
97 |
-
# Celery stuff
|
98 |
-
celerybeat-schedule
|
99 |
-
celerybeat.pid
|
100 |
|
101 |
# SageMath parsed files
|
102 |
*.sage.py
|
103 |
|
104 |
# Environments
|
105 |
.env
|
|
|
106 |
.venv
|
107 |
env/
|
108 |
venv/
|
@@ -122,8 +156,123 @@ venv.bak/
|
|
122 |
|
123 |
# mypy
|
124 |
.mypy_cache/
|
125 |
-
.dmypy.json
|
126 |
-
dmypy.json
|
127 |
|
128 |
-
|
129 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
|
3 |
+
|
4 |
+
### Linux ###
|
5 |
+
*~
|
6 |
+
|
7 |
+
# temporary files which can be created if a process still has a handle open of a deleted file
|
8 |
+
.fuse_hidden*
|
9 |
+
|
10 |
+
# KDE directory preferences
|
11 |
+
.directory
|
12 |
+
|
13 |
+
# Linux trash folder which might appear on any partition or disk
|
14 |
+
.Trash-*
|
15 |
+
|
16 |
+
# .nfs files are created when an open file is removed but is still being accessed
|
17 |
+
.nfs*
|
18 |
+
|
19 |
+
### OSX ###
|
20 |
+
*.DS_Store
|
21 |
+
.AppleDouble
|
22 |
+
.LSOverride
|
23 |
+
|
24 |
+
# Icon must end with two \r
|
25 |
+
Icon
|
26 |
+
|
27 |
+
# Thumbnails
|
28 |
+
._*
|
29 |
+
|
30 |
+
# Files that might appear in the root of a volume
|
31 |
+
.DocumentRevisions-V100
|
32 |
+
.fseventsd
|
33 |
+
.Spotlight-V100
|
34 |
+
.TemporaryItems
|
35 |
+
.Trashes
|
36 |
+
.VolumeIcon.icns
|
37 |
+
.com.apple.timemachine.donotpresent
|
38 |
+
|
39 |
+
# Directories potentially created on remote AFP share
|
40 |
+
.AppleDB
|
41 |
+
.AppleDesktop
|
42 |
+
Network Trash Folder
|
43 |
+
Temporary Items
|
44 |
+
.apdisk
|
45 |
+
|
46 |
+
# CMake
|
47 |
+
cmake-build-debug/
|
48 |
+
|
49 |
+
# Ruby plugin and RubyMine
|
50 |
+
/.rakeTasks
|
51 |
+
|
52 |
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
53 |
+
com_crashlytics_export_strings.xml
|
54 |
+
crashlytics.properties
|
55 |
+
crashlytics-build.properties
|
56 |
+
fabric.properties
|
57 |
+
|
58 |
+
### Python ###
|
59 |
# Byte-compiled / optimized / DLL files
|
60 |
__pycache__/
|
61 |
*.py[cod]
|
|
|
78 |
sdist/
|
79 |
var/
|
80 |
wheels/
|
|
|
|
|
81 |
*.egg-info/
|
82 |
.installed.cfg
|
83 |
*.egg
|
|
|
84 |
|
85 |
# PyInstaller
|
86 |
# Usually these files are written by a python script from a template
|
|
|
95 |
# Unit test / coverage reports
|
96 |
htmlcov/
|
97 |
.tox/
|
|
|
98 |
.coverage
|
99 |
.coverage.*
|
100 |
.cache
|
101 |
+
.pytest_cache/
|
102 |
nosetests.xml
|
103 |
coverage.xml
|
104 |
*.cover
|
|
|
105 |
.hypothesis/
|
|
|
106 |
|
107 |
# Translations
|
108 |
*.mo
|
109 |
*.pot
|
110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
# Flask stuff:
|
112 |
instance/
|
113 |
.webassets-cache
|
|
|
116 |
.scrapy
|
117 |
|
118 |
# Sphinx documentation
|
119 |
+
docs/_build/doctrees/*
|
120 |
+
docs/_build/html/*
|
121 |
|
122 |
# PyBuilder
|
123 |
target/
|
|
|
125 |
# Jupyter Notebook
|
126 |
.ipynb_checkpoints
|
127 |
|
|
|
|
|
|
|
|
|
128 |
# pyenv
|
129 |
.python-version
|
130 |
|
131 |
+
# celery beat schedule file
|
132 |
+
celerybeat-schedule.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
133 |
|
134 |
# SageMath parsed files
|
135 |
*.sage.py
|
136 |
|
137 |
# Environments
|
138 |
.env
|
139 |
+
.env*
|
140 |
.venv
|
141 |
env/
|
142 |
venv/
|
|
|
156 |
|
157 |
# mypy
|
158 |
.mypy_cache/
|
|
|
|
|
159 |
|
160 |
+
### VisualStudioCode ###
|
161 |
+
.vscode/*
|
162 |
+
!.vscode/settings.json
|
163 |
+
!.vscode/tasks.json
|
164 |
+
!.vscode/launch.json
|
165 |
+
!.vscode/extensions.json
|
166 |
+
.history
|
167 |
+
|
168 |
+
### Windows ###
|
169 |
+
# Windows thumbnail cache files
|
170 |
+
Thumbs.db
|
171 |
+
ehthumbs.db
|
172 |
+
ehthumbs_vista.db
|
173 |
+
|
174 |
+
# Folder config file
|
175 |
+
Desktop.ini
|
176 |
+
|
177 |
+
# Recycle Bin used on file shares
|
178 |
+
$RECYCLE.BIN/
|
179 |
+
|
180 |
+
# Windows Installer files
|
181 |
+
*.cab
|
182 |
+
*.msi
|
183 |
+
*.msm
|
184 |
+
*.msp
|
185 |
+
|
186 |
+
# Windows shortcuts
|
187 |
+
*.lnk
|
188 |
+
|
189 |
+
# Build folder
|
190 |
+
|
191 |
+
*/build/*
|
192 |
+
|
193 |
+
# custom
|
194 |
+
*.ori
|
195 |
+
tmp
|
196 |
+
nohup.out
|
197 |
+
/tests/events.tar
|
198 |
+
function_dump_*.json
|
199 |
+
|
200 |
+
# onnx models
|
201 |
+
*.onnx
|
202 |
+
|
203 |
+
# End of https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
|
204 |
+
|
205 |
+
## .idea files
|
206 |
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
207 |
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
208 |
+
|
209 |
+
# User-specific stuff
|
210 |
+
.idea/**/workspace.xml
|
211 |
+
.idea/**/tasks.xml
|
212 |
+
.idea/**/usage.statistics.xml
|
213 |
+
.idea/**/dictionaries
|
214 |
+
.idea/**/shelf
|
215 |
+
|
216 |
+
# Generated files
|
217 |
+
.idea/**/contentModel.xml
|
218 |
+
|
219 |
+
# Sensitive or high-churn files
|
220 |
+
.idea/**/dataSources/
|
221 |
+
.idea/**/dataSources.ids
|
222 |
+
.idea/**/dataSources.local.xml
|
223 |
+
.idea/**/sqlDataSources.xml
|
224 |
+
.idea/**/dynamic.xml
|
225 |
+
.idea/**/uiDesigner.xml
|
226 |
+
.idea/**/dbnavigator.xml
|
227 |
+
|
228 |
+
# Gradle
|
229 |
+
.idea/**/gradle.xml
|
230 |
+
.idea/**/libraries
|
231 |
+
|
232 |
+
# Gradle and Maven with auto-import
|
233 |
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
234 |
+
# since they will be recreated, and may cause churn. Uncomment if using
|
235 |
+
# auto-import.
|
236 |
+
# .idea/modules.xml
|
237 |
+
.idea/*.iml
|
238 |
+
# .idea/modules
|
239 |
+
|
240 |
+
# CMake
|
241 |
+
cmake-build-*/
|
242 |
+
|
243 |
+
# Mongo Explorer plugin
|
244 |
+
.idea/**/mongoSettings.xml
|
245 |
+
|
246 |
+
# File-based project format
|
247 |
+
*.iws
|
248 |
+
|
249 |
+
# IntelliJ
|
250 |
+
out/
|
251 |
+
|
252 |
+
# mpeltonen/sbt-idea plugin
|
253 |
+
.idea_modules/
|
254 |
+
|
255 |
+
# JIRA plugin
|
256 |
+
atlassian-ide-plugin.xml
|
257 |
+
|
258 |
+
# Cursive Clojure plugin
|
259 |
+
.idea/replstate.xml
|
260 |
+
|
261 |
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
262 |
+
com_crashlytics_export_strings.xml
|
263 |
+
crashlytics.properties
|
264 |
+
crashlytics-build.properties
|
265 |
+
fabric.properties
|
266 |
+
|
267 |
+
# Editor-based Rest Client
|
268 |
+
.idea/httpRequests
|
269 |
+
|
270 |
+
# Android studio 3.1+ serialized cache file
|
271 |
+
.idea/caches/build_file_checksums.ser
|
272 |
+
|
273 |
+
# Sonarlint plugin
|
274 |
+
.idea/sonarlint
|
275 |
+
/.idea/modules.xml
|
276 |
+
|
277 |
+
# node_modules
|
278 |
+
node_modules
|
WordMatching.py → aip_trainer/WordMatching.py
RENAMED
@@ -1,9 +1,11 @@
|
|
1 |
-
import
|
2 |
-
from ortools.sat.python import cp_model
|
3 |
-
import numpy as np
|
4 |
from string import punctuation
|
|
|
|
|
5 |
from dtwalign import dtw_from_distance_matrix
|
6 |
-
import
|
|
|
|
|
7 |
|
8 |
offset_blank = 1
|
9 |
TIME_THRESHOLD_MAPPING = 5.0
|
@@ -77,7 +79,8 @@ def get_best_path_from_distance_matrix(word_distance_matrix):
|
|
77 |
(solver.Value(estimated_words_order[word_idx])))
|
78 |
|
79 |
return np.array(mapped_indices, dtype=int)
|
80 |
-
except:
|
|
|
81 |
return []
|
82 |
|
83 |
|
|
|
1 |
+
import time
|
|
|
|
|
2 |
from string import punctuation
|
3 |
+
|
4 |
+
import numpy as np
|
5 |
from dtwalign import dtw_from_distance_matrix
|
6 |
+
from ortools.sat.python import cp_model
|
7 |
+
|
8 |
+
from . import WordMetrics, app_logger
|
9 |
|
10 |
offset_blank = 1
|
11 |
TIME_THRESHOLD_MAPPING = 5.0
|
|
|
79 |
(solver.Value(estimated_words_order[word_idx])))
|
80 |
|
81 |
return np.array(mapped_indices, dtype=int)
|
82 |
+
except Exception as ex:
|
83 |
+
app_logger.error(f"ex:{ex}.")
|
84 |
return []
|
85 |
|
86 |
|
WordMetrics.py → aip_trainer/WordMetrics.py
RENAMED
@@ -1,8 +1,12 @@
|
|
1 |
import numpy as np
|
2 |
|
|
|
|
|
|
|
3 |
# ref from https://gitlab.com/-/snippets/1948157
|
4 |
# For some variants, look here https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Python
|
5 |
|
|
|
6 |
# Pure python
|
7 |
def edit_distance_python2(a, b):
|
8 |
# This version is commutative, so as an optimization we force |a|>=|b|
|
@@ -52,5 +56,5 @@ def edit_distance_python(seq1, seq2):
|
|
52 |
matrix[x-1,y-1] + 1,
|
53 |
matrix[x,y-1] + 1
|
54 |
)
|
55 |
-
|
56 |
return matrix[size_x - 1, size_y - 1]
|
|
|
1 |
import numpy as np
|
2 |
|
3 |
+
from aip_trainer import app_logger
|
4 |
+
|
5 |
+
|
6 |
# ref from https://gitlab.com/-/snippets/1948157
|
7 |
# For some variants, look here https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Python
|
8 |
|
9 |
+
|
10 |
# Pure python
|
11 |
def edit_distance_python2(a, b):
|
12 |
# This version is commutative, so as an optimization we force |a|>=|b|
|
|
|
56 |
matrix[x-1,y-1] + 1,
|
57 |
matrix[x,y-1] + 1
|
58 |
)
|
59 |
+
app_logger.debug("matrix:{}\n".format(matrix))
|
60 |
return matrix[size_x - 1, size_y - 1]
|
aip_trainer/__init__.py
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Get machine learning predictions from geodata raster images"""
|
2 |
+
import os
|
3 |
+
from pathlib import Path
|
4 |
+
|
5 |
+
import structlog
|
6 |
+
from dotenv import load_dotenv
|
7 |
+
|
8 |
+
from aip_trainer.utils import session_logger
|
9 |
+
|
10 |
+
|
11 |
+
load_dotenv()
|
12 |
+
PROJECT_ROOT_FOLDER = Path(globals().get("__file__", "./_")).absolute().parent.parent
|
13 |
+
LOG_JSON_FORMAT = bool(os.getenv("LOG_JSON_FORMAT", False))
|
14 |
+
log_level = os.getenv("LOG_LEVEL", "INFO")
|
15 |
+
session_logger.setup_logging(json_logs=LOG_JSON_FORMAT, log_level=log_level)
|
16 |
+
app_logger = structlog.stdlib.get_logger(__name__)
|
aip_trainer/lambdas/__init__.py
ADDED
File without changes
|
data_de_en_2.pickle → aip_trainer/lambdas/data_de_en_2.pickle
RENAMED
File without changes
|
lambdaGetSample.py → aip_trainer/lambdas/lambdaGetSample.py
RENAMED
@@ -1,10 +1,12 @@
|
|
1 |
-
|
2 |
-
import pandas as pd
|
3 |
import json
|
4 |
-
import RuleBasedModels
|
5 |
-
import epitran
|
6 |
-
import random
|
7 |
import pickle
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
|
10 |
class TextDataset:
|
@@ -27,11 +29,11 @@ class TextDataset:
|
|
27 |
return self.number_of_samples
|
28 |
|
29 |
|
30 |
-
sample_folder = "
|
31 |
lambda_database = {}
|
32 |
lambda_ipa_converter = {}
|
33 |
|
34 |
-
with open(sample_folder
|
35 |
df = pickle.load(handle)
|
36 |
|
37 |
lambda_database['de'] = TextDataset(df, 'de')
|
|
|
|
|
|
|
1 |
import json
|
|
|
|
|
|
|
2 |
import pickle
|
3 |
+
import random
|
4 |
+
from pathlib import Path
|
5 |
+
|
6 |
+
import epitran
|
7 |
+
|
8 |
+
from aip_trainer import PROJECT_ROOT_FOLDER
|
9 |
+
from aip_trainer.models import RuleBasedModels
|
10 |
|
11 |
|
12 |
class TextDataset:
|
|
|
29 |
return self.number_of_samples
|
30 |
|
31 |
|
32 |
+
sample_folder = Path(PROJECT_ROOT_FOLDER / "aip_trainer" / "lambdas")
|
33 |
lambda_database = {}
|
34 |
lambda_ipa_converter = {}
|
35 |
|
36 |
+
with open(sample_folder / 'data_de_en_2.pickle', 'rb') as handle:
|
37 |
df = pickle.load(handle)
|
38 |
|
39 |
lambda_database['de'] = TextDataset(df, 'de')
|
lambdaSpeechToScore.py → aip_trainer/lambdas/lambdaSpeechToScore.py
RENAMED
@@ -1,16 +1,18 @@
|
|
1 |
|
2 |
-
import
|
3 |
import json
|
4 |
import os
|
5 |
-
import WordMatching as wm
|
6 |
-
import utilsFileIO
|
7 |
-
import pronunciationTrainer
|
8 |
-
import base64
|
9 |
import time
|
|
|
10 |
import audioread
|
11 |
import numpy as np
|
|
|
12 |
from torchaudio.transforms import Resample
|
13 |
|
|
|
|
|
|
|
|
|
14 |
|
15 |
trainer_SST_lambda = {
|
16 |
'de': pronunciationTrainer.getTrainer("de"),
|
@@ -42,25 +44,28 @@ def lambda_handler(event, context):
|
|
42 |
}
|
43 |
|
44 |
start = time.time()
|
45 |
-
random_file_name = './'+utilsFileIO.generateRandomString()+'.ogg'
|
46 |
f = open(random_file_name, 'wb')
|
47 |
f.write(file_bytes)
|
48 |
f.close()
|
49 |
-
|
|
|
50 |
|
51 |
start = time.time()
|
52 |
signal, fs = audioread_load(random_file_name)
|
53 |
|
54 |
signal = transform(torch.Tensor(signal)).unsqueeze(0)
|
55 |
|
56 |
-
|
|
|
57 |
|
58 |
result = trainer_SST_lambda[language].processAudioForGivenText(
|
59 |
signal, real_text)
|
60 |
|
61 |
start = time.time()
|
62 |
os.remove(random_file_name)
|
63 |
-
|
|
|
64 |
|
65 |
start = time.time()
|
66 |
real_transcripts_ipa = ' '.join(
|
@@ -90,7 +95,8 @@ def lambda_handler(event, context):
|
|
90 |
|
91 |
pair_accuracy_category = ' '.join(
|
92 |
[str(category) for category in result['pronunciation_categories']])
|
93 |
-
|
|
|
94 |
|
95 |
res = {'real_transcript': result['recording_transcript'],
|
96 |
'ipa_transcript': result['recording_ipa'],
|
|
|
1 |
|
2 |
+
import base64
|
3 |
import json
|
4 |
import os
|
|
|
|
|
|
|
|
|
5 |
import time
|
6 |
+
|
7 |
import audioread
|
8 |
import numpy as np
|
9 |
+
import torch
|
10 |
from torchaudio.transforms import Resample
|
11 |
|
12 |
+
from aip_trainer import WordMatching as wm, app_logger
|
13 |
+
from aip_trainer import pronunciationTrainer
|
14 |
+
from aip_trainer import utilsFileIO
|
15 |
+
|
16 |
|
17 |
trainer_SST_lambda = {
|
18 |
'de': pronunciationTrainer.getTrainer("de"),
|
|
|
44 |
}
|
45 |
|
46 |
start = time.time()
|
47 |
+
random_file_name = './' + utilsFileIO.generateRandomString() + '.ogg'
|
48 |
f = open(random_file_name, 'wb')
|
49 |
f.write(file_bytes)
|
50 |
f.close()
|
51 |
+
duration = time.time() - start
|
52 |
+
app_logger.info(f'Time for saving binary in file: {duration}.')
|
53 |
|
54 |
start = time.time()
|
55 |
signal, fs = audioread_load(random_file_name)
|
56 |
|
57 |
signal = transform(torch.Tensor(signal)).unsqueeze(0)
|
58 |
|
59 |
+
duration = time.time() - start
|
60 |
+
app_logger.info(f'Time for loading .ogg file file: {duration}.')
|
61 |
|
62 |
result = trainer_SST_lambda[language].processAudioForGivenText(
|
63 |
signal, real_text)
|
64 |
|
65 |
start = time.time()
|
66 |
os.remove(random_file_name)
|
67 |
+
duration = time.time() - start
|
68 |
+
app_logger.info(f'Time for deleting file: {duration}')
|
69 |
|
70 |
start = time.time()
|
71 |
real_transcripts_ipa = ' '.join(
|
|
|
95 |
|
96 |
pair_accuracy_category = ' '.join(
|
97 |
[str(category) for category in result['pronunciation_categories']])
|
98 |
+
duration = time.time() - start
|
99 |
+
app_logger.info(f'Time to post-process results: {duration}')
|
100 |
|
101 |
res = {'real_transcript': result['recording_transcript'],
|
102 |
'ipa_transcript': result['recording_ipa'],
|
aip_trainer/lambdas/lambdaTTS.py
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import base64
|
3 |
+
import json
|
4 |
+
import tempfile
|
5 |
+
|
6 |
+
import soundfile as sf
|
7 |
+
|
8 |
+
from aip_trainer import app_logger
|
9 |
+
from aip_trainer.models.models import getTTSModel
|
10 |
+
from aip_trainer.models.AIModels import NeuralTTS
|
11 |
+
|
12 |
+
|
13 |
+
sampling_rate = 16000
|
14 |
+
model_de = getTTSModel('de')
|
15 |
+
model_TTS_lambda = NeuralTTS(model_de, sampling_rate)
|
16 |
+
|
17 |
+
|
18 |
+
def lambda_handler(event, context):
|
19 |
+
|
20 |
+
body = json.loads(event['body'])
|
21 |
+
|
22 |
+
text_string = body['value']
|
23 |
+
|
24 |
+
linear_factor = 0.2
|
25 |
+
audio = model_TTS_lambda.getAudioFromSentence(
|
26 |
+
text_string).detach().numpy()*linear_factor
|
27 |
+
with tempfile.TemporaryFile(prefix="temp_sound_", suffix=".wav") as f1:
|
28 |
+
app_logger.info(f"Saving temp audio to {f1.name}...")
|
29 |
+
# random_file_name = utilsFileIO.generateRandomString(20) + '.wav'
|
30 |
+
# sf.write('./'+random_file_name, audio, 16000)
|
31 |
+
|
32 |
+
sf.write(f1.name, audio, sampling_rate)
|
33 |
+
with open(f1.name, "rb") as f:
|
34 |
+
audio_byte_array = f.read()
|
35 |
+
# os.remove(random_file_name)
|
36 |
+
return {
|
37 |
+
'statusCode': 200,
|
38 |
+
'headers': {
|
39 |
+
'Access-Control-Allow-Headers': '*',
|
40 |
+
'Access-Control-Allow-Origin': '*',
|
41 |
+
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
|
42 |
+
},
|
43 |
+
'body': json.dumps(
|
44 |
+
{
|
45 |
+
"wavBase64": str(base64.b64encode(audio_byte_array))[2:-1],
|
46 |
+
},
|
47 |
+
)
|
48 |
+
}
|
AIModels.py → aip_trainer/models/AIModels.py
RENAMED
@@ -1,6 +1,7 @@
|
|
1 |
-
import ModelInterfaces
|
2 |
-
import torch
|
3 |
import numpy as np
|
|
|
|
|
|
|
4 |
|
5 |
|
6 |
class NeuralASR(ModelInterfaces.IASRModel):
|
|
|
|
|
|
|
1 |
import numpy as np
|
2 |
+
import torch
|
3 |
+
|
4 |
+
from aip_trainer.models import ModelInterfaces
|
5 |
|
6 |
|
7 |
class NeuralASR(ModelInterfaces.IASRModel):
|
ModelInterfaces.py → aip_trainer/models/ModelInterfaces.py
RENAMED
@@ -1,5 +1,5 @@
|
|
1 |
-
|
2 |
import abc
|
|
|
3 |
import numpy as np
|
4 |
|
5 |
|
|
|
|
|
1 |
import abc
|
2 |
+
|
3 |
import numpy as np
|
4 |
|
5 |
|
RuleBasedModels.py → aip_trainer/models/RuleBasedModels.py
RENAMED
@@ -1,9 +1,7 @@
|
|
1 |
-
import ModelInterfaces
|
2 |
-
import torch
|
3 |
-
import numpy as np
|
4 |
-
import epitran
|
5 |
import eng_to_ipa
|
6 |
|
|
|
|
|
7 |
|
8 |
class EpitranPhonemConverter(ModelInterfaces.ITextToPhonemModel):
|
9 |
word_locations_in_samples = None
|
|
|
|
|
|
|
|
|
|
|
1 |
import eng_to_ipa
|
2 |
|
3 |
+
from aip_trainer.models import ModelInterfaces
|
4 |
+
|
5 |
|
6 |
class EpitranPhonemConverter(ModelInterfaces.ITextToPhonemModel):
|
7 |
word_locations_in_samples = None
|
aip_trainer/models/__init__.py
ADDED
File without changes
|
models.py → aip_trainer/models/models.py
RENAMED
@@ -4,9 +4,6 @@ import torch.nn as nn
|
|
4 |
import pickle
|
5 |
|
6 |
|
7 |
-
import pickle
|
8 |
-
|
9 |
-
|
10 |
def getASRModel(language: str) -> nn.Module:
|
11 |
|
12 |
if language == 'de':
|
|
|
4 |
import pickle
|
5 |
|
6 |
|
|
|
|
|
|
|
7 |
def getASRModel(language: str) -> nn.Module:
|
8 |
|
9 |
if language == 'de':
|
pronunciationTrainer.py → aip_trainer/pronunciationTrainer.py
RENAMED
@@ -1,15 +1,14 @@
|
|
|
|
|
|
1 |
|
2 |
-
import torch
|
3 |
-
import numpy as np
|
4 |
-
import models as mo
|
5 |
-
import WordMetrics
|
6 |
-
import WordMatching as wm
|
7 |
import epitran
|
8 |
-
import
|
9 |
-
import
|
10 |
-
|
11 |
-
from
|
12 |
-
import
|
|
|
|
|
13 |
|
14 |
|
15 |
def getTrainer(language: str):
|
@@ -66,7 +65,7 @@ class PronunciationTrainer:
|
|
66 |
def getWordsRelativeIntonation(self, Audio: torch.tensor, word_locations: list):
|
67 |
intonations = torch.zeros((len(word_locations), 1))
|
68 |
intonation_fade_samples = 0.3*self.sampling_rate
|
69 |
-
|
70 |
for word in range(len(word_locations)):
|
71 |
intonation_start = int(np.maximum(
|
72 |
0, word_locations[word][0]-intonation_fade_samples))
|
@@ -85,12 +84,15 @@ class PronunciationTrainer:
|
|
85 |
start = time.time()
|
86 |
recording_transcript, recording_ipa, word_locations = self.getAudioTranscript(
|
87 |
recordedAudio)
|
88 |
-
|
|
|
|
|
89 |
|
90 |
start = time.time()
|
91 |
real_and_transcribed_words, real_and_transcribed_words_ipa, mapped_words_indices = self.matchSampleAndRecordedWords(
|
92 |
real_text, recording_transcript)
|
93 |
-
|
|
|
94 |
|
95 |
start_time, end_time = self.getWordLocationsFromRecordInSeconds(
|
96 |
word_locations, mapped_words_indices)
|
|
|
1 |
+
import time
|
2 |
+
from string import punctuation
|
3 |
|
|
|
|
|
|
|
|
|
|
|
4 |
import epitran
|
5 |
+
import numpy as np
|
6 |
+
import torch
|
7 |
+
|
8 |
+
from . import WordMatching as wm
|
9 |
+
from . import WordMetrics
|
10 |
+
from . import app_logger
|
11 |
+
from .models import AIModels, ModelInterfaces as mi, RuleBasedModels, models as mo
|
12 |
|
13 |
|
14 |
def getTrainer(language: str):
|
|
|
65 |
def getWordsRelativeIntonation(self, Audio: torch.tensor, word_locations: list):
|
66 |
intonations = torch.zeros((len(word_locations), 1))
|
67 |
intonation_fade_samples = 0.3*self.sampling_rate
|
68 |
+
app_logger.info(intonations.shape)
|
69 |
for word in range(len(word_locations)):
|
70 |
intonation_start = int(np.maximum(
|
71 |
0, word_locations[word][0]-intonation_fade_samples))
|
|
|
84 |
start = time.time()
|
85 |
recording_transcript, recording_ipa, word_locations = self.getAudioTranscript(
|
86 |
recordedAudio)
|
87 |
+
|
88 |
+
duration = time.time() - start
|
89 |
+
app_logger.info(f'Time for NN to transcript audio: {duration}.')
|
90 |
|
91 |
start = time.time()
|
92 |
real_and_transcribed_words, real_and_transcribed_words_ipa, mapped_words_indices = self.matchSampleAndRecordedWords(
|
93 |
real_text, recording_transcript)
|
94 |
+
duration = time.time() - start
|
95 |
+
app_logger.info(f'Time for matching transcripts: {duration}.')
|
96 |
|
97 |
start_time, end_time = self.getWordLocationsFromRecordInSeconds(
|
98 |
word_locations, mapped_words_indices)
|
aip_trainer/utils/__init__.py
ADDED
File without changes
|
aip_trainer/utils/session_logger.py
ADDED
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
import sys
|
3 |
+
|
4 |
+
import structlog
|
5 |
+
from structlog.types import EventDict, Processor
|
6 |
+
|
7 |
+
|
8 |
+
# https://github.com/hynek/structlog/issues/35#issuecomment-591321744
|
9 |
+
def rename_event_key(_, __, event_dict: EventDict) -> EventDict:
|
10 |
+
"""
|
11 |
+
Log entries keep the text message in the `event` field, but Datadog
|
12 |
+
uses the `message` field. This processor moves the value from one field to
|
13 |
+
the other.
|
14 |
+
See https://github.com/hynek/structlog/issues/35#issuecomment-591321744
|
15 |
+
"""
|
16 |
+
event_dict["message"] = event_dict.pop("event")
|
17 |
+
return event_dict
|
18 |
+
|
19 |
+
|
20 |
+
def drop_color_message_key(_, __, event_dict: EventDict) -> EventDict:
|
21 |
+
"""
|
22 |
+
Uvicorn logs the message a second time in the extra `color_message`, but we don't
|
23 |
+
need it. This processor drops the key from the event dict if it exists.
|
24 |
+
"""
|
25 |
+
event_dict.pop("color_message", None)
|
26 |
+
return event_dict
|
27 |
+
|
28 |
+
|
29 |
+
def setup_logging(json_logs: bool = False, log_level: str = "INFO"):
|
30 |
+
"""Enhance the configuration of structlog.
|
31 |
+
Needed for correlation id injection with fastapi middleware in samgis-web.
|
32 |
+
After the use of logging_middleware() in samgis_web.web.middlewares, add also the CorrelationIdMiddleware from
|
33 |
+
'asgi_correlation_id' package. (See 'tests/web/test_middlewares.py' in samgis_web).
|
34 |
+
To change an input parameter like the log level, re-run the function changing the parameter
|
35 |
+
(no need to re-instantiate the logger instance: it's a hot change)
|
36 |
+
|
37 |
+
Args:
|
38 |
+
json_logs: set logs in json format
|
39 |
+
log_level: log level string
|
40 |
+
|
41 |
+
Returns:
|
42 |
+
|
43 |
+
"""
|
44 |
+
timestamper = structlog.processors.TimeStamper(fmt="iso")
|
45 |
+
|
46 |
+
shared_processors: list[Processor] = [
|
47 |
+
structlog.contextvars.merge_contextvars,
|
48 |
+
structlog.stdlib.add_logger_name,
|
49 |
+
structlog.stdlib.add_log_level,
|
50 |
+
structlog.stdlib.PositionalArgumentsFormatter(),
|
51 |
+
structlog.stdlib.ExtraAdder(),
|
52 |
+
drop_color_message_key,
|
53 |
+
timestamper,
|
54 |
+
structlog.processors.StackInfoRenderer(),
|
55 |
+
# adapted from https://www.structlog.org/en/stable/standard-library.html
|
56 |
+
# If the "exc_info" key in the event dict is either true or a
|
57 |
+
# sys.exc_info() tuple, remove "exc_info" and render the exception
|
58 |
+
# with traceback into the "exception" key.
|
59 |
+
structlog.processors.format_exc_info,
|
60 |
+
# If some value is in bytes, decode it to a Unicode str.
|
61 |
+
structlog.processors.UnicodeDecoder(),
|
62 |
+
# Add callsite parameters.
|
63 |
+
structlog.processors.CallsiteParameterAdder(
|
64 |
+
{
|
65 |
+
structlog.processors.CallsiteParameter.FUNC_NAME,
|
66 |
+
structlog.processors.CallsiteParameter.LINENO,
|
67 |
+
}
|
68 |
+
),
|
69 |
+
# Render the final event dict as JSON.
|
70 |
+
]
|
71 |
+
|
72 |
+
if json_logs:
|
73 |
+
# We rename the `event` key to `message` only in JSON logs, as Datadog looks for the
|
74 |
+
# `message` key but the pretty ConsoleRenderer looks for `event`
|
75 |
+
shared_processors.append(rename_event_key)
|
76 |
+
# Format the exception only for JSON logs, as we want to pretty-print them when
|
77 |
+
# using the ConsoleRenderer
|
78 |
+
shared_processors.append(structlog.processors.format_exc_info)
|
79 |
+
|
80 |
+
structlog.configure(
|
81 |
+
processors=shared_processors
|
82 |
+
+ [
|
83 |
+
# Prepare event dict for `ProcessorFormatter`.
|
84 |
+
structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
|
85 |
+
],
|
86 |
+
logger_factory=structlog.stdlib.LoggerFactory(),
|
87 |
+
cache_logger_on_first_use=True,
|
88 |
+
)
|
89 |
+
|
90 |
+
log_renderer: structlog.types.Processor
|
91 |
+
if json_logs:
|
92 |
+
log_renderer = structlog.processors.JSONRenderer()
|
93 |
+
else:
|
94 |
+
log_renderer = structlog.dev.ConsoleRenderer()
|
95 |
+
|
96 |
+
formatter = structlog.stdlib.ProcessorFormatter(
|
97 |
+
# These run ONLY on `logging` entries that do NOT originate within
|
98 |
+
# structlog.
|
99 |
+
foreign_pre_chain=shared_processors,
|
100 |
+
# These run on ALL entries after the pre_chain is done.
|
101 |
+
processors=[
|
102 |
+
# Remove _record & _from_structlog.
|
103 |
+
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
|
104 |
+
log_renderer,
|
105 |
+
],
|
106 |
+
)
|
107 |
+
|
108 |
+
handler = logging.StreamHandler()
|
109 |
+
# Use OUR `ProcessorFormatter` to format all `logging` entries.
|
110 |
+
handler.setFormatter(formatter)
|
111 |
+
root_logger = logging.getLogger()
|
112 |
+
root_logger.addHandler(handler)
|
113 |
+
root_logger.setLevel(log_level.upper())
|
114 |
+
|
115 |
+
for _log in ["uvicorn", "uvicorn.error"]:
|
116 |
+
# Clear the log handlers for uvicorn loggers, and enable propagation
|
117 |
+
# so the messages are caught by our root logger and formatted correctly
|
118 |
+
# by structlog
|
119 |
+
logging.getLogger(_log).handlers.clear()
|
120 |
+
logging.getLogger(_log).propagate = True
|
121 |
+
|
122 |
+
# Since we re-create the access logs ourselves, to add all information
|
123 |
+
# in the structured log (see the `logging_middleware` in main.py), we clear
|
124 |
+
# the handlers and prevent the logs to propagate to a logger higher up in the
|
125 |
+
# hierarchy (effectively rendering them silent).
|
126 |
+
logging.getLogger("uvicorn.access").handlers.clear()
|
127 |
+
logging.getLogger("uvicorn.access").propagate = False
|
128 |
+
|
129 |
+
def handle_exception(exc_type, exc_value, exc_traceback):
|
130 |
+
"""
|
131 |
+
Log any uncaught exception instead of letting it be printed by Python
|
132 |
+
(but leave KeyboardInterrupt untouched to allow users to Ctrl+C to stop)
|
133 |
+
See https://stackoverflow.com/a/16993115/3641865
|
134 |
+
"""
|
135 |
+
if issubclass(exc_type, KeyboardInterrupt):
|
136 |
+
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
137 |
+
return
|
138 |
+
|
139 |
+
root_logger.error(
|
140 |
+
"Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)
|
141 |
+
)
|
142 |
+
|
143 |
+
sys.excepthook = handle_exception
|
utilsFileIO.py → aip_trainer/utilsFileIO.py
RENAMED
File without changes
|
lambdaTTS.py
DELETED
@@ -1,46 +0,0 @@
|
|
1 |
-
|
2 |
-
import models
|
3 |
-
import soundfile as sf
|
4 |
-
import json
|
5 |
-
import AIModels
|
6 |
-
#from flask import Response
|
7 |
-
import utilsFileIO
|
8 |
-
import os
|
9 |
-
import base64
|
10 |
-
|
11 |
-
sampling_rate = 16000
|
12 |
-
model_TTS_lambda = AIModels.NeuralTTS(models.getTTSModel('de'), sampling_rate)
|
13 |
-
|
14 |
-
|
15 |
-
def lambda_handler(event, context):
|
16 |
-
|
17 |
-
body = json.loads(event['body'])
|
18 |
-
|
19 |
-
text_string = body['value']
|
20 |
-
|
21 |
-
linear_factor = 0.2
|
22 |
-
audio = model_TTS_lambda.getAudioFromSentence(
|
23 |
-
text_string).detach().numpy()*linear_factor
|
24 |
-
random_file_name = utilsFileIO.generateRandomString(20)+'.wav'
|
25 |
-
|
26 |
-
sf.write('./'+random_file_name, audio, 16000)
|
27 |
-
|
28 |
-
with open(random_file_name, "rb") as f:
|
29 |
-
audio_byte_array = f.read()
|
30 |
-
|
31 |
-
os.remove(random_file_name)
|
32 |
-
|
33 |
-
|
34 |
-
return {
|
35 |
-
'statusCode': 200,
|
36 |
-
'headers': {
|
37 |
-
'Access-Control-Allow-Headers': '*',
|
38 |
-
'Access-Control-Allow-Origin': '*',
|
39 |
-
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
|
40 |
-
},
|
41 |
-
'body': json.dumps(
|
42 |
-
{
|
43 |
-
"wavBase64": str(base64.b64encode(audio_byte_array))[2:-1],
|
44 |
-
},
|
45 |
-
)
|
46 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/javascript/callbacks.js
CHANGED
@@ -105,7 +105,7 @@ const UIRecordingError = () => {
|
|
105 |
//################### Application state functions #######################
|
106 |
function updateScore(currentPronunciationScore) {
|
107 |
|
108 |
-
if (isNaN(currentPronunciationScore))
|
109 |
return;
|
110 |
currentScore += currentPronunciationScore * scoreMultiplier;
|
111 |
currentScore = Math.round(currentScore);
|
|
|
105 |
//################### Application state functions #######################
|
106 |
function updateScore(currentPronunciationScore) {
|
107 |
|
108 |
+
if (Number.isNaN(currentPronunciationScore))
|
109 |
return;
|
110 |
currentScore += currentPronunciationScore * scoreMultiplier;
|
111 |
currentScore = Math.round(currentScore);
|
tests/__init__.py
ADDED
File without changes
|
unitTests.py → tests/unitTests.py
RENAMED
@@ -1,11 +1,11 @@
|
|
|
|
1 |
import unittest
|
2 |
|
3 |
-
import ModelInterfaces
|
4 |
-
import lambdaGetSample
|
5 |
-
import RuleBasedModels
|
6 |
import epitran
|
7 |
-
|
8 |
-
import
|
|
|
|
|
9 |
|
10 |
|
11 |
def test_category(category: int, threshold_min: int, threshold_max: int):
|
@@ -40,12 +40,12 @@ class TestDataset(unittest.TestCase):
|
|
40 |
self.assertTrue(test_category(3, 20, 10000))
|
41 |
|
42 |
|
43 |
-
def check_phonem_converter(converter: ModelInterfaces.ITextToPhonemModel,
|
44 |
-
output = converter.convertToPhonem(
|
45 |
|
46 |
is_correct = output == expected_output
|
47 |
if not is_correct:
|
48 |
-
print('Conversion from "',
|
49 |
expected_output, '", but was "', output, '"')
|
50 |
return is_correct
|
51 |
|
@@ -60,7 +60,6 @@ class TestPhonemConverter(unittest.TestCase):
|
|
60 |
def test_german(self):
|
61 |
phonem_converter = RuleBasedModels.EpitranPhonemConverter(
|
62 |
epitran.Epitran('deu-Latn'))
|
63 |
-
|
64 |
self.assertTrue(check_phonem_converter(
|
65 |
phonem_converter, 'Hallo, das ist ein Test', 'haloː, dɑːs ɪst ain tɛst'))
|
66 |
|
|
|
1 |
+
import json
|
2 |
import unittest
|
3 |
|
|
|
|
|
|
|
4 |
import epitran
|
5 |
+
|
6 |
+
from aip_trainer.models import ModelInterfaces, RuleBasedModels
|
7 |
+
from aip_trainer import pronunciationTrainer
|
8 |
+
from aip_trainer.lambdas import lambdaGetSample
|
9 |
|
10 |
|
11 |
def test_category(category: int, threshold_min: int, threshold_max: int):
|
|
|
40 |
self.assertTrue(test_category(3, 20, 10000))
|
41 |
|
42 |
|
43 |
+
def check_phonem_converter(converter: ModelInterfaces.ITextToPhonemModel, input_phonem: str, expected_output: str):
|
44 |
+
output = converter.convertToPhonem(input_phonem)
|
45 |
|
46 |
is_correct = output == expected_output
|
47 |
if not is_correct:
|
48 |
+
print('Conversion from "', input_phonem, '" should be "',
|
49 |
expected_output, '", but was "', output, '"')
|
50 |
return is_correct
|
51 |
|
|
|
60 |
def test_german(self):
|
61 |
phonem_converter = RuleBasedModels.EpitranPhonemConverter(
|
62 |
epitran.Epitran('deu-Latn'))
|
|
|
63 |
self.assertTrue(check_phonem_converter(
|
64 |
phonem_converter, 'Hallo, das ist ein Test', 'haloː, dɑːs ɪst ain tɛst'))
|
65 |
|
webApp.py
CHANGED
@@ -4,9 +4,10 @@ import os
|
|
4 |
from flask_cors import CORS
|
5 |
import json
|
6 |
|
7 |
-
import lambdaTTS
|
8 |
-
import lambdaSpeechToScore
|
9 |
-
import lambdaGetSample
|
|
|
10 |
|
11 |
app = Flask(__name__)
|
12 |
cors = CORS(app)
|
|
|
4 |
from flask_cors import CORS
|
5 |
import json
|
6 |
|
7 |
+
from aip_trainer.lambdas import lambdaTTS
|
8 |
+
from aip_trainer.lambdas import lambdaSpeechToScore
|
9 |
+
from aip_trainer.lambdas import lambdaGetSample
|
10 |
+
|
11 |
|
12 |
app = Flask(__name__)
|
13 |
cors = CORS(app)
|