Emeritus-21 commited on
Commit
e5f524a
·
verified ·
1 Parent(s): f8271ff

Upload 2 files

Browse files
Files changed (2) hide show
  1. templates/browser-detect.html +1272 -0
  2. templates/temp.html +593 -0
templates/browser-detect.html ADDED
@@ -0,0 +1,1272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <!--
3
+ SignSpeak - A Communicator For Signers
4
+
5
+ Copyright (C) 2025 Shantanu Khedkar
6
+
7
+ This program is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ This program is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
19
+ -->
20
+
21
+ <html lang="en">
22
+
23
+ <head></head>
24
+ <meta charset="UTF-8">
25
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
26
+
27
+ <title>Sign Language Interpreter</title>
28
+
29
+
30
+ <script>
31
+ window.console = window.console || function (t) { };
32
+ </script>
33
+ <!-- For Android
34
+ <link rel="stylesheet" type="text/css" href="http://127.0.0.1:8125/assets/static/browser_detect.css" />
35
+ -->
36
+ <!-- For Web-->
37
+ <link rel="stylesheet" type="text/css" href="static/browser_detect.css" />
38
+
39
+
40
+ </head>
41
+
42
+ <body translate="no" style="overflow: scroll;">
43
+ <!-- For Android
44
+ <script src="../assets/ipc/androidjs.js"></script>
45
+ <script src="http://127.0.0.1:8125/assets/static/drawing_utils.js" crossorigin="anonymous"></script>
46
+ <script src="http://127.0.0.1:8125/assets/static/hands.js" crossorigin="anonymous"></script>
47
+ <script src="http://127.0.0.1:8125/assets/static/tfjs-core"></script>
48
+ <script src="http://127.0.0.1:8125/assets/static/tfjs-backend-cpu"></script>
49
+ <script src="http://127.0.0.1:8125/assets/static/tf-tflite.min.js"></script>
50
+ <script src="http://127.0.0.1:8125/assets/static/vision_wasm_internal.js" crossorigin="anonymous"></script>
51
+ -->
52
+ <!-- For Web -->
53
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"
54
+ crossorigin="anonymous"></script>
55
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
56
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core"></script>
57
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-cpu"></script>
58
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-tflite/dist/tf-tflite.min.js"></script>
59
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.js"
60
+ crossorigin="anonymous"></script>
61
+
62
+ <section id="home-page">
63
+ <div class="container">
64
+
65
+ <video id="webcam" style="display:none" autoplay playsinline muted></video>
66
+ <div class="canvas_wrapper" id="canvas_wrapper">
67
+ <button id="switch-camera"
68
+ style="display:none; position: absolute; top:10px; left:10px; padding:5px; height:40px; width:40px; text-align: center; border-radius: 12.25px; font-size: 20px; font-weight: 900; border:none; background-color: #ffffff2e; backdrop-filter: blur(30px); color:black; box-shadow: 0px 4px 20px 4px rgba(0, 0, 0, 0.38); z-index:100">
69
+ <span>⟳</span>
70
+ </button>
71
+ <canvas class="output_canvas" id="output_canvas" width="100%" height="500%"></canvas>
72
+ <center>
73
+ <button id="webcamButton" style="font-weight: 600;">
74
+ <span>Enable Webcam</span>
75
+ </button>
76
+ <div class="mode_switch_wrapper">
77
+ <div class="mode_switch" id="mode-switch" style="display: none;">
78
+ <div class="mode_selector" id="modeSelector"></div>
79
+ <div class="mode_switch_modeBtn">Text</div>
80
+ <div class="mode_switch_modeBtn">Word</div>
81
+ </div>
82
+ </div>
83
+ </center>
84
+
85
+ </div>
86
+ </div>
87
+ <center>
88
+ <img id="output_image" style="display:none"></img>
89
+
90
+ <div class="wrapper_result">
91
+ <div id="predicted_result">></div>
92
+ </div>
93
+ <div class="wrapper_text">
94
+ <textarea id="text" onkeyup="set_output_array(this.value)"></textarea>
95
+ <div class="textControls">
96
+ <button id="undoButton" onclick="undo()">
97
+ <svg xmlns="http://www.w3.org/2000/svg" height="16px" width="16px"
98
+ viewBox="0 0 576 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
99
+ <path fill="#fcfcfc"
100
+ d="M576 128c0-35.3-28.7-64-64-64L205.3 64c-17 0-33.3 6.7-45.3 18.7L9.4 233.4c-6 6-9.4 14.1-9.4 22.6s3.4 16.6 9.4 22.6L160 429.3c12 12 28.3 18.7 45.3 18.7L512 448c35.3 0 64-28.7 64-64l0-256zM271 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z" />
101
+ </svg>
102
+ </button>
103
+ <button id="speakButton" onclick="speak(document.getElementById('text').value)">
104
+ <span>Listen</span>
105
+ <svg xmlns="http://www.w3.org/2000/svg" height="16px" width="16px"
106
+ viewBox="0 0 640 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
107
+ <path fill="#fcfcfc"
108
+ d="M533.6 32.5C598.5 85.2 640 165.8 640 256s-41.5 170.7-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64l0 384c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352 64 352c-35.3 0-64-28.7-64-64l0-64c0-35.3 28.7-64 64-64l67.8 0L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z" />
109
+ </svg>
110
+ </button>
111
+ <button id="clearButton" onclick="clear_output_array()">
112
+ <svg xmlns="http://www.w3.org/2000/svg" height="16px" width="16px"
113
+ viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
114
+ <path fill="#fcfcfc"
115
+ d="M135.2 17.7L128 32 32 32C14.3 32 0 46.3 0 64S14.3 96 32 96l384 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-96 0-7.2-14.3C307.4 6.8 296.3 0 284.2 0L163.8 0c-12.1 0-23.2 6.8-28.6 17.7zM416 128L32 128 53.2 467c1.6 25.3 22.6 45 47.9 45l245.8 0c25.3 0 46.3-19.7 47.9-45L416 128z" />
116
+ </svg>
117
+ </button>
118
+ </div>
119
+
120
+ <audio id="audioPlayer">-</audio>
121
+ </div>
122
+
123
+ <div id="logUI" style="display:none"></div>
124
+ </center>
125
+ </section>
126
+ <section id="info-page" style="display:none; padding: 10px;">
127
+ <center>
128
+
129
+ <svg width="77.5" height="103" viewBox="0 0 310 412" fill="none" xmlns="http://www.w3.org/2000/svg">
130
+ <g filter="url(#filter0_d_21435_307)">
131
+ <path
132
+ d="M186.81 198.496C176.153 230.73 130.699 230.73 120.042 198.496C118.566 194.034 117.995 189.322 118.361 184.635L129.694 27.7729C130.663 15.3457 140.999 5.75757 153.426 5.75757C165.852 5.75757 176.188 15.3457 177.158 27.7729L188.491 184.635C188.857 189.322 188.285 194.034 186.81 198.496Z"
133
+ fill="#FF6E7D" />
134
+ <path
135
+ d="M129.248 210.461C131.138 242.358 93.345 260.329 69.9261 238.67C65.7586 234.815 62.575 230.013 60.6432 224.667L21.653 116.772C17.3477 104.858 22.7777 91.6137 34.1929 86.1855C45.6081 80.7572 59.2664 84.9246 65.7351 95.8096L124.318 194.388C127.221 199.272 128.911 204.784 129.248 210.461Z"
136
+ fill="#FF6E7D" />
137
+ <path
138
+ d="M92.2772 198.028C93.9837 196.578 95.8085 195.273 97.7326 194.129L171.089 150.508C188.74 140.012 211.322 151.175 213.777 171.611C214.485 177.507 217.012 183.034 221.005 187.418L268.354 239.422C278.948 251.057 281.761 267.839 275.544 282.309L245.615 351.969C239.301 366.663 224.879 376.182 208.928 376.182H97.9241C81.9729 376.182 67.5505 366.663 61.2371 351.969L32.802 285.787C25.8462 269.597 30.2544 250.749 43.6623 239.352L92.2772 198.028Z"
139
+ fill="#FF6E7D" />
140
+ <path
141
+ d="M55.1238 304.632C26.6986 296.743 20.0237 259.356 43.9529 242.062C49.3081 238.192 55.6418 235.913 62.2284 235.486L152.085 229.664C164.75 228.844 176.01 237.693 178.247 250.224C180.485 262.756 172.986 274.975 160.824 278.614L74.5386 304.437C68.2137 306.33 61.4852 306.397 55.1238 304.632Z"
142
+ fill="#D6505E" />
143
+ <path
144
+ d="M239.546 153.001C228.433 125.986 190.518 125.338 178.413 151.957C176.038 157.178 175.042 162.922 175.519 168.637L183.682 266.336C184.68 278.282 194.526 287.521 206.477 287.725C218.427 287.929 228.609 279.033 230.048 267.128L241.82 169.769C242.509 164.074 241.726 158.3 239.546 153.001Z"
145
+ fill="#D6505E" />
146
+ <path
147
+ d="M286.006 191.73C274.39 169.568 243.251 168.174 229.541 189.203C225.491 195.415 223.646 202.808 224.305 210.184L229.849 272.21C230.925 284.253 240.697 293.621 252.745 294.16C264.794 294.699 275.429 286.245 277.666 274.35L289.188 213.088C290.559 205.803 289.437 198.277 286.006 191.73Z"
148
+ fill="#A8404B" />
149
+ </g>
150
+ <defs>
151
+ <filter id="filter0_d_21435_307" x="0.181641" y="0.757568" width="309.576" height="410.424"
152
+ filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
153
+ <feFlood flood-opacity="0" result="BackgroundImageFix" />
154
+ <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
155
+ result="hardAlpha" />
156
+ <feOffset dy="15" />
157
+ <feGaussianBlur stdDeviation="10" />
158
+ <feComposite in2="hardAlpha" operator="out" />
159
+ <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0" />
160
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_21435_307" />
161
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_21435_307" result="shape" />
162
+ </filter>
163
+ </defs>
164
+ </svg>
165
+ <h1>SpeakSign</h1>
166
+ </center>
167
+ <hr>
168
+ <div class="about-card">
169
+ <span>Version</span>
170
+
171
+ <span>1.2.5b</span>
172
+ </div>
173
+ <div class="about-card">
174
+ <span>Author</span>
175
+ <span>Shantanu Khedkar</span>
176
+ </div>
177
+ <div class="about-card">
178
+ <!--For Android
179
+ <img src="http://127.0.0.1:8125/assets/static/ASL.png" style="height:auto; width:auto; margin: -5px"></img>
180
+ -->
181
+ <!--For Web-->
182
+ <img src="static/ASL.png" style="height:auto; width:auto; margin: -5px"></img>
183
+ </div>
184
+ <div class="about-card">
185
+ <span>Source</span>
186
+ <span><a href="https://github.com/Shantanu-Khedkar/silangint"
187
+ target="_blank">https://github.com/Shantanu-Khedkar/silangint</a></span>
188
+ </div>
189
+ <div class="about-card">
190
+ <span>License</span>
191
+ <span>GPL-v3.0 - <a href="https://github.com/Shantanu-Khedkar/silangint/blob/master/LICENSE"
192
+ target="_blank">https://github.com/Shantanu-Khedkar/silangint/blob/master/LICENSE</a></span>
193
+ </div>
194
+ <div class="about-card">
195
+ <span>Copyright Notice</span>
196
+ <span>
197
+ SignSpeak - A Communicator For Signers
198
+ <br>
199
+ Copyright (C) 2025 Shantanu Khedkar
200
+ <br>
201
+ This program is free software: you can redistribute it and/or modify
202
+ it under the terms of the GNU General Public License as published by
203
+ the Free Software Foundation, either version 3 of the License, or
204
+ (at your option) any later version.
205
+ <br>
206
+ This program is distributed in the hope that it will be useful,
207
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
208
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
209
+ GNU General Public License for more details.
210
+ <br>
211
+ You should have received a copy of the GNU General Public License
212
+ along with this program. If not, see https://www.gnu.org/licenses.
213
+ </span>
214
+ </div>
215
+
216
+ </section>
217
+ <section id="settings-page" style="display:none">
218
+ <div
219
+ style="display:inline-flex; align-items:center; gap:10px; padding:12px; margin-bottom: 24px; margin-top: 16px;">
220
+ <div style="padding:10px; display: inherit;" onclick="settingsBack()">
221
+ <svg xmlns="http://www.w3.org/2000/svg" width="30px" height="30px"
222
+ viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
223
+ <path
224
+ d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z" />
225
+ </svg>
226
+ </div>
227
+ <span class="settings-header">Settings</span>
228
+ </div>
229
+ <form id="speechForm" style="display:block;">
230
+ <div class="about-card">
231
+ <label for="language">Select Language:</label>
232
+ <select id="language"></select>
233
+ </div>
234
+ <div class="about-card">
235
+ <label for="voice">Select Voice:</label>
236
+ <select id="voice"></select>
237
+ </div>
238
+ <div class="about-card">
239
+ <label for="rate">Rate:</label>
240
+ <input type="range" id="rate" min="0.1" max="2" step="0.1" value="1">
241
+ <span style="display:none" id="rateValue">1</span>
242
+ </div>
243
+ <div class="about-card">
244
+ <label for="pitch">Pitch:</label>
245
+ <input type="range" id="pitch" min="0" max="2" step="0.1" value="1">
246
+ <span style="display:none" id="pitchValue">1</span>
247
+ </div>
248
+ <div class="about-card">
249
+ <label for="testText">Test Voice:</label>
250
+ <input id="testText" value="Hello, this is a test!" placeholder="Enter test sentence..."
251
+ onkeyup="set_output_array(this.value)"></input>
252
+ <button type="button" id="testSpeakButton"
253
+ onclick="document.getElementById('text').value = document.getElementById('testText').value; document.getElementById('speakButton').click()">
254
+ <a>Listen 🔊</a>
255
+ </button>
256
+ </div>
257
+ <div class="about-card">
258
+ <label for="duration">Input Duration <span id="durationValue"></span>(ms)</label>
259
+ <input type="range" id="duration" min="200" max="1500" step="1" value="300"
260
+ oninput="document.getElementById('durationValue').innerText=this.value">
261
+ </div>
262
+ <div class="about-card">
263
+ <label for="repeat">Repeat Buffer <span id="repeatValue"></span>(ms)</label>
264
+ <input type="range" id="repeat" min="200" max="700" step="1" value="500"
265
+ oninput="document.getElementById('repeatValue').innerText=this.value">
266
+ </div>
267
+ <div class="about-card" style="flex-direction:row; align-items: center;">
268
+ <label style="margin-right: 10px;" for="duration">Natural Input :</label>
269
+
270
+ <label class="switch">
271
+ <input type="checkbox" id="natInp" unchecked>
272
+ <span class="slider round"></span>
273
+ </label>
274
+ </div>
275
+ </form>
276
+ </section>
277
+ <div class="bottom-nav">
278
+ <div id="info-" class="bottom-nav-btn">
279
+ <svg xmlns="http://www.w3.org/2000/svg" height="28px" width="28px"
280
+ viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
281
+ <path fill="#5d5d5d"
282
+ d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336l24 0 0-64-24 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0c13.3 0 24 10.7 24 24l0 88 8 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-80 0c-13.3 0-24-10.7-24-24s10.7-24 24-24zm40-208a32 32 0 1 1 0 64 32 32 0 1 1 0-64z" />
283
+ </svg>
284
+ </div>
285
+ <div id="home-" class="bottom-nav-btn center-btn">
286
+ <!--For Android
287
+ <img src="http://127.0.0.1:8125/assets/static/logo_sil.svg" height="48" width="48px"></img>
288
+ -->
289
+ <img src="/static/logo_sil.svg" height="48" width="48px"></img>
290
+ </div>
291
+ <div id="settings-" class="bottom-nav-btn">
292
+ <svg xmlns="http://www.w3.org/2000/svg" height="28px" width="28px"
293
+ viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->
294
+ <path fill="#5d5d5d"
295
+ d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z" />
296
+ </svg>
297
+ </div>
298
+
299
+ </div>
300
+ <script>
301
+ var word_list = []
302
+ var speechSupported = true
303
+ var prevSpeech = ""
304
+ var prevSettings = ""
305
+
306
+ logUI = document.getElementById("logUI")
307
+ const speechSynthesis = window.speechSynthesis;
308
+ const voiceSelect = document.getElementById('voice');
309
+ const textInput = document.getElementById('text');
310
+ const languageSelect = document.getElementById('language');
311
+ const speakButton = document.getElementById('speakButton');
312
+ const rateInput = document.getElementById('rate');
313
+ const pitchInput = document.getElementById('pitch');
314
+ const rateValue = document.getElementById('rateValue');
315
+ const pitchValue = document.getElementById('pitchValue');
316
+
317
+ let voices = [];
318
+
319
+ const languageNames = {
320
+ 'en': '- English ',
321
+ 'mra': '- Marathi',
322
+ 'mr': '- Marathi',
323
+ 'hi': '- Hindi',
324
+ 'es': 'Spanish',
325
+ 'fr': 'French',
326
+ 'de': 'German',
327
+ 'it': 'Italian',
328
+ 'af': 'Afrikaans',
329
+ 'am': 'Amharic',
330
+ 'an': 'Aragonese',
331
+ 'ar': 'Arabic',
332
+ 'as': 'Assamese',
333
+ 'az': 'Azerbaijani',
334
+ 'bg': 'Bulgarian',
335
+ 'bn': 'Bengali',
336
+ 'bpy': 'Bishnupriya Manipuri',
337
+ 'bs': 'Bosnian',
338
+ 'ca': 'Catalan',
339
+ 'cs': 'Czech',
340
+ 'cy': 'Welsh',
341
+ 'da': 'Danish',
342
+ 'de': 'German',
343
+ 'el': 'Greek',
344
+ 'eo': 'Esperanto',
345
+ 'es': 'Spanish',
346
+ 'et': 'Estonian',
347
+ 'eu': 'Basque',
348
+ 'fa': 'Persian',
349
+ 'fi': 'Finnish',
350
+ 'fr': 'French',
351
+ 'ga': 'Irish',
352
+ 'gd': 'Scottish Gaelic',
353
+ 'gn': 'Guarani',
354
+ 'grc': 'Ancient Greek',
355
+ 'gu': 'Gujarati',
356
+ 'hak': 'Hakka',
357
+ 'hr': 'Croatian',
358
+ 'ht': 'Haitian Creole',
359
+ 'hu': 'Hungarian',
360
+ 'hy': 'Armenian',
361
+ 'ia': 'Interlingua',
362
+ 'id': 'Indonesian',
363
+ 'is': 'Icelandic',
364
+ 'it': 'Italian',
365
+ 'ja': 'Japanese',
366
+ 'jbo': 'Lojban',
367
+ 'ka': 'Georgian',
368
+ 'kk': 'Kazakh',
369
+ 'kl': 'Kalaallisut',
370
+ 'kn': 'Kannada',
371
+ 'ko': 'Korean',
372
+ 'kok': 'Konkani',
373
+ 'ku': 'Kurdish',
374
+ 'ky': 'Kyrgyz',
375
+ 'la': 'Latin',
376
+ 'lfn': 'Lingua Franca Nova',
377
+ 'lt': 'Lithuanian',
378
+ 'lv': 'Latvian',
379
+ 'mi': 'Māori',
380
+ 'mk': 'Macedonian',
381
+ 'ml': 'Malayalam',
382
+ 'ms': 'Malay',
383
+ 'mt': 'Maltese',
384
+ 'my': 'Burmese',
385
+ 'nci': 'Navajo',
386
+ 'ne': 'Nepali',
387
+ 'nl': 'Dutch',
388
+ 'no': 'Norwegian',
389
+ 'om': 'Oromo',
390
+ 'or': 'Odia',
391
+ 'pa': 'Punjabi',
392
+ 'pap': 'Papiamento',
393
+ 'pl': 'Polish',
394
+ 'pt': 'Portuguese',
395
+ 'quc': 'K’iche’',
396
+ 'ro': 'Romanian',
397
+ 'ru': 'Russian',
398
+ 'sd': 'Sindhi',
399
+ 'shn': 'Shan',
400
+ 'si': 'Sinhala',
401
+ 'sk': 'Slovak',
402
+ 'sl': 'Slovenian',
403
+ 'sq': 'Albanian',
404
+ 'sr': 'Serbian',
405
+ 'sv': 'Swedish',
406
+ 'sw': 'Swahili',
407
+ 'ta': 'Tamil',
408
+ 'te': 'Telugu',
409
+ 'tn': 'Tswana',
410
+ 'tr': 'Turkish',
411
+ 'tt': 'Tatar',
412
+ 'ur': 'Urdu',
413
+ 'vi': 'Vietnamese'
414
+
415
+ };
416
+ function undo() {
417
+ word_list.pop()
418
+ textInput.value = word_list.join('')
419
+ }
420
+ function clear() {
421
+ word_list = []
422
+ textInput.value = ''
423
+ }
424
+ function populateVoiceList() {
425
+ voices = speechSynthesis.getVoices();
426
+ updateLanguageList();
427
+ updateVoiceList();
428
+ }
429
+ async function populateTTWVoices() {
430
+ const url = "http://127.0.0.1:8125/assets/static/voicesList.txt";
431
+ try {
432
+ const response = await fetch(url);
433
+ if (!response.ok) {
434
+ throw new Error(`Response status: ${response.status}`);
435
+ }
436
+
437
+ var voicesList = await response.text();
438
+ voicesList = voicesList.split('\n')
439
+ voicesList.pop()
440
+ voicesList.forEach(voice => {
441
+ const option = document.createElement('option');
442
+ option.value = voice;
443
+ option.textContent = voice;
444
+ voiceSelect.appendChild(option);
445
+ })
446
+ } catch (error) {
447
+ console.error(error.message);
448
+ }
449
+ }
450
+ async function populateTTWLangs() {
451
+ const url = "http://127.0.0.1:8125/assets/static/langList.txt";
452
+ try {
453
+ const response = await fetch(url);
454
+ if (!response.ok) {
455
+ throw new Error(`Response status: ${response.status}`);
456
+ }
457
+
458
+ var langsList = await response.text();
459
+ langsList = langsList.split('\n')
460
+ langsList.pop()
461
+ unknownLangs = []
462
+ langsList.forEach(lang => {
463
+ if (!languageNames[lang]) {
464
+ unknownLangs.push(lang)
465
+ } else {
466
+ const option = document.createElement('option');
467
+ option.value = lang;
468
+ option.textContent = languageNames[lang];
469
+ languageSelect.appendChild(option);
470
+ }
471
+ })
472
+ unknownLangs.forEach(lang => {
473
+ const option = document.createElement('option');
474
+ option.value = lang;
475
+ option.textContent = lang;
476
+ languageSelect.appendChild(option);
477
+ })
478
+ } catch (error) {
479
+ console.error(error.message);
480
+ }
481
+
482
+ const optionNodes = Array.from(languageSelect.children);
483
+ const comparator = new Intl.Collator('en'.slice(0, 2)).compare;
484
+ optionNodes.sort((a, b) => comparator(a.textContent, b.textContent));
485
+ optionNodes.forEach((option) => languageSelect.appendChild(option));
486
+ languageSelect.children[0].selected = "selected"
487
+ }
488
+
489
+ function getBaseLanguageCode(lang) {
490
+ return lang.split('-').slice(0, 2).join('-');
491
+ }
492
+ function getBaseBaseLanguageCode(lang) {
493
+ return lang.split('-').slice(0, 1).join('-');
494
+ }
495
+ function getModifierLanguageCode(lang) {
496
+ return lang.split('-').slice(1, 3).join('-');
497
+ }
498
+
499
+ function updateLanguageList() {
500
+ const uniqueLanguages = new Set(voices.map(voice => getBaseLanguageCode(voice.lang)));
501
+ languageSelect.innerHTML = '';
502
+
503
+ Object.keys(languageNames).forEach(lang => {
504
+ if (uniqueLanguages.has(lang)) {
505
+ const option = document.createElement('option');
506
+ option.value = lang;
507
+ option.textContent = languageNames[lang];
508
+ languageSelect.appendChild(option);
509
+ }
510
+ });
511
+
512
+ uniqueLanguages.forEach(lang => {
513
+ if (!languageNames[lang]) {
514
+ if (!languageNames[getBaseBaseLanguageCode(lang)]) {
515
+ const option = document.createElement('option');
516
+ option.value = lang;
517
+ option.textContent = lang;
518
+ languageSelect.appendChild(option);
519
+ }
520
+ else {
521
+ const option = document.createElement('option');
522
+ option.value = lang;
523
+ option.textContent = `${languageNames[getBaseBaseLanguageCode(lang)]} (${getModifierLanguageCode(lang)})`;
524
+ languageSelect.appendChild(option);
525
+ }
526
+ }
527
+ });
528
+
529
+ const optionNodes = Array.from(languageSelect.children);
530
+ const comparator = new Intl.Collator('en'.slice(0, 2)).compare;
531
+ optionNodes.sort((a, b) => comparator(a.textContent, b.textContent));
532
+ optionNodes.forEach((option) => languageSelect.appendChild(option));
533
+ languageSelect.children[0].selected = "selected"
534
+ }
535
+
536
+ function updateVoiceList() {
537
+ const selectedLanguage = languageSelect.value;
538
+ voiceSelect.innerHTML = '';
539
+
540
+ voices.forEach((voice) => {
541
+ if (getBaseLanguageCode(voice.lang) === selectedLanguage) {
542
+ const option = document.createElement('option');
543
+ option.value = voice.name;
544
+ option.textContent = `${voice.name} (${voice.lang})`;
545
+ voiceSelect.appendChild(option);
546
+ }
547
+ });
548
+ }
549
+ function settingsBack() {
550
+ document.getElementById("home-").click()
551
+ }
552
+ function logMessage(msg) {
553
+ const span = document.createElement('span');
554
+ span.textContent = msg;
555
+ logUI.appendChild(span);
556
+ logUI.appendChild(document.createElement('br')); // Add a line break
557
+ }
558
+
559
+ const originalFetch = window.fetch;
560
+
561
+ // Override the fetch function
562
+ window.fetch = async function (input, init) {
563
+ // Convert input to URL if it's a Request object
564
+ const url = typeof input === 'string' ? input : input.url;
565
+ var newUrl = url
566
+ if (url == 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.wasm') {
567
+ //newUrl = 'http://127.0.0.1:8125/assets/static/vision_wasm_internal.wasm' //For Android
568
+ newUrl = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.wasm' // For Web
569
+
570
+ }
571
+ console.log("This was FETCHED: ", newUrl)
572
+ // Call the original fetch function with the new URL
573
+ return originalFetch(newUrl, init);
574
+ };
575
+
576
+
577
+
578
+
579
+ if ('speechSynthesis' in window) {
580
+ speechSynthesis.onvoiceschanged = () => {
581
+ populateVoiceList();
582
+ };
583
+
584
+ languageSelect.addEventListener('change', updateVoiceList);
585
+
586
+ rateInput.addEventListener('input', () => {
587
+ rateValue.textContent = rateInput.value;
588
+ });
589
+
590
+ pitchInput.addEventListener('input', () => {
591
+ pitchValue.textContent = pitchInput.value;
592
+ });
593
+
594
+ speakButton.addEventListener('click', () => {
595
+ const utterance = new SpeechSynthesisUtterance(textInput.value);
596
+ const selectedVoice = voices.find(voice => voice.name === voiceSelect.value);
597
+ utterance.voice = selectedVoice;
598
+ utterance.rate = rateInput.value;
599
+ utterance.pitch = pitchInput.value;
600
+ speechSynthesis.speak(utterance);
601
+ });
602
+
603
+ populateVoiceList()
604
+ // Create an utterance object ⣿
605
+
606
+ } else {
607
+ speechSupported = false;
608
+ console.log('Text-to-speech not supported.');
609
+
610
+ populateTTWLangs()
611
+ populateTTWVoices()
612
+ }
613
+
614
+ function speak(toSpeak) {
615
+ console.log("speech api support", speechSupported)
616
+ console.log("condition: ", !speechSupported)
617
+ console.log("condition2: ", speechSupported == false)
618
+ console.log("speech api support", speechSupported)
619
+ if (!speechSupported) {
620
+ const audioPlayer = document.getElementById('audioPlayer');
621
+ var currSettings = '&v=' + encodeURIComponent(languageSelect.value + "+" + voiceSelect.value) + '&r=' + Math.round(rateInput.value * 7.5).toString() + '&p=' + Math.round(pitchInput.value * 49.5).toString()
622
+ if ((prevSpeech != toSpeak) || (prevSettings != currSettings)) {
623
+ prevSpeech = toSpeak
624
+ prevSettings = currSettings
625
+ audioPlayer.src = 'http://127.0.0.1:8125/speech?t=' + encodeURIComponent(toSpeak) + currSettings
626
+ console.log("Set src: ", audioPlayer.src)
627
+ }
628
+
629
+ audioPlayer.play() // Play the audio
630
+ .then(() => {
631
+
632
+ console.log('Audio is playing');
633
+ })
634
+ .catch(error => {
635
+ console.error('Error playing audio:', error);
636
+ prevSpeech = ''
637
+ });
638
+ } else if ('speechSynthesis' in window) {
639
+ console.log("probably spoken")
640
+ } else {
641
+ console.log("Text to speech is now not supported")
642
+ }
643
+ }
644
+
645
+
646
+
647
+ function set_output_array(text) {
648
+ console.log(text)
649
+ word_list = text.split("");
650
+ console.log(word_list)
651
+ }
652
+ function clear_output_array() {
653
+ word_list = [];
654
+ textInput.value = ""
655
+ }
656
+
657
+ </script>
658
+
659
+ <script type="module">
660
+ document.getElementById("info-").addEventListener("click", switchPage.bind(null, "info-"));
661
+ document.getElementById("home-").addEventListener("click", switchPage.bind(null, "home-"));
662
+ document.getElementById("settings-").addEventListener("click", switchPage.bind(null, "settings-"));
663
+
664
+ //import { HandLandmarker, FilesetResolver } from "http://127.0.0.1:8125/assets/static/tasks-vision@0.10.0" // For Android
665
+ import { HandLandmarker, FilesetResolver } from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0"; // For Web
666
+ let handLandmarker = undefined;
667
+ let runningMode = "IMAGE";
668
+ let enableWebcamButton;
669
+ let webcamRunning = false;
670
+ var time_since_letter = 0
671
+ var last_letter_time = 0
672
+ var maxPercentage = 1
673
+
674
+ function inputFrequency() {
675
+ return document.getElementById('duration').value
676
+ }
677
+ function repeatBuffer() {
678
+ return document.getElementById('repeat').value
679
+ }
680
+ document.getElementById('durationValue').innerText = inputFrequency()
681
+ document.getElementById('repeatValue').innerText = repeatBuffer()
682
+ var is_first_run = 1
683
+ const letter_list = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"]
684
+ const phrase_list = [" Good", " Hello", " How're", " I'm", " Thanks", " You"]
685
+ var index_list = letter_list
686
+ // Before we can use HandLandmarker class we must wait for it to finish
687
+ // loading. Machine Learning models can be large and take a moment to
688
+ // get everything needed to run.
689
+ const createHandLandmarker = async () => {
690
+ const vision = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm"); // This doesnt really matter as this is already imported somewhere else, and the code runs fine without the request
691
+ handLandmarker = await HandLandmarker.createFromOptions(vision, {
692
+ baseOptions: {
693
+ //modelAssetPath: `http://127.0.0.1:8125/assets/static/hand_landmarker.task`, // For Android
694
+ modelAssetPath: `https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task`, // For Web
695
+ delegate: "GPU"
696
+ },
697
+ runningMode: runningMode,
698
+ numHands: 1
699
+ });
700
+ };
701
+ createHandLandmarker();
702
+
703
+ //const MODEL_PATH = "http://127.0.0.1:8125/assets/static/model.tflite" // For Android
704
+ //const WORD_MODEL = "http://127.0.0.1:8125/assets/static/word.tflite" // For Android
705
+ const MODEL_PATH = "/exported" // For Web
706
+ const WORD_MODEL = "/word" // For Web
707
+
708
+ const letterDetector = tflite.loadTFLiteModel(MODEL_PATH);
709
+ const wordDetector = tflite.loadTFLiteModel(WORD_MODEL);
710
+ var objectDetector = letterDetector
711
+ /********************************************************************
712
+ // Continuously grab images
713
+ ********************************************************************/
714
+ document.getElementById("mode-switch").addEventListener('click', function () {
715
+ document.getElementById('modeSelector').classList.toggle('right')
716
+ var modeState = Array.from(document.getElementById('modeSelector').classList).includes('right')
717
+ console.log("swicth", modeState)
718
+ if (modeState) {
719
+ console.log("word")
720
+ objectDetector = wordDetector
721
+ index_list = phrase_list
722
+ } else {
723
+ console.log("letter")
724
+ objectDetector = letterDetector
725
+ index_list = letter_list
726
+ }
727
+ })
728
+ var global_res = 0;
729
+ var global_cres = ''
730
+ const video = document.getElementById("webcam");
731
+ const canvasElement = document.getElementById("output_canvas");
732
+ const canvasCtx = canvasElement.getContext("2d", { willReadFrequently: true });
733
+ var x_array = []
734
+ var y_array = []
735
+ var video_facing_mode = "user"
736
+ // Check if webcam access is supported.
737
+ const hasGetUserMedia = () => { var _a; return !!((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia); };
738
+ // If webcam supported, add event listener to button for when user
739
+ // wants to activate it.
740
+ if (hasGetUserMedia()) {
741
+ enableWebcamButton = document.getElementById("webcamButton");
742
+ enableWebcamButton.addEventListener("click", enableCam);
743
+ document.getElementById("switch-camera").addEventListener("click", switch_camera);
744
+ }
745
+ else {
746
+ console.warn("getUserMedia() is not supported by your browser");
747
+ }
748
+ async function switch_camera() {
749
+ if (video_facing_mode == 'user') {
750
+ webcamRunning = false
751
+ video_facing_mode = 'environment'
752
+ await load_camera()
753
+ webcamRunning = true
754
+ }
755
+ else {
756
+ webcamRunning = false
757
+ video_facing_mode = 'user'
758
+ await load_camera()
759
+ webcamRunning = true
760
+ }
761
+ }
762
+ // Enable the live webcam view and start detection.
763
+ function enableCam(event) {
764
+ if (!handLandmarker) {
765
+ console.log("Wait! objectDetector not loaded yet.");
766
+ return;
767
+ }
768
+ if (webcamRunning === true) {
769
+ webcamRunning = false;
770
+ enableWebcamButton.innerText = "ENABLE PREDICTIONS";
771
+ }
772
+ else {
773
+ webcamRunning = true;
774
+ enableWebcamButton.style = "display:none"
775
+ document.getElementById("switch-camera").style.display = "block"
776
+ document.getElementById("mode-switch").style.display = "flex"
777
+
778
+ }
779
+ // getUsermedia parameters.
780
+ load_camera()
781
+ }
782
+
783
+ function switchPage(elem) {
784
+ prevSpeech = ""
785
+
786
+ var pH = document.getElementById("home-page")
787
+ var pI = document.getElementById("info-page")
788
+ var pS = document.getElementById("settings-page")
789
+
790
+ pH.style.display = "none"
791
+ pI.style.display = "none"
792
+ pS.style.display = "none"
793
+
794
+ document.getElementById(elem + "page").style.display = "block"
795
+ if (elem != "home-") {
796
+ webcamRunning = false
797
+ document.getElementById("webcamButton").style = "display:block"
798
+ document.getElementById("switch-camera").style.display = "none"
799
+ document.getElementById("mode-switch").style.display = "none"
800
+ var canvas = document.getElementById("output_canvas")
801
+ const context = canvas.getContext('2d');
802
+ context.clearRect(0, 0, canvas.width, canvas.height);
803
+ } else {
804
+ textInput.value = ""
805
+ clear_output_array()
806
+ var canvas = document.getElementById("output_canvas")
807
+ const context = canvas.getContext('2d');
808
+ context.clearRect(0, 0, canvas.width, canvas.height);
809
+ }
810
+ }
811
+
812
+ function load_camera() {
813
+ try {
814
+ var stream = video.srcObject;
815
+ // now get all tracks
816
+ var tracks = stream.getTracks();
817
+ // now close each track by having forEach loop
818
+ tracks.forEach(function (track) {
819
+ // stopping every track
820
+ track.stop();
821
+ });
822
+ // assign null to srcObject of video
823
+ video.srcObject = null;
824
+ } catch (error) {
825
+ console.error(error.message);
826
+ }
827
+
828
+ const constraints = {
829
+ video: {
830
+ facingMode: video_facing_mode
831
+ }
832
+ };
833
+ // Activate the webcam stream.
834
+ navigator.mediaDevices.getUserMedia(constraints)
835
+ .then((stream) => {
836
+ video.srcObject = stream;
837
+ video.play();
838
+ video.addEventListener("loadeddata", predictWebcam);
839
+ })
840
+ .catch((error) => {
841
+ console.error("Error accessing the camera: ", error.name, error.message, error.code);
842
+ });
843
+ }
844
+ let lastVideoTime = -1;
845
+ let results = undefined;
846
+ console.log(video);
847
+ async function predictWebcam() {
848
+ if (video.videoHeight == 0) {
849
+ return
850
+ }
851
+ canvasElement.width = window.innerWidth;
852
+ // Start detecting the stream.
853
+ if (runningMode === "IMAGE") {
854
+ runningMode = "VIDEO";
855
+ await handLandmarker.setOptions({ runningMode: "VIDEO" });
856
+ }
857
+ let startTimeMs = performance.now();
858
+ if (lastVideoTime !== video.currentTime) {
859
+ lastVideoTime = video.currentTime;
860
+ results = handLandmarker.detectForVideo(video, startTimeMs);
861
+ }
862
+ canvasCtx.save();
863
+ canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
864
+
865
+ if (is_first_run == 1) {
866
+ var elem_rect = document.getElementById("output_canvas").getBoundingClientRect()
867
+ console.log(elem_rect.height | 0);
868
+ document.getElementById("canvas_wrapper").style.height = (elem_rect.height | 0).toString() + "px"
869
+
870
+ is_first_run = 0
871
+ }
872
+
873
+ if (results.landmarks && results.handednesses[0]) {
874
+ var current_time = Math.round(Date.now())
875
+
876
+ if (results.handednesses[0][0].categoryName == "Left") {
877
+ if (!Array.from(document.getElementById('modeSelector').classList).includes('right')) {
878
+ annotateImage()
879
+ }
880
+ canvasCtx.drawImage(video, 0, 0, canvasElement.width, (video.videoHeight / video.videoWidth) * canvasElement.width)
881
+ annotateImage(Array.from(document.getElementById('modeSelector').classList).includes('right'))
882
+ console.log("LEFT")
883
+ //detectSign()
884
+ } else {
885
+ canvasCtx.drawImage(video, 0, 0, canvasElement.width, (video.videoHeight / video.videoWidth) * canvasElement.width)
886
+ console.log("RIGHT")
887
+ var current_result = "_"
888
+ var previous_result = document.getElementById("predicted_result").innerText
889
+ document.getElementById("predicted_result").innerText = current_result
890
+
891
+
892
+ if (previous_result == current_result) {
893
+ if (current_time - last_letter_time > getNaturalLength(" ")) {
894
+ last_letter_time = current_time
895
+ word_list.push(" ")
896
+ triggerPulse()
897
+ console.log(word_list)
898
+ document.getElementById("text").value = word_list.join('')
899
+ }
900
+ }
901
+ else {
902
+ last_letter_time = current_time
903
+ }
904
+ }
905
+ document.getElementById("predicted_result").style.width = String((current_time - last_letter_time) / getNaturalLength(global_cres) * 100) + "%"
906
+ }
907
+ else {
908
+ canvasCtx.drawImage(video, 0, 0, canvasElement.width, (video.videoHeight / video.videoWidth) * canvasElement.width)
909
+ if (30 > calculateCanvasBrightness(canvasElement)) {
910
+
911
+ var current_result = "<"
912
+ var previous_result = document.getElementById("predicted_result").innerText
913
+ document.getElementById("predicted_result").innerText = current_result
914
+ var current_time = Math.round(Date.now())
915
+ console.log(current_time - last_letter_time)
916
+ if (previous_result == current_result) {
917
+ if (current_time - last_letter_time > 400) {
918
+ last_letter_time = current_time
919
+ word_list.pop()
920
+ console.log(word_list)
921
+ document.getElementById("text").value = word_list.join('')
922
+ }
923
+ }
924
+ else {
925
+ last_letter_time = current_time
926
+ }
927
+ } else {
928
+ last_letter_time = Math.round(Date.now())
929
+
930
+ document.getElementById("predicted_result").style.width = String(0) + "%"
931
+ }
932
+ }
933
+
934
+ canvasCtx.restore();
935
+ // Keep predicting
936
+ if (webcamRunning === true) {
937
+ window.requestAnimationFrame(predictWebcam);
938
+ }
939
+ }
940
+ function annotateImage(firstA = true) {
941
+
942
+ //console.log(results.landmarks)
943
+ if (results.landmarks[0]) {
944
+ x_array = []
945
+ y_array = []
946
+ results.landmarks[0].forEach(iterate)
947
+ //console.log(x_array)
948
+ var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
949
+ var image_width = canvasElement.width
950
+ var min_x = Math.min(...x_array) * image_width
951
+ var min_y = Math.min(...y_array) * image_height
952
+ var max_x = Math.max(...x_array) * image_width
953
+ var max_y = Math.max(...y_array) * image_height
954
+
955
+ var sect_height = max_y - (min_y)
956
+ var sect_width = max_x - (min_x)
957
+ var center_x = (min_x + max_x) / 2
958
+ var center_y = (min_y + max_y) / 2
959
+
960
+ var sect_diameter = 50
961
+ if (sect_height > sect_width) {
962
+ sect_diameter = sect_height
963
+ //console.log("sect_height", sect_diameter)
964
+ }
965
+ if (sect_height < sect_width) {
966
+ sect_diameter = sect_width
967
+ // console.log("sect_width", sect_diameter)
968
+ }
969
+
970
+ sect_diameter = sect_diameter + 50
971
+ var sect_radius = sect_diameter / 2
972
+ var crop_top = center_y - sect_radius
973
+ var crop_bottom = center_y + sect_radius
974
+ var crop_left = center_x - sect_radius
975
+ var crop_right = center_x + sect_radius
976
+ if (crop_top < 0) {
977
+ crop_top = 0
978
+ }
979
+ if (crop_left < 0) {
980
+ crop_left = 0
981
+ }
982
+ if (crop_right > image_width) {
983
+ crop_right = image_width
984
+ }
985
+ if (crop_bottom > image_height) {
986
+ crop_bottom = image_height
987
+ }
988
+ if (firstA == true) {
989
+ canvasCtx.beginPath();
990
+ canvasCtx.rect(crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top);
991
+ canvasCtx.stroke();
992
+ if (!Array.from(document.getElementById("modeSelector").classList).includes('right')) {
993
+ canvasCtx.fillStyle = "#ffffff"
994
+ canvasCtx.fill()
995
+ }
996
+
997
+ } else {
998
+ canvasCtx.beginPath();
999
+ canvasCtx.rect(crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top);
1000
+ canvasCtx.stroke();
1001
+ }
1002
+
1003
+
1004
+
1005
+ }
1006
+ /* for (const landmarks of results.multiHandLandmarks) {
1007
+ drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {
1008
+ color: "#00FF00",
1009
+ lineWidth: 5
1010
+ });
1011
+ drawLandmarks(canvasCtx, landmarks, { color: "#FF0000", lineWidth: 2 });
1012
+ }*/
1013
+ // console.log(results)
1014
+ const landmarks = results.landmarks;
1015
+ if (landmarks[0]) {
1016
+ var hand = landmarks[0]
1017
+ const zValues = hand.map(node => node.z);
1018
+ const maxZ = Math.max.apply(null, zValues)
1019
+ const minZ = Math.min.apply(null, zValues)
1020
+ const zRange = maxZ - minZ
1021
+ hand.forEach(node => {
1022
+ node.z = ((node.z - minZ) / zRange) * 255
1023
+ });
1024
+ // Thumb connections
1025
+ drawConnection(hand[4], hand[3], 'rgb(0, 0, 255)', 5); // 4-3 (was red)
1026
+ drawConnection(hand[3], hand[2], 'rgb(0, 0, 255)', 5); // 3-2 (was red)
1027
+ drawConnection(hand[2], hand[1], 'rgb(0, 0, 255)', 5); // 2-1 (was red)
1028
+
1029
+ // Index connections
1030
+ drawConnection(hand[8], hand[7], 'rgb(0, 255, 0)', 5); // 8-7
1031
+ drawConnection(hand[7], hand[6], 'rgb(0, 255, 0)', 5); // 7-6
1032
+ drawConnection(hand[6], hand[5], 'rgb(0, 255, 0)', 5); // 6-5
1033
+
1034
+ // Middle connections
1035
+ drawConnection(hand[12], hand[11], 'rgb(255, 0, 0)', 5); // 12-11 (was blue)
1036
+ drawConnection(hand[11], hand[10], 'rgb(255, 0, 0)', 5); // 11-10 (was blue)
1037
+ drawConnection(hand[10], hand[9], 'rgb(255, 0, 0)', 5); // 10-9 (was blue)
1038
+
1039
+ // Ring connections
1040
+ drawConnection(hand[16], hand[15], 'rgb(0, 255, 255)', 5); // 16-15 (was yellow)
1041
+ drawConnection(hand[15], hand[14], 'rgb(0, 255, 255)', 5); // 15-14 (was yellow)
1042
+ drawConnection(hand[14], hand[13], 'rgb(0, 255, 255)', 5); // 14-13 (was yellow)
1043
+
1044
+ // Pinky connections
1045
+ drawConnection(hand[20], hand[19], 'rgb(255, 0, 255)', 5); // 20-19
1046
+ drawConnection(hand[19], hand[18], 'rgb(255, 0, 255)', 5); // 19-18
1047
+ drawConnection(hand[18], hand[17], 'rgb(255, 0, 255)', 5); // 18-17
1048
+
1049
+ drawConnection(hand[0], hand[1], 'rgb(200, 200, 200)', 5); // 0-1
1050
+ drawConnection(hand[0], hand[5], 'rgb(200, 200, 200)', 5); // 0-5
1051
+ drawConnection(hand[0], hand[17], 'rgb(200, 200, 200)', 5); // 0-17
1052
+ drawConnection(hand[5], hand[9], 'rgb(200, 200, 200)', 5); // 5-9
1053
+ drawConnection(hand[9], hand[13], 'rgb(200, 200, 200)', 5); // 9-13
1054
+ drawConnection(hand[13], hand[17], 'rgb(200, 200, 200)', 5); // 13-17
1055
+
1056
+ // Thumb
1057
+ drawLandmarks(canvasCtx, hand[2], '#ffe5b4'); // Thumb tip (2)
1058
+ drawLandmarks(canvasCtx, hand[3], '#ffe5b4'); // Thumb base (3)
1059
+ drawLandmarks(canvasCtx, hand[4], '#ffe5b4'); // Thumb base (4)
1060
+
1061
+ // Index
1062
+ drawLandmarks(canvasCtx, hand[6], '#804080'); // Index tip (6)
1063
+ drawLandmarks(canvasCtx, hand[7], '#804080'); // Index base (7)
1064
+ drawLandmarks(canvasCtx, hand[8], '#804080'); // Index base (8)
1065
+
1066
+ // Middle
1067
+ drawLandmarks(canvasCtx, hand[10], '#ffcc00'); // Middle tip (10)
1068
+ drawLandmarks(canvasCtx, hand[11], '#ffcc00'); // Middle base (11)
1069
+ drawLandmarks(canvasCtx, hand[12], '#ffcc00'); // Middle base (12)
1070
+
1071
+ // Ring
1072
+ drawLandmarks(canvasCtx, hand[14], '#30ff30'); // Ring tip (14)
1073
+ drawLandmarks(canvasCtx, hand[15], '#30ff30'); // Ring base (15)
1074
+ drawLandmarks(canvasCtx, hand[16], '#30ff30'); // Ring base (16)
1075
+
1076
+ // Pinky
1077
+ drawLandmarks(canvasCtx, hand[18], '#1565c0'); // Pinky tip (18)
1078
+ drawLandmarks(canvasCtx, hand[19], '#1565c0'); // Pinky base (19)
1079
+ drawLandmarks(canvasCtx, hand[20], '#1565c0'); // Pinky base (20)
1080
+
1081
+ drawLandmarks(canvasCtx, hand[0], '#ff3030'); // Wrist (0)
1082
+
1083
+ drawLandmarks(canvasCtx, hand[1], '#ff3030'); // Palm base (1)
1084
+
1085
+ drawLandmarks(canvasCtx, hand[5], '#ff3030'); // Index palm (5)
1086
+
1087
+ drawLandmarks(canvasCtx, hand[9], '#ff3030'); // Middle palm (9)
1088
+
1089
+ drawLandmarks(canvasCtx, hand[13], '#ff3030'); // Ring palm (13)
1090
+
1091
+ drawLandmarks(canvasCtx, hand[17], '#ff3030'); // Pinky palm (17)
1092
+
1093
+ // Crop Canvas
1094
+ if (firstA == true) {
1095
+ cropCanvas(canvasElement, crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top)
1096
+ }
1097
+ }
1098
+ // Add more drawing calls for each landmark collection as needed
1099
+
1100
+
1101
+
1102
+
1103
+ //# sourceURL=pen.js
1104
+ }
1105
+
1106
+
1107
+ function iterate(x, y) {
1108
+ x_array.push(x.x)
1109
+ y_array.push(x.y)
1110
+ }
1111
+
1112
+ const cropCanvas = (sourceCanvas, left, top, width, height) => {
1113
+ let destCanvas = document.createElement('canvas');
1114
+ destCanvas.width = 224;
1115
+ var cropAspectRatio = width / height;
1116
+
1117
+ destCanvas.height = 224 / cropAspectRatio
1118
+ destCanvas.getContext("2d").drawImage(
1119
+ sourceCanvas,
1120
+ left, top, width, height, // source rect with content to crop
1121
+ 0, 0, 224, destCanvas.height); // newCanvas, same size as source
1122
+ var predictionInput = tf.browser.fromPixels(destCanvas.getContext("2d").getImageData(0, 0, 224, 224))
1123
+
1124
+ predict(tf.expandDims(predictionInput, 0));
1125
+ }
1126
+ function getNaturalLength(letter) {
1127
+ console.log('i got', letter, word_list[word_list.length - 1])
1128
+ if (document.getElementById("natInp").checked) {
1129
+ console.log('naturally:')
1130
+ if (letter != word_list[word_list.length - 1]) {
1131
+ return ((inputFrequency()) * (1 / (maxPercentage / 100)))
1132
+ } else {
1133
+ console.log("penalty!")
1134
+ var rb = Number(repeatBuffer())
1135
+ return ((inputFrequency()*(80/100))+400)
1136
+ }
1137
+ } else {
1138
+ console.log('stably:')
1139
+ if (letter != word_list[word_list.length - 1]) {
1140
+ return ((inputFrequency()))
1141
+ } else {
1142
+ console.log("penalty!")
1143
+ var rb = Number(repeatBuffer())
1144
+ return ((inputFrequency()*(80/100))+rb)
1145
+ }
1146
+ }
1147
+ }
1148
+ function mapLogitsToPercentage(logits) {
1149
+ // Apply ceiling of 260
1150
+ const cappedLogits = logits.map(value => Math.min(value, 260));
1151
+
1152
+ // Map the values from 0-260 to 0-100
1153
+ const mappedPercentages = cappedLogits.map(value => (value / 260) * 100);
1154
+
1155
+ return mappedPercentages;
1156
+ }
1157
+
1158
+
1159
+ async function predict(inputTensor) {
1160
+
1161
+ console.log(index_list[0])
1162
+ objectDetector.then(function (res) {
1163
+
1164
+ var prediction = res.predict(inputTensor);
1165
+
1166
+ var outputArray = prediction.dataSync(); // Get the output as an array
1167
+ var percentages = mapLogitsToPercentage(outputArray)
1168
+ maxPercentage = Math.max(...percentages)
1169
+ document.getElementById('predicted_result').style.opacity = (maxPercentage+30)+'%'
1170
+
1171
+ var predictedClass = percentages.indexOf(Math.max(...percentages)); // Get the index
1172
+
1173
+ var current_result = index_list[predictedClass]
1174
+ global_cres = current_result
1175
+ var previous_result = document.getElementById("predicted_result").innerText
1176
+ document.getElementById("predicted_result").innerText = current_result
1177
+ var current_time = Math.round(Date.now())
1178
+ if (index_list[0] == " Good") {
1179
+ previous_result = " " + previous_result
1180
+ }
1181
+ console.log("p:", previous_result, "c:", current_result)
1182
+ if (previous_result == current_result) {
1183
+
1184
+ if (current_time - last_letter_time > getNaturalLength(current_result)) {
1185
+ last_letter_time = current_time
1186
+ word_list.push(current_result)
1187
+ triggerPulse()
1188
+ console.log(word_list)
1189
+ document.getElementById("text").value = word_list.join('')
1190
+ }
1191
+ }
1192
+ else {
1193
+ last_letter_time = current_time
1194
+ }
1195
+ console.log(index_list[predictedClass]);
1196
+ }, function (err) {
1197
+ console.log(err);
1198
+ });
1199
+
1200
+ }
1201
+
1202
+ function drawLandmarks(canvasCtx, landmarks, color) {
1203
+ var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
1204
+ var image_width = canvasElement.width
1205
+
1206
+ canvasCtx.fillStyle = `rgb(${landmarks.z},${landmarks.z},${landmarks.z})`;
1207
+ canvasCtx.beginPath();
1208
+ canvasCtx.arc(landmarks.x * image_width, landmarks.y * image_height, 8, 0, 2 * Math.PI);
1209
+ canvasCtx.fill();
1210
+
1211
+ }
1212
+
1213
+ function drawConnection(startNode, endNode, strokeColor, strokeWidth) {
1214
+
1215
+ var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
1216
+ var image_width = canvasElement.width
1217
+
1218
+ canvasCtx.strokeStyle = strokeColor;
1219
+ canvasCtx.lineWidth = strokeWidth - 1;
1220
+ canvasCtx.beginPath();
1221
+ canvasCtx.moveTo(startNode.x * image_width, startNode.y * image_height);
1222
+ canvasCtx.lineTo(endNode.x * image_width, endNode.y * image_height);
1223
+ canvasCtx.stroke();
1224
+ }
1225
+ function calculateCanvasBrightness(canvas) {
1226
+ const context = canvas.getContext('2d', { willReadFrequently: true });
1227
+
1228
+ // Get the image data from the canvas
1229
+ const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
1230
+ const data = imageData.data;
1231
+
1232
+ let totalBrightness = 0;
1233
+ let pixelCount = 0;
1234
+
1235
+ // Loop through each pixel
1236
+ for (let i = 0; i < data.length; i += 4) {
1237
+ const r = data[i]; // Red
1238
+ const g = data[i + 1]; // Green
1239
+ const b = data[i + 2]; // Blue
1240
+
1241
+ // Calculate brightness for this pixel
1242
+ const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
1243
+ totalBrightness += brightness;
1244
+ pixelCount++;
1245
+ }
1246
+
1247
+ // Calculate average brightness
1248
+ const averageBrightness = totalBrightness / pixelCount;
1249
+
1250
+ return averageBrightness;
1251
+ }
1252
+ function triggerPulse() {
1253
+ console.log('did nothing')
1254
+ /* Apply after working on pulse css more
1255
+ const resultWrapper = document.querySelector('.wrapper_result');
1256
+ resultWrapper.classList.add('pulse');
1257
+
1258
+ // Remove the class after the animation ends to allow it to be triggered again
1259
+ resultWrapper.addEventListener('animationend', () => {
1260
+ resultWrapper.classList.remove('pulse');
1261
+ }, { once: true });*/
1262
+ }
1263
+ </script>
1264
+
1265
+
1266
+
1267
+
1268
+
1269
+
1270
+ </body>
1271
+
1272
+ </html>
templates/temp.html ADDED
@@ -0,0 +1,593 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head></head>
5
+ <meta charset="UTF-8">
6
+
7
+
8
+ <title>Sign Language Interpreter</title>
9
+
10
+
11
+ <script>
12
+ window.console = window.console || function (t) { };
13
+ </script>
14
+ <!-- For Android
15
+ <link rel="stylesheet" type="text/css" href="http://127.0.0.1:8125/assets/static/browser_detect.css" />
16
+ -->
17
+ <!-- For Web -->
18
+ <link rel="stylesheet" type="text/css" href="static/browser_detect.css" />
19
+
20
+
21
+ </head>
22
+
23
+ <body translate="no">
24
+ <!-- For Android
25
+ <script src="../assets/ipc/androidjs.js"></script>
26
+ <script src="http://127.0.0.1:8125/assets/static/drawing_utils.js" crossorigin="anonymous"></script>
27
+ <script src="http://127.0.0.1:8125/assets/static/hands.js" crossorigin="anonymous"></script>
28
+ <script src="http://127.0.0.1:8125/assets/static/tfjs-core"></script>
29
+ <script src="http://127.0.0.1:8125/assets/static/tfjs-backend-cpu"></script>
30
+ <script src="http://127.0.0.1:8125/assets/static/tf-tflite.min.js"></script>
31
+ <script src="http://127.0.0.1:8125/assets/static/vision_wasm_internal.js" crossorigin="anonymous"></script>
32
+ -->
33
+
34
+ <!-- For Web -->
35
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"
36
+ crossorigin="anonymous"></script>
37
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
38
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core"></script>
39
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-cpu"></script>
40
+ <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-tflite/dist/tf-tflite.min.js"></script>
41
+
42
+
43
+ <div class="container">
44
+
45
+ <video id="webcam" style="display:none" autoplay="" playsinline=""></video>
46
+ <div class="canvas_wrapper" id="canvas_wrapper">
47
+ <button id="switch-camera" style="display:none; position: absolute; top:10px; left:10px; padding:5px; height:40px; width:40px; text-align: center; border-radius: 12.25px; font-size: 20px; font-weight: 900; border:none; background-color: #f2f2f2; color:black;
48
+ box-shadow: 0px 4px 20px 4px rgba(0, 0, 0, 0.38); z-index:100">
49
+ <span>⟳</span>
50
+ </button>
51
+ <canvas class="output_canvas" id="output_canvas" width="100%" height="300%"></canvas>
52
+ <center>
53
+ <button id="webcamButton" style="font-weight: 600; color:black;">
54
+ <span>Enable Webcam</span>
55
+ </button>
56
+ </center>
57
+ </div>
58
+ </div>
59
+ <center>
60
+ <img id="output_image" style="display:none"></img>
61
+ <div class="wrapper_result">
62
+ <div id="predicted_result">></div>
63
+ </div>
64
+ <div class="wrapper_text">
65
+ <textarea id="text" onkeyup="set_output_array(this.value)"></textarea>
66
+ <button id="text-to-speech" onclick="speak(document.getElementById('text').value)">
67
+ <span>Listen 🔊</span>
68
+ </button>
69
+
70
+ </div>
71
+ <center>
72
+ <script>
73
+
74
+ const originalFetch = window.fetch;
75
+
76
+ // Override the fetch function
77
+ window.fetch = async function (input, init) {
78
+ // Convert input to URL if it's a Request object
79
+ const url = typeof input === 'string' ? input : input.url;
80
+ var newUrl = url
81
+ if (url == 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.wasm') {
82
+ // newUrl = 'http://127.0.0.1:8125/assets/static/vision_wasm_internal.wasm' //For Android
83
+ newUrl = 'https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.wasm' // For Web
84
+
85
+ }
86
+ console.log("This was FETCHED: ", newUrl)
87
+ // Call the original fetch function with the new URL
88
+ return originalFetch(newUrl, init);
89
+ };
90
+
91
+
92
+ var synthesis = window.speechSynthesis;
93
+
94
+ if ('speechSynthesis' in window) {
95
+
96
+ var synthesis = window.speechSynthesis;
97
+
98
+ // Get the first `en` language voice in the list
99
+ var voice = synthesis.getVoices().filter(function (voice) {
100
+ return voice.lang === 'en';
101
+ })[0];
102
+
103
+ // Create an utterance object
104
+
105
+ } else {
106
+ speechSupported = false;
107
+ console.log('Text-to-speech not supported.');
108
+ }
109
+
110
+ function speak(text) {
111
+
112
+ if (!speechSupported) {
113
+ const audioPlayer = document.getElementById('audioPlayer');
114
+ if (prevSpeech != text) {
115
+ prevSpeech = text
116
+ audioPlayer.src = 'http://127.0.0.1:8125/speech?t=' + text; // Set the audio source
117
+ console.log("Set src: ", audioPlayer.src)
118
+ }
119
+
120
+ audioPlayer.play() // Play the audio
121
+ .then(() => {
122
+
123
+ console.log('Audio is playing');
124
+ })
125
+ .catch(error => {
126
+ console.error('Error playing audio:', error);
127
+ prevSpeech = ''
128
+ });
129
+ } else if ('speechSynthesis' in window) {
130
+ var utterance = new SpeechSynthesisUtterance(text);
131
+ utterance.voice = voice;
132
+ utterance.pitch = 0.6;
133
+ utterance.rate = 0.8;
134
+ utterance.volume = 0.8;
135
+ synthesis.speak(utterance);
136
+ } else {
137
+ console.log("Text to speech is now not supported")
138
+ }
139
+ }
140
+ var word_list = []
141
+
142
+
143
+ function set_output_array(text) {
144
+ console.log(text)
145
+ word_list = text.split("");
146
+ console.log(word_list)
147
+ }
148
+ </script>
149
+
150
+ <script type="module">
151
+ //import { HandLandmarker, FilesetResolver } from "http://127.0.0.1:8125/assets/static/tasks-vision@0.10.0" // For Android
152
+ import { HandLandmarker, FilesetResolver } from "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0"; // For Web
153
+ let handLandmarker = undefined;
154
+ let runningMode = "IMAGE";
155
+ let enableWebcamButton;
156
+ let webcamRunning = false;
157
+ var time_since_letter = 0
158
+ var last_letter_time = 0
159
+ var is_first_run = 1
160
+ // Before we can use HandLandmarker class we must wait for it to finish
161
+ // loading. Machine Learning models can be large and take a moment to
162
+ // get everything needed to run.
163
+ const createHandLandmarker = async () => {
164
+ const vision = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm");
165
+ handLandmarker = await HandLandmarker.createFromOptions(vision, {
166
+ baseOptions: {
167
+ modelAssetPath: `https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task`,
168
+ delegate: "GPU"
169
+ },
170
+ runningMode: runningMode,
171
+ numHands: 1
172
+ });
173
+ };
174
+ createHandLandmarker();
175
+
176
+ const MODEL_PATH = "/exported"
177
+ var objectDetector = tflite.loadTFLiteModel(MODEL_PATH);
178
+
179
+ /********************************************************************
180
+ // Continuously grab images
181
+ ********************************************************************/
182
+ var global_res = 0;
183
+ const video = document.getElementById("webcam");
184
+ const canvasElement = document.getElementById("output_canvas");
185
+ const canvasCtx = canvasElement.getContext("2d");
186
+ var x_array = []
187
+ var y_array = []
188
+ var video_facing_mode = "user"
189
+ // Check if webcam access is supported.
190
+ const hasGetUserMedia = () => { var _a; return !!((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia); };
191
+ // If webcam supported, add event listener to button for when user
192
+ // wants to activate it.
193
+ if (hasGetUserMedia()) {
194
+ enableWebcamButton = document.getElementById("webcamButton");
195
+ enableWebcamButton.addEventListener("click", enableCam);
196
+ document.getElementById("switch-camera").addEventListener("click", switch_camera);
197
+ }
198
+ else {
199
+ console.warn("getUserMedia() is not supported by your browser");
200
+ }
201
+ async function switch_camera() {
202
+ if (video_facing_mode == 'user') {
203
+ webcamRunning = false
204
+ video_facing_mode = 'environment'
205
+ await load_camera()
206
+ webcamRunning = true
207
+ }
208
+ else {
209
+ webcamRunning = false
210
+ video_facing_mode = 'user'
211
+ await load_camera()
212
+ webcamRunning = true
213
+ }
214
+ }
215
+ // Enable the live webcam view and start detection.
216
+ function enableCam(event) {
217
+ if (!handLandmarker) {
218
+ console.log("Wait! objectDetector not loaded yet.");
219
+ return;
220
+ }
221
+ if (webcamRunning === true) {
222
+ webcamRunning = false;
223
+ enableWebcamButton.innerText = "ENABLE PREDICTIONS";
224
+ }
225
+ else {
226
+ webcamRunning = true;
227
+ enableWebcamButton.style = "display:none"
228
+ document.getElementById("switch-camera").style.display = "block"
229
+
230
+ }
231
+ // getUsermedia parameters.
232
+ load_camera()
233
+ }
234
+ function load_camera() {
235
+ const constraints = {
236
+ video: {
237
+ facingMode: video_facing_mode
238
+ }
239
+ };
240
+ // Activate the webcam stream.
241
+ navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
242
+ video.srcObject = stream;
243
+ video.addEventListener("loadeddata", predictWebcam);
244
+ });
245
+ }
246
+ let lastVideoTime = -1;
247
+ let results = undefined;
248
+ console.log(video);
249
+ async function predictWebcam() {
250
+ if (video.videoHeight == 0) {
251
+ return
252
+ }
253
+ canvasElement.width = window.innerWidth;
254
+ // Start detecting the stream.
255
+ if (runningMode === "IMAGE") {
256
+ runningMode = "VIDEO";
257
+ await handLandmarker.setOptions({ runningMode: "VIDEO" });
258
+ }
259
+ let startTimeMs = performance.now();
260
+ if (lastVideoTime !== video.currentTime) {
261
+ lastVideoTime = video.currentTime;
262
+ results = handLandmarker.detectForVideo(video, startTimeMs);
263
+ }
264
+ canvasCtx.save();
265
+ canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
266
+ canvasCtx.drawImage(video, 0, 0, canvasElement.width, (video.videoHeight / video.videoWidth) * canvasElement.width)
267
+ if (is_first_run == 1) {
268
+ var elem_rect = document.getElementById("output_canvas").getBoundingClientRect()
269
+ console.log(elem_rect.height | 0);
270
+ document.getElementById("canvas_wrapper").style.height = (elem_rect.height | 0).toString() + "px"
271
+
272
+ is_first_run = 0
273
+ }
274
+
275
+ if (results.landmarks && results.handednesses[0]) {
276
+ var current_time = Math.round(Date.now())
277
+ document.getElementById("predicted_result").style.width = String((current_time - last_letter_time) / 10) + "%"
278
+ if (results.handednesses[0][0].categoryName == "Left") {
279
+ annotateImage()
280
+ console.log("LEFT")
281
+ //detectSign()
282
+ } else {
283
+ console.log("RIGHT")
284
+ var current_result = "_"
285
+ var previous_result = document.getElementById("predicted_result").innerText
286
+ document.getElementById("predicted_result").innerText = current_result
287
+
288
+
289
+ if (previous_result == current_result) {
290
+ if (current_time - last_letter_time > 1000) {
291
+ last_letter_time = current_time
292
+ word_list.push(" ")
293
+ console.log(word_list)
294
+ document.getElementById("text").value = word_list.join('')
295
+ }
296
+ }
297
+ else {
298
+ last_letter_time = current_time
299
+ }
300
+ }
301
+ }
302
+ else {
303
+ if (30 > calculateCanvasBrightness(canvasElement)) {
304
+
305
+ var current_result = "<"
306
+ var previous_result = document.getElementById("predicted_result").innerText
307
+ document.getElementById("predicted_result").innerText = current_result
308
+ var current_time = Math.round(Date.now())
309
+ console.log(current_time - last_letter_time)
310
+ if (previous_result == current_result) {
311
+ if (current_time - last_letter_time > 400) {
312
+ last_letter_time = current_time
313
+ word_list.pop()
314
+ console.log(word_list)
315
+ document.getElementById("text").value = word_list.join('')
316
+ }
317
+ }
318
+ else {
319
+ last_letter_time = current_time
320
+ }
321
+ } else {
322
+ last_letter_time = Math.round(Date.now())
323
+
324
+ document.getElementById("predicted_result").style.width = String(0) + "%"
325
+ }
326
+ }
327
+
328
+ canvasCtx.restore();
329
+ // Kepp predicting
330
+ if (webcamRunning === true) {
331
+ window.requestAnimationFrame(predictWebcam);
332
+ }
333
+ }
334
+ function annotateImage() {
335
+
336
+ //console.log(results.landmarks)
337
+ if (results.landmarks[0]) {
338
+ x_array = []
339
+ y_array = []
340
+ results.landmarks[0].forEach(iterate)
341
+ //console.log(x_array)
342
+ var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
343
+ var image_width = canvasElement.width
344
+ var min_x = Math.min(...x_array) * image_width
345
+ var min_y = Math.min(...y_array) * image_height
346
+ var max_x = Math.max(...x_array) * image_width
347
+ var max_y = Math.max(...y_array) * image_height
348
+
349
+ var sect_height = max_y - (min_y)
350
+ var sect_width = max_x - (min_x)
351
+ var center_x = (min_x + max_x) / 2
352
+ var center_y = (min_y + max_y) / 2
353
+
354
+ var sect_diameter = 50
355
+ if (sect_height > sect_width) {
356
+ sect_diameter = sect_height
357
+ //console.log("sect_height", sect_diameter)
358
+ }
359
+ if (sect_height < sect_width) {
360
+ sect_diameter = sect_width
361
+ // console.log("sect_width", sect_diameter)
362
+ }
363
+
364
+ sect_diameter = sect_diameter + 50
365
+ var sect_radius = sect_diameter / 2
366
+ var crop_top = center_y - sect_radius
367
+ var crop_bottom = center_y + sect_radius
368
+ var crop_left = center_x - sect_radius
369
+ var crop_right = center_x + sect_radius
370
+ if (crop_top < 0) {
371
+ crop_top = 0
372
+ }
373
+ if (crop_left < 0) {
374
+ crop_left = 0
375
+ }
376
+ if (crop_right > image_width) {
377
+ crop_right = image_width
378
+ }
379
+ if (crop_bottom > image_height) {
380
+ crop_bottom = image_height
381
+ }
382
+
383
+ canvasCtx.beginPath();
384
+ canvasCtx.rect(crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top);
385
+ canvasCtx.stroke();
386
+
387
+
388
+ }
389
+ /* for (const landmarks of results.multiHandLandmarks) {
390
+ drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {
391
+ color: "#00FF00",
392
+ lineWidth: 5
393
+ });
394
+ drawLandmarks(canvasCtx, landmarks, { color: "#FF0000", lineWidth: 2 });
395
+ }*/
396
+ // console.log(results)
397
+ const landmarks = results.landmarks;
398
+ if (landmarks[0]) {
399
+ var hand = landmarks[0]
400
+
401
+ // Thumb connections
402
+ drawConnection(hand[4], hand[3], '#ffe5b4', 5); // 4-3
403
+ drawConnection(hand[3], hand[2], '#ffe5b4', 5); // 3-2
404
+ drawConnection(hand[2], hand[1], '#ffe5b4', 5); // 2-1
405
+
406
+ // Index connections
407
+ drawConnection(hand[8], hand[7], '#804080', 5); // 8-7
408
+ drawConnection(hand[7], hand[6], '#804080', 5); // 7-6
409
+ drawConnection(hand[6], hand[5], '#804080', 5); // 6-5
410
+
411
+ // Middle connections
412
+ drawConnection(hand[12], hand[11], '#ffcc00', 5); // 12-11
413
+ drawConnection(hand[11], hand[10], '#ffcc00', 5); // 11-10
414
+ drawConnection(hand[10], hand[9], '#ffcc00', 5); // 10-9
415
+
416
+ // Ring connections
417
+ drawConnection(hand[16], hand[15], '#30ff30', 5); // 16-15
418
+ drawConnection(hand[15], hand[14], '#30ff30', 5); // 15-14
419
+ drawConnection(hand[14], hand[13], '#30ff30', 5); // 14-13
420
+
421
+ // Pinky connections
422
+ drawConnection(hand[20], hand[19], '#1565c0', 5); // 20-19
423
+ drawConnection(hand[19], hand[18], '#1565c0', 5); // 19-18
424
+ drawConnection(hand[18], hand[17], '#1565c0', 5); // 18-17
425
+
426
+ drawConnection(hand[0], hand[1], '#808080', 5); // 0-1
427
+ drawConnection(hand[0], hand[5], '#808080', 5); // 0-5
428
+ drawConnection(hand[0], hand[17], '#808080', 5); // 0-17
429
+ drawConnection(hand[5], hand[9], '#808080', 5); // 5-9
430
+ drawConnection(hand[9], hand[13], '#808080', 5); // 9-13
431
+ drawConnection(hand[13], hand[17], '#808080', 5); // 13-17
432
+
433
+ // Thumb
434
+ drawLandmarks(canvasCtx, hand[2], '#ffe5b4'); // Thumb tip (2)
435
+ drawLandmarks(canvasCtx, hand[3], '#ffe5b4'); // Thumb base (3)
436
+ drawLandmarks(canvasCtx, hand[4], '#ffe5b4'); // Thumb base (4)
437
+
438
+ // Index
439
+ drawLandmarks(canvasCtx, hand[6], '#804080'); // Index tip (6)
440
+ drawLandmarks(canvasCtx, hand[7], '#804080'); // Index base (7)
441
+ drawLandmarks(canvasCtx, hand[8], '#804080'); // Index base (8)
442
+
443
+ // Middle
444
+ drawLandmarks(canvasCtx, hand[10], '#ffcc00'); // Middle tip (10)
445
+ drawLandmarks(canvasCtx, hand[11], '#ffcc00'); // Middle base (11)
446
+ drawLandmarks(canvasCtx, hand[12], '#ffcc00'); // Middle base (12)
447
+
448
+ // Ring
449
+ drawLandmarks(canvasCtx, hand[14], '#30ff30'); // Ring tip (14)
450
+ drawLandmarks(canvasCtx, hand[15], '#30ff30'); // Ring base (15)
451
+ drawLandmarks(canvasCtx, hand[16], '#30ff30'); // Ring base (16)
452
+
453
+ // Pinky
454
+ drawLandmarks(canvasCtx, hand[18], '#1565c0'); // Pinky tip (18)
455
+ drawLandmarks(canvasCtx, hand[19], '#1565c0'); // Pinky base (19)
456
+ drawLandmarks(canvasCtx, hand[20], '#1565c0'); // Pinky base (20)
457
+
458
+ drawLandmarks(canvasCtx, hand[0], '#ff3030'); // Wrist (0)
459
+
460
+ drawLandmarks(canvasCtx, hand[1], '#ff3030'); // Palm base (1)
461
+
462
+ drawLandmarks(canvasCtx, hand[5], '#ff3030'); // Index palm (5)
463
+
464
+ drawLandmarks(canvasCtx, hand[9], '#ff3030'); // Middle palm (9)
465
+
466
+ drawLandmarks(canvasCtx, hand[13], '#ff3030'); // Ring palm (13)
467
+
468
+ drawLandmarks(canvasCtx, hand[17], '#ff3030'); // Pinky palm (17)
469
+ cropCanvas(canvasElement, crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top)
470
+ }
471
+ // Add more drawing calls for each landmark collection as needed
472
+
473
+
474
+
475
+
476
+ //# sourceURL=pen.js
477
+ }
478
+
479
+
480
+ function iterate(x, y) {
481
+ x_array.push(x.x)
482
+ y_array.push(x.y)
483
+ }
484
+
485
+ const cropCanvas = (sourceCanvas, left, top, width, height) => {
486
+ let destCanvas = document.createElement('canvas');
487
+ destCanvas.width = 224;
488
+ var cropAspectRatio = width / height;
489
+
490
+ destCanvas.height = 224 / cropAspectRatio
491
+ destCanvas.getContext("2d").drawImage(
492
+ sourceCanvas,
493
+ left, top, width, height, // source rect with content to crop
494
+ 0, 0, 224, destCanvas.height); // newCanvas, same size as source
495
+ var predictionInput = tf.browser.fromPixels(destCanvas.getContext("2d").getImageData(0, 0, 224, 224))
496
+
497
+ predict(tf.expandDims(predictionInput, 0));
498
+ }
499
+ async function predict(inputTensor) {
500
+
501
+ //console.log("in predict")
502
+ var letter_list = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "#"]
503
+ objectDetector.then(function (res) {
504
+ var prediction = res.predict(inputTensor);
505
+ var outputArray = prediction.dataSync(); // Get the output as an array
506
+ var predictedClass = outputArray.indexOf(Math.max(...outputArray)); // Get the index
507
+ var current_result = letter_list[predictedClass]
508
+ var previous_result = document.getElementById("predicted_result").innerText
509
+ document.getElementById("predicted_result").innerText = current_result
510
+ var current_time = Math.round(Date.now())
511
+
512
+ if (previous_result == current_result) {
513
+ if (current_time - last_letter_time > 1000) {
514
+ last_letter_time = current_time
515
+ word_list.push(current_result)
516
+ console.log(word_list)
517
+ document.getElementById("text").value = word_list.join('')
518
+ }
519
+ }
520
+ else {
521
+ last_letter_time = current_time
522
+ }
523
+ console.log(letter_list[predictedClass]);
524
+ }, function (err) {
525
+ console.log(err);
526
+ });
527
+
528
+ }
529
+
530
+ function drawLandmarks(canvasCtx, landmarks, color) {
531
+ var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
532
+ var image_width = canvasElement.width
533
+
534
+ canvasCtx.fillStyle = color;
535
+ canvasCtx.strokeStyle = 'white';
536
+ canvasCtx.lineWidth = 1;
537
+ canvasCtx.beginPath();
538
+ canvasCtx.arc(landmarks.x * image_width, landmarks.y * image_height, 6, 0, 2 * Math.PI);
539
+ canvasCtx.fill();
540
+ canvasCtx.stroke();
541
+
542
+ }
543
+
544
+ function drawConnection(startNode, endNode, strokeColor, strokeWidth) {
545
+
546
+ var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
547
+ var image_width = canvasElement.width
548
+
549
+ canvasCtx.strokeStyle = strokeColor;
550
+ canvasCtx.lineWidth = strokeWidth;
551
+ canvasCtx.beginPath();
552
+ canvasCtx.moveTo(startNode.x * image_width, startNode.y * image_height);
553
+ canvasCtx.lineTo(endNode.x * image_width, endNode.y * image_height);
554
+ canvasCtx.stroke();
555
+ }
556
+ function calculateCanvasBrightness(canvas) {
557
+ const context = canvas.getContext('2d');
558
+
559
+ // Get the image data from the canvas
560
+ const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
561
+ const data = imageData.data;
562
+
563
+ let totalBrightness = 0;
564
+ let pixelCount = 0;
565
+
566
+ // Loop through each pixel
567
+ for (let i = 0; i < data.length; i += 4) {
568
+ const r = data[i]; // Red
569
+ const g = data[i + 1]; // Green
570
+ const b = data[i + 2]; // Blue
571
+
572
+ // Calculate brightness for this pixel
573
+ const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
574
+ totalBrightness += brightness;
575
+ pixelCount++;
576
+ }
577
+
578
+ // Calculate average brightness
579
+ const averageBrightness = totalBrightness / pixelCount;
580
+
581
+ return averageBrightness;
582
+ }
583
+ </script>
584
+
585
+
586
+
587
+
588
+
589
+ <script src="https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.0/wasm/vision_wasm_internal.js"
590
+ crossorigin="anonymous"></script>
591
+ </body>
592
+
593
+ </html>