Emeritus-21's picture
Upload browser-detect.html
cadc4d2 verified
<!DOCTYPE html>
<!--
SignSpeak - A Communicator For Signers
Copyright (C) 2025 Shantanu Khedkar
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<html lang="en">
<head></head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<title>Sign Language Interpreter</title>
<script>
window.console = window.console || function (t) { };
</script>
<!-- For Android
<link rel="stylesheet" type="text/css" href="http://127.0.0.1:8125/assets/static/browser_detect.css" />
-->
<!-- For Web-->
<link rel="stylesheet" type="text/css" href="static/browser_detect.css" />
</head>
<body translate="no" style="overflow: scroll;">
<!-- For Android
<script src="../assets/ipc/androidjs.js"></script>
<script src="http://127.0.0.1:8125/assets/static/drawing_utils.js" crossorigin="anonymous"></script>
<script src="http://127.0.0.1:8125/assets/static/hands.js" crossorigin="anonymous"></script>
<script src="http://127.0.0.1:8125/assets/static/tfjs-core"></script>
<script src="http://127.0.0.1:8125/assets/static/tfjs-backend-cpu"></script>
<script src="http://127.0.0.1:8125/assets/static/tf-tflite.min.js"></script>
<script src="http://127.0.0.1:8125/assets/static/vision_wasm_internal.js" crossorigin="anonymous"></script>
-->
<!-- For Web -->
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-cpu"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-tflite/dist/tf-tflite.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/wasm/vision_wasm_internal.js"
crossorigin="anonymous"></script>
<section id="home-page">
<div class="container">
<video id="webcam" style="display:none" autoplay playsinline muted></video>
<div class="canvas_wrapper" id="canvas_wrapper">
<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: #ffffff2e; backdrop-filter: blur(30px); color:black; box-shadow: 0px 4px 20px 4px rgba(0, 0, 0, 0.38); z-index:100">
<span></span>
</button>
<canvas class="output_canvas" id="output_canvas" width="100%" height="500%"></canvas>
<center>
<button id="webcamButton" style="font-weight: 600;">
<span>Enable Webcam</span>
</button>
<div class="mode_switch_wrapper">
<div class="mode_switch" id="mode-switch" style="display: none;">
<div class="mode_selector" id="modeSelector"></div>
<div class="mode_switch_modeBtn">Text</div>
<div class="mode_switch_modeBtn">Word</div>
</div>
</div>
</center>
</div>
</div>
<center>
<img id="output_image" style="display:none"></img>
<div class="wrapper_result">
<div id="predicted_result">></div>
</div>
<div class="wrapper_text">
<textarea id="text" onkeyup="set_output_array(this.value)"></textarea>
<div class="textControls">
<button id="undoButton" onclick="undo()">
<svg xmlns="http://www.w3.org/2000/svg" height="16px" width="16px"
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.-->
<path fill="#fcfcfc"
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" />
</svg>
</button>
<button id="speakButton" onclick="speak(document.getElementById('text').value)">
<span>Listen</span>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" width="16px"
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.-->
<path fill="#fcfcfc"
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" />
</svg>
</button>
<button id="clearButton" onclick="clear_output_array()">
<svg xmlns="http://www.w3.org/2000/svg" height="16px" width="16px"
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.-->
<path fill="#fcfcfc"
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" />
</svg>
</button>
</div>
<audio id="audioPlayer">-</audio>
</div>
<div id="logUI" style="display:none"></div>
</center>
</section>
<section id="info-page" style="display:none; padding: 10px;">
<center>
<svg width="77.5" height="103" viewBox="0 0 310 412" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_21435_307)">
<path
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"
fill="#FF6E7D" />
<path
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"
fill="#FF6E7D" />
<path
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"
fill="#FF6E7D" />
<path
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"
fill="#D6505E" />
<path
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"
fill="#D6505E" />
<path
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"
fill="#A8404B" />
</g>
<defs>
<filter id="filter0_d_21435_307" x="0.181641" y="0.757568" width="309.576" height="410.424"
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix" />
<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"
result="hardAlpha" />
<feOffset dy="15" />
<feGaussianBlur stdDeviation="10" />
<feComposite in2="hardAlpha" operator="out" />
<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" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_21435_307" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_21435_307" result="shape" />
</filter>
</defs>
</svg>
<h1>SpeakSign</h1>
</center>
<hr>
<div class="about-card">
<span>Version</span>
<span>1.2.5b</span>
</div>
<div class="about-card">
<span>Author</span>
<span>Shantanu Khedkar</span>
</div>
<div class="about-card">
<!--For Android
<img src="http://127.0.0.1:8125/assets/static/ASL.png" style="height:auto; width:auto; margin: -5px"></img>
-->
<!--For Web-->
<img src="static/ASL.png" style="height:auto; width:auto; margin: -5px"></img>
</div>
<div class="about-card">
<span>Source</span>
<span><a href="https://github.com/Shantanu-Khedkar/silangint"
target="_blank">https://github.com/Shantanu-Khedkar/silangint</a></span>
</div>
<div class="about-card">
<span>License</span>
<span>GPL-v3.0 - <a href="https://github.com/Shantanu-Khedkar/silangint/blob/master/LICENSE"
target="_blank">https://github.com/Shantanu-Khedkar/silangint/blob/master/LICENSE</a></span>
</div>
<div class="about-card">
<span>Copyright Notice</span>
<span>
SignSpeak - A Communicator For Signers
<br>
Copyright (C) 2025 Shantanu Khedkar
<br>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
<br>
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
<br>
You should have received a copy of the GNU General Public License
along with this program. If not, see https://www.gnu.org/licenses.
</span>
</div>
</section>
<section id="settings-page" style="display:none">
<div
style="display:inline-flex; align-items:center; gap:10px; padding:12px; margin-bottom: 24px; margin-top: 16px;">
<div style="padding:10px; display: inherit;" onclick="settingsBack()">
<svg xmlns="http://www.w3.org/2000/svg" width="30px" height="30px"
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.-->
<path
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" />
</svg>
</div>
<span class="settings-header">Settings</span>
</div>
<form id="speechForm" style="display:block;">
<div class="about-card">
<label for="language">Select Language:</label>
<select id="language"></select>
</div>
<div class="about-card">
<label for="voice">Select Voice:</label>
<select id="voice"></select>
</div>
<div class="about-card">
<label for="rate">Rate:</label>
<input type="range" id="rate" min="0.1" max="2" step="0.1" value="1">
<span style="display:none" id="rateValue">1</span>
</div>
<div class="about-card">
<label for="pitch">Pitch:</label>
<input type="range" id="pitch" min="0" max="2" step="0.1" value="1">
<span style="display:none" id="pitchValue">1</span>
</div>
<div class="about-card">
<label for="testText">Test Voice:</label>
<input id="testText" value="Hello, this is a test!" placeholder="Enter test sentence..."
onkeyup="set_output_array(this.value)"></input>
<button type="button" id="testSpeakButton"
onclick="document.getElementById('text').value = document.getElementById('testText').value; document.getElementById('speakButton').click()">
<a>Listen 🔊</a>
</button>
</div>
<div class="about-card">
<label for="duration">Input Duration <span id="durationValue"></span>(ms)</label>
<input type="range" id="duration" min="200" max="1500" step="1" value="300"
oninput="document.getElementById('durationValue').innerText=this.value">
</div>
<div class="about-card">
<label for="repeat">Repeat Buffer <span id="repeatValue"></span>(ms)</label>
<input type="range" id="repeat" min="200" max="700" step="1" value="500"
oninput="document.getElementById('repeatValue').innerText=this.value">
</div>
<div class="about-card" style="flex-direction:row; align-items: center;">
<label style="margin-right: 10px;" for="duration">Natural Input :</label>
<label class="switch">
<input type="checkbox" id="natInp" unchecked>
<span class="slider round"></span>
</label>
</div>
</form>
</section>
<div class="bottom-nav">
<div id="info-" class="bottom-nav-btn">
<svg xmlns="http://www.w3.org/2000/svg" height="28px" width="28px"
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.-->
<path fill="#5d5d5d"
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" />
</svg>
</div>
<div id="home-" class="bottom-nav-btn center-btn">
<!--For Android
<img src="http://127.0.0.1:8125/assets/static/logo_sil.svg" height="48" width="48px"></img>
-->
<img src="/static/logo_sil.svg" height="48" width="48px"></img>
</div>
<div id="settings-" class="bottom-nav-btn">
<svg xmlns="http://www.w3.org/2000/svg" height="28px" width="28px"
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.-->
<path fill="#5d5d5d"
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" />
</svg>
</div>
</div>
<script>
var word_list = []
var speechSupported = true
var prevSpeech = ""
var prevSettings = ""
logUI = document.getElementById("logUI")
const speechSynthesis = window.speechSynthesis;
const voiceSelect = document.getElementById('voice');
const textInput = document.getElementById('text');
const languageSelect = document.getElementById('language');
const speakButton = document.getElementById('speakButton');
const rateInput = document.getElementById('rate');
const pitchInput = document.getElementById('pitch');
const rateValue = document.getElementById('rateValue');
const pitchValue = document.getElementById('pitchValue');
let voices = [];
const languageNames = {
'en': '- English ',
'mra': '- Marathi',
'mr': '- Marathi',
'hi': '- Hindi',
'es': 'Spanish',
'fr': 'French',
'de': 'German',
'it': 'Italian',
'af': 'Afrikaans',
'am': 'Amharic',
'an': 'Aragonese',
'ar': 'Arabic',
'as': 'Assamese',
'az': 'Azerbaijani',
'bg': 'Bulgarian',
'bn': 'Bengali',
'bpy': 'Bishnupriya Manipuri',
'bs': 'Bosnian',
'ca': 'Catalan',
'cs': 'Czech',
'cy': 'Welsh',
'da': 'Danish',
'de': 'German',
'el': 'Greek',
'eo': 'Esperanto',
'es': 'Spanish',
'et': 'Estonian',
'eu': 'Basque',
'fa': 'Persian',
'fi': 'Finnish',
'fr': 'French',
'ga': 'Irish',
'gd': 'Scottish Gaelic',
'gn': 'Guarani',
'grc': 'Ancient Greek',
'gu': 'Gujarati',
'hak': 'Hakka',
'hr': 'Croatian',
'ht': 'Haitian Creole',
'hu': 'Hungarian',
'hy': 'Armenian',
'ia': 'Interlingua',
'id': 'Indonesian',
'is': 'Icelandic',
'it': 'Italian',
'ja': 'Japanese',
'jbo': 'Lojban',
'ka': 'Georgian',
'kk': 'Kazakh',
'kl': 'Kalaallisut',
'kn': 'Kannada',
'ko': 'Korean',
'kok': 'Konkani',
'ku': 'Kurdish',
'ky': 'Kyrgyz',
'la': 'Latin',
'lfn': 'Lingua Franca Nova',
'lt': 'Lithuanian',
'lv': 'Latvian',
'mi': 'Māori',
'mk': 'Macedonian',
'ml': 'Malayalam',
'ms': 'Malay',
'mt': 'Maltese',
'my': 'Burmese',
'nci': 'Navajo',
'ne': 'Nepali',
'nl': 'Dutch',
'no': 'Norwegian',
'om': 'Oromo',
'or': 'Odia',
'pa': 'Punjabi',
'pap': 'Papiamento',
'pl': 'Polish',
'pt': 'Portuguese',
'quc': 'K’iche’',
'ro': 'Romanian',
'ru': 'Russian',
'sd': 'Sindhi',
'shn': 'Shan',
'si': 'Sinhala',
'sk': 'Slovak',
'sl': 'Slovenian',
'sq': 'Albanian',
'sr': 'Serbian',
'sv': 'Swedish',
'sw': 'Swahili',
'ta': 'Tamil',
'te': 'Telugu',
'tn': 'Tswana',
'tr': 'Turkish',
'tt': 'Tatar',
'ur': 'Urdu',
'vi': 'Vietnamese'
};
function undo() {
word_list.pop()
textInput.value = word_list.join('')
}
function clear() {
word_list = []
textInput.value = ''
}
function populateVoiceList() {
voices = speechSynthesis.getVoices();
updateLanguageList();
updateVoiceList();
}
async function populateTTWVoices() {
const url = "http://127.0.0.1:8125/assets/static/voicesList.txt";
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
var voicesList = await response.text();
voicesList = voicesList.split('\n')
voicesList.pop()
voicesList.forEach(voice => {
const option = document.createElement('option');
option.value = voice;
option.textContent = voice;
voiceSelect.appendChild(option);
})
} catch (error) {
console.error(error.message);
}
}
async function populateTTWLangs() {
const url = "http://127.0.0.1:8125/assets/static/langList.txt";
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
var langsList = await response.text();
langsList = langsList.split('\n')
langsList.pop()
unknownLangs = []
langsList.forEach(lang => {
if (!languageNames[lang]) {
unknownLangs.push(lang)
} else {
const option = document.createElement('option');
option.value = lang;
option.textContent = languageNames[lang];
languageSelect.appendChild(option);
}
})
unknownLangs.forEach(lang => {
const option = document.createElement('option');
option.value = lang;
option.textContent = lang;
languageSelect.appendChild(option);
})
} catch (error) {
console.error(error.message);
}
const optionNodes = Array.from(languageSelect.children);
const comparator = new Intl.Collator('en'.slice(0, 2)).compare;
optionNodes.sort((a, b) => comparator(a.textContent, b.textContent));
optionNodes.forEach((option) => languageSelect.appendChild(option));
languageSelect.children[0].selected = "selected"
}
function getBaseLanguageCode(lang) {
return lang.split('-').slice(0, 2).join('-');
}
function getBaseBaseLanguageCode(lang) {
return lang.split('-').slice(0, 1).join('-');
}
function getModifierLanguageCode(lang) {
return lang.split('-').slice(1, 3).join('-');
}
function updateLanguageList() {
const uniqueLanguages = new Set(voices.map(voice => getBaseLanguageCode(voice.lang)));
languageSelect.innerHTML = '';
Object.keys(languageNames).forEach(lang => {
if (uniqueLanguages.has(lang)) {
const option = document.createElement('option');
option.value = lang;
option.textContent = languageNames[lang];
languageSelect.appendChild(option);
}
});
uniqueLanguages.forEach(lang => {
if (!languageNames[lang]) {
if (!languageNames[getBaseBaseLanguageCode(lang)]) {
const option = document.createElement('option');
option.value = lang;
option.textContent = lang;
languageSelect.appendChild(option);
}
else {
const option = document.createElement('option');
option.value = lang;
option.textContent = `${languageNames[getBaseBaseLanguageCode(lang)]} (${getModifierLanguageCode(lang)})`;
languageSelect.appendChild(option);
}
}
});
const optionNodes = Array.from(languageSelect.children);
const comparator = new Intl.Collator('en'.slice(0, 2)).compare;
optionNodes.sort((a, b) => comparator(a.textContent, b.textContent));
optionNodes.forEach((option) => languageSelect.appendChild(option));
languageSelect.children[0].selected = "selected"
}
function updateVoiceList() {
const selectedLanguage = languageSelect.value;
voiceSelect.innerHTML = '';
voices.forEach((voice) => {
if (getBaseLanguageCode(voice.lang) === selectedLanguage) {
const option = document.createElement('option');
option.value = voice.name;
option.textContent = `${voice.name} (${voice.lang})`;
voiceSelect.appendChild(option);
}
});
}
function settingsBack() {
document.getElementById("home-").click()
}
function logMessage(msg) {
const span = document.createElement('span');
span.textContent = msg;
logUI.appendChild(span);
logUI.appendChild(document.createElement('br')); // Add a line break
}
const originalFetch = window.fetch;
// Override the fetch function
window.fetch = async function (input, init) {
// Convert input to URL if it's a Request object
const url = typeof input === 'string' ? input : input.url;
var newUrl = url
if (url == 'https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/wasm/vision_wasm_internal.wasm') {
//newUrl = 'http://127.0.0.1:8125/assets/static/vision_wasm_internal.wasm' //For Android
newUrl = 'https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/wasm/vision_wasm_internal.wasm' // For Web
}
console.log("This was FETCHED: ", newUrl)
// Call the original fetch function with the new URL
return originalFetch(newUrl, init);
};
if ('speechSynthesis' in window) {
speechSynthesis.onvoiceschanged = () => {
populateVoiceList();
};
languageSelect.addEventListener('change', updateVoiceList);
rateInput.addEventListener('input', () => {
rateValue.textContent = rateInput.value;
});
pitchInput.addEventListener('input', () => {
pitchValue.textContent = pitchInput.value;
});
speakButton.addEventListener('click', () => {
const utterance = new SpeechSynthesisUtterance(textInput.value);
const selectedVoice = voices.find(voice => voice.name === voiceSelect.value);
utterance.voice = selectedVoice;
utterance.rate = rateInput.value;
utterance.pitch = pitchInput.value;
speechSynthesis.speak(utterance);
});
populateVoiceList()
// Create an utterance object ⣿
} else {
speechSupported = false;
console.log('Text-to-speech not supported.');
populateTTWLangs()
populateTTWVoices()
}
function speak(toSpeak) {
console.log("speech api support", speechSupported)
console.log("condition: ", !speechSupported)
console.log("condition2: ", speechSupported == false)
console.log("speech api support", speechSupported)
if (!speechSupported) {
const audioPlayer = document.getElementById('audioPlayer');
var currSettings = '&v=' + encodeURIComponent(languageSelect.value + "+" + voiceSelect.value) + '&r=' + Math.round(rateInput.value * 7.5).toString() + '&p=' + Math.round(pitchInput.value * 49.5).toString()
if ((prevSpeech != toSpeak) || (prevSettings != currSettings)) {
prevSpeech = toSpeak
prevSettings = currSettings
audioPlayer.src = 'http://127.0.0.1:8125/speech?t=' + encodeURIComponent(toSpeak) + currSettings
console.log("Set src: ", audioPlayer.src)
}
audioPlayer.play() // Play the audio
.then(() => {
console.log('Audio is playing');
})
.catch(error => {
console.error('Error playing audio:', error);
prevSpeech = ''
});
} else if ('speechSynthesis' in window) {
console.log("probably spoken")
} else {
console.log("Text to speech is now not supported")
}
}
function set_output_array(text) {
console.log(text)
word_list = text.split("");
console.log(word_list)
}
function clear_output_array() {
word_list = [];
textInput.value = ""
}
</script>
<script type="module">
document.getElementById("info-").addEventListener("click", switchPage.bind(null, "info-"));
document.getElementById("home-").addEventListener("click", switchPage.bind(null, "home-"));
document.getElementById("settings-").addEventListener("click", switchPage.bind(null, "settings-"));
//import { HandLandmarker, FilesetResolver } from "http://127.0.0.1:8125/assets/static/[email protected]" // For Android
import { HandLandmarker, FilesetResolver } from "https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]"; // For Web
let handLandmarker = undefined;
let runningMode = "IMAGE";
let enableWebcamButton;
let webcamRunning = false;
var time_since_letter = 0
var last_letter_time = 0
var maxPercentage = 1
function inputFrequency() {
return document.getElementById('duration').value
}
function repeatBuffer() {
return document.getElementById('repeat').value
}
document.getElementById('durationValue').innerText = inputFrequency()
document.getElementById('repeatValue').innerText = repeatBuffer()
var is_first_run = 1
const letter_list = ["A", "B", "", "D", "E", "F", "G", "", "I", "J", "K", "L", "M", "N", "O", "P", "", "R", "S", "T", "U", "V", "W", "", "Y", "Z", ""]
const phrase_list = ["Ere-ọwurọ", "Bọkọ", "Bokọ???", "Ere-Alẹ", "Mo dọkpẹ", "Ẹrẹ"]
// const phrase_list = [" Good-morning", " Hello", " How are you", "Good-evening", " Thanks", " You"]
var index_list = letter_list
// Before we can use HandLandmarker class we must wait for it to finish
// loading. Machine Learning models can be large and take a moment to
// get everything needed to run.
const createHandLandmarker = async () => {
const vision = await FilesetResolver.forVisionTasks("https://cdn.jsdelivr.net/npm/@mediapipe/[email protected]/wasm"); // This doesnt really matter as this is already imported somewhere else, and the code runs fine without the request
handLandmarker = await HandLandmarker.createFromOptions(vision, {
baseOptions: {
//modelAssetPath: `http://127.0.0.1:8125/assets/static/hand_landmarker.task`, // For Android
modelAssetPath: `https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task`, // For Web
delegate: "GPU"
},
runningMode: runningMode,
numHands: 1
});
};
createHandLandmarker();
//const MODEL_PATH = "http://127.0.0.1:8125/assets/static/model.tflite" // For Android
//const WORD_MODEL = "http://127.0.0.1:8125/assets/static/word.tflite" // For Android
const MODEL_PATH = "/exported" // For Web
const WORD_MODEL = "/word" // For Web
const letterDetector = tflite.loadTFLiteModel(MODEL_PATH);
const wordDetector = tflite.loadTFLiteModel(WORD_MODEL);
var objectDetector = letterDetector
/********************************************************************
// Continuously grab images
********************************************************************/
document.getElementById("mode-switch").addEventListener('click', function () {
document.getElementById('modeSelector').classList.toggle('right')
var modeState = Array.from(document.getElementById('modeSelector').classList).includes('right')
console.log("swicth", modeState)
if (modeState) {
console.log("word")
objectDetector = wordDetector
index_list = phrase_list
} else {
console.log("letter")
objectDetector = letterDetector
index_list = letter_list
}
})
var global_res = 0;
var global_cres = ''
const video = document.getElementById("webcam");
const canvasElement = document.getElementById("output_canvas");
const canvasCtx = canvasElement.getContext("2d", { willReadFrequently: true });
var x_array = []
var y_array = []
var video_facing_mode = "user"
// Check if webcam access is supported.
const hasGetUserMedia = () => { var _a; return !!((_a = navigator.mediaDevices) === null || _a === void 0 ? void 0 : _a.getUserMedia); };
// If webcam supported, add event listener to button for when user
// wants to activate it.
if (hasGetUserMedia()) {
enableWebcamButton = document.getElementById("webcamButton");
enableWebcamButton.addEventListener("click", enableCam);
document.getElementById("switch-camera").addEventListener("click", switch_camera);
}
else {
console.warn("getUserMedia() is not supported by your browser");
}
async function switch_camera() {
if (video_facing_mode == 'user') {
webcamRunning = false
video_facing_mode = 'environment'
await load_camera()
webcamRunning = true
}
else {
webcamRunning = false
video_facing_mode = 'user'
await load_camera()
webcamRunning = true
}
}
// Enable the live webcam view and start detection.
function enableCam(event) {
if (!handLandmarker) {
console.log("Wait! objectDetector not loaded yet.");
return;
}
if (webcamRunning === true) {
webcamRunning = false;
enableWebcamButton.innerText = "ENABLE PREDICTIONS";
}
else {
webcamRunning = true;
enableWebcamButton.style = "display:none"
document.getElementById("switch-camera").style.display = "block"
document.getElementById("mode-switch").style.display = "flex"
}
// getUsermedia parameters.
load_camera()
}
function switchPage(elem) {
prevSpeech = ""
var pH = document.getElementById("home-page")
var pI = document.getElementById("info-page")
var pS = document.getElementById("settings-page")
pH.style.display = "none"
pI.style.display = "none"
pS.style.display = "none"
document.getElementById(elem + "page").style.display = "block"
if (elem != "home-") {
webcamRunning = false
document.getElementById("webcamButton").style = "display:block"
document.getElementById("switch-camera").style.display = "none"
document.getElementById("mode-switch").style.display = "none"
var canvas = document.getElementById("output_canvas")
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
} else {
textInput.value = ""
clear_output_array()
var canvas = document.getElementById("output_canvas")
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
}
}
function load_camera() {
try {
var stream = video.srcObject;
// now get all tracks
var tracks = stream.getTracks();
// now close each track by having forEach loop
tracks.forEach(function (track) {
// stopping every track
track.stop();
});
// assign null to srcObject of video
video.srcObject = null;
} catch (error) {
console.error(error.message);
}
const constraints = {
video: {
facingMode: video_facing_mode
}
};
// Activate the webcam stream.
navigator.mediaDevices.getUserMedia(constraints)
.then((stream) => {
video.srcObject = stream;
video.play();
video.addEventListener("loadeddata", predictWebcam);
})
.catch((error) => {
console.error("Error accessing the camera: ", error.name, error.message, error.code);
});
}
let lastVideoTime = -1;
let results = undefined;
console.log(video);
async function predictWebcam() {
if (video.videoHeight == 0) {
return
}
canvasElement.width = window.innerWidth;
// Start detecting the stream.
if (runningMode === "IMAGE") {
runningMode = "VIDEO";
await handLandmarker.setOptions({ runningMode: "VIDEO" });
}
let startTimeMs = performance.now();
if (lastVideoTime !== video.currentTime) {
lastVideoTime = video.currentTime;
results = handLandmarker.detectForVideo(video, startTimeMs);
}
canvasCtx.save();
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
if (is_first_run == 1) {
var elem_rect = document.getElementById("output_canvas").getBoundingClientRect()
console.log(elem_rect.height | 0);
document.getElementById("canvas_wrapper").style.height = (elem_rect.height | 0).toString() + "px"
is_first_run = 0
}
if (results.landmarks && results.handednesses[0]) {
var current_time = Math.round(Date.now())
if (results.handednesses[0][0].categoryName == "Left") {
if (!Array.from(document.getElementById('modeSelector').classList).includes('right')) {
annotateImage()
}
canvasCtx.drawImage(video, 0, 0, canvasElement.width, (video.videoHeight / video.videoWidth) * canvasElement.width)
annotateImage(Array.from(document.getElementById('modeSelector').classList).includes('right'))
console.log("LEFT")
//detectSign()
} else {
canvasCtx.drawImage(video, 0, 0, canvasElement.width, (video.videoHeight / video.videoWidth) * canvasElement.width)
console.log("RIGHT")
var current_result = "_"
var previous_result = document.getElementById("predicted_result").innerText
document.getElementById("predicted_result").innerText = current_result
if (previous_result == current_result) {
if (current_time - last_letter_time > getNaturalLength(" ")) {
last_letter_time = current_time
word_list.push(" ")
triggerPulse()
console.log(word_list)
document.getElementById("text").value = word_list.join('')
}
}
else {
last_letter_time = current_time
}
}
document.getElementById("predicted_result").style.width = String((current_time - last_letter_time) / getNaturalLength(global_cres) * 100) + "%"
}
else {
canvasCtx.drawImage(video, 0, 0, canvasElement.width, (video.videoHeight / video.videoWidth) * canvasElement.width)
if (30 > calculateCanvasBrightness(canvasElement)) {
var current_result = "<"
var previous_result = document.getElementById("predicted_result").innerText
document.getElementById("predicted_result").innerText = current_result
var current_time = Math.round(Date.now())
console.log(current_time - last_letter_time)
if (previous_result == current_result) {
if (current_time - last_letter_time > 400) {
last_letter_time = current_time
word_list.pop()
console.log(word_list)
document.getElementById("text").value = word_list.join('')
}
}
else {
last_letter_time = current_time
}
} else {
last_letter_time = Math.round(Date.now())
document.getElementById("predicted_result").style.width = String(0) + "%"
}
}
canvasCtx.restore();
// Keep predicting
if (webcamRunning === true) {
window.requestAnimationFrame(predictWebcam);
}
}
function annotateImage(firstA = true) {
//console.log(results.landmarks)
if (results.landmarks[0]) {
x_array = []
y_array = []
results.landmarks[0].forEach(iterate)
//console.log(x_array)
var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
var image_width = canvasElement.width
var min_x = Math.min(...x_array) * image_width
var min_y = Math.min(...y_array) * image_height
var max_x = Math.max(...x_array) * image_width
var max_y = Math.max(...y_array) * image_height
var sect_height = max_y - (min_y)
var sect_width = max_x - (min_x)
var center_x = (min_x + max_x) / 2
var center_y = (min_y + max_y) / 2
var sect_diameter = 50
if (sect_height > sect_width) {
sect_diameter = sect_height
//console.log("sect_height", sect_diameter)
}
if (sect_height < sect_width) {
sect_diameter = sect_width
// console.log("sect_width", sect_diameter)
}
sect_diameter = sect_diameter + 50
var sect_radius = sect_diameter / 2
var crop_top = center_y - sect_radius
var crop_bottom = center_y + sect_radius
var crop_left = center_x - sect_radius
var crop_right = center_x + sect_radius
if (crop_top < 0) {
crop_top = 0
}
if (crop_left < 0) {
crop_left = 0
}
if (crop_right > image_width) {
crop_right = image_width
}
if (crop_bottom > image_height) {
crop_bottom = image_height
}
if (firstA == true) {
canvasCtx.beginPath();
canvasCtx.rect(crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top);
canvasCtx.stroke();
if (!Array.from(document.getElementById("modeSelector").classList).includes('right')) {
canvasCtx.fillStyle = "#ffffff"
canvasCtx.fill()
}
} else {
canvasCtx.beginPath();
canvasCtx.rect(crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top);
canvasCtx.stroke();
}
}
/* for (const landmarks of results.multiHandLandmarks) {
drawConnectors(canvasCtx, landmarks, HAND_CONNECTIONS, {
color: "#00FF00",
lineWidth: 5
});
drawLandmarks(canvasCtx, landmarks, { color: "#FF0000", lineWidth: 2 });
}*/
// console.log(results)
const landmarks = results.landmarks;
if (landmarks[0]) {
var hand = landmarks[0]
const zValues = hand.map(node => node.z);
const maxZ = Math.max.apply(null, zValues)
const minZ = Math.min.apply(null, zValues)
const zRange = maxZ - minZ
hand.forEach(node => {
node.z = ((node.z - minZ) / zRange) * 255
});
// Thumb connections
drawConnection(hand[4], hand[3], 'rgb(0, 0, 255)', 5); // 4-3 (was red)
drawConnection(hand[3], hand[2], 'rgb(0, 0, 255)', 5); // 3-2 (was red)
drawConnection(hand[2], hand[1], 'rgb(0, 0, 255)', 5); // 2-1 (was red)
// Index connections
drawConnection(hand[8], hand[7], 'rgb(0, 255, 0)', 5); // 8-7
drawConnection(hand[7], hand[6], 'rgb(0, 255, 0)', 5); // 7-6
drawConnection(hand[6], hand[5], 'rgb(0, 255, 0)', 5); // 6-5
// Middle connections
drawConnection(hand[12], hand[11], 'rgb(255, 0, 0)', 5); // 12-11 (was blue)
drawConnection(hand[11], hand[10], 'rgb(255, 0, 0)', 5); // 11-10 (was blue)
drawConnection(hand[10], hand[9], 'rgb(255, 0, 0)', 5); // 10-9 (was blue)
// Ring connections
drawConnection(hand[16], hand[15], 'rgb(0, 255, 255)', 5); // 16-15 (was yellow)
drawConnection(hand[15], hand[14], 'rgb(0, 255, 255)', 5); // 15-14 (was yellow)
drawConnection(hand[14], hand[13], 'rgb(0, 255, 255)', 5); // 14-13 (was yellow)
// Pinky connections
drawConnection(hand[20], hand[19], 'rgb(255, 0, 255)', 5); // 20-19
drawConnection(hand[19], hand[18], 'rgb(255, 0, 255)', 5); // 19-18
drawConnection(hand[18], hand[17], 'rgb(255, 0, 255)', 5); // 18-17
drawConnection(hand[0], hand[1], 'rgb(200, 200, 200)', 5); // 0-1
drawConnection(hand[0], hand[5], 'rgb(200, 200, 200)', 5); // 0-5
drawConnection(hand[0], hand[17], 'rgb(200, 200, 200)', 5); // 0-17
drawConnection(hand[5], hand[9], 'rgb(200, 200, 200)', 5); // 5-9
drawConnection(hand[9], hand[13], 'rgb(200, 200, 200)', 5); // 9-13
drawConnection(hand[13], hand[17], 'rgb(200, 200, 200)', 5); // 13-17
// Thumb
drawLandmarks(canvasCtx, hand[2], '#ffe5b4'); // Thumb tip (2)
drawLandmarks(canvasCtx, hand[3], '#ffe5b4'); // Thumb base (3)
drawLandmarks(canvasCtx, hand[4], '#ffe5b4'); // Thumb base (4)
// Index
drawLandmarks(canvasCtx, hand[6], '#804080'); // Index tip (6)
drawLandmarks(canvasCtx, hand[7], '#804080'); // Index base (7)
drawLandmarks(canvasCtx, hand[8], '#804080'); // Index base (8)
// Middle
drawLandmarks(canvasCtx, hand[10], '#ffcc00'); // Middle tip (10)
drawLandmarks(canvasCtx, hand[11], '#ffcc00'); // Middle base (11)
drawLandmarks(canvasCtx, hand[12], '#ffcc00'); // Middle base (12)
// Ring
drawLandmarks(canvasCtx, hand[14], '#30ff30'); // Ring tip (14)
drawLandmarks(canvasCtx, hand[15], '#30ff30'); // Ring base (15)
drawLandmarks(canvasCtx, hand[16], '#30ff30'); // Ring base (16)
// Pinky
drawLandmarks(canvasCtx, hand[18], '#1565c0'); // Pinky tip (18)
drawLandmarks(canvasCtx, hand[19], '#1565c0'); // Pinky base (19)
drawLandmarks(canvasCtx, hand[20], '#1565c0'); // Pinky base (20)
drawLandmarks(canvasCtx, hand[0], '#ff3030'); // Wrist (0)
drawLandmarks(canvasCtx, hand[1], '#ff3030'); // Palm base (1)
drawLandmarks(canvasCtx, hand[5], '#ff3030'); // Index palm (5)
drawLandmarks(canvasCtx, hand[9], '#ff3030'); // Middle palm (9)
drawLandmarks(canvasCtx, hand[13], '#ff3030'); // Ring palm (13)
drawLandmarks(canvasCtx, hand[17], '#ff3030'); // Pinky palm (17)
// Crop Canvas
if (firstA == true) {
cropCanvas(canvasElement, crop_left, crop_top, crop_right - crop_left, crop_bottom - crop_top)
}
}
// Add more drawing calls for each landmark collection as needed
//# sourceURL=pen.js
}
function iterate(x, y) {
x_array.push(x.x)
y_array.push(x.y)
}
const cropCanvas = (sourceCanvas, left, top, width, height) => {
let destCanvas = document.createElement('canvas');
destCanvas.width = 224;
var cropAspectRatio = width / height;
destCanvas.height = 224 / cropAspectRatio
destCanvas.getContext("2d").drawImage(
sourceCanvas,
left, top, width, height, // source rect with content to crop
0, 0, 224, destCanvas.height); // newCanvas, same size as source
var predictionInput = tf.browser.fromPixels(destCanvas.getContext("2d").getImageData(0, 0, 224, 224))
predict(tf.expandDims(predictionInput, 0));
}
function getNaturalLength(letter) {
console.log('i got', letter, word_list[word_list.length - 1])
if (document.getElementById("natInp").checked) {
console.log('naturally:')
if (letter != word_list[word_list.length - 1]) {
return ((inputFrequency()) * (1 / (maxPercentage / 100)))
} else {
console.log("penalty!")
var rb = Number(repeatBuffer())
return ((inputFrequency()*(80/100))+400)
}
} else {
console.log('stably:')
if (letter != word_list[word_list.length - 1]) {
return ((inputFrequency()))
} else {
console.log("penalty!")
var rb = Number(repeatBuffer())
return ((inputFrequency()*(80/100))+rb)
}
}
}
function mapLogitsToPercentage(logits) {
// Apply ceiling of 260
const cappedLogits = logits.map(value => Math.min(value, 260));
// Map the values from 0-260 to 0-100
const mappedPercentages = cappedLogits.map(value => (value / 260) * 100);
return mappedPercentages;
}
async function predict(inputTensor) {
console.log(index_list[0])
objectDetector.then(function (res) {
var prediction = res.predict(inputTensor);
var outputArray = prediction.dataSync(); // Get the output as an array
var percentages = mapLogitsToPercentage(outputArray)
maxPercentage = Math.max(...percentages)
document.getElementById('predicted_result').style.opacity = (maxPercentage+30)+'%'
var predictedClass = percentages.indexOf(Math.max(...percentages)); // Get the index
var current_result = index_list[predictedClass]
global_cres = current_result
var previous_result = document.getElementById("predicted_result").innerText
document.getElementById("predicted_result").innerText = current_result
var current_time = Math.round(Date.now())
if (index_list[0] == " Good") {
previous_result = " " + previous_result
}
console.log("p:", previous_result, "c:", current_result)
if (previous_result == current_result) {
if (current_time - last_letter_time > getNaturalLength(current_result)) {
last_letter_time = current_time
word_list.push(current_result)
triggerPulse()
console.log(word_list)
document.getElementById("text").value = word_list.join('')
}
}
else {
last_letter_time = current_time
}
console.log(index_list[predictedClass]);
}, function (err) {
console.log(err);
});
}
function drawLandmarks(canvasCtx, landmarks, color) {
var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
var image_width = canvasElement.width
canvasCtx.fillStyle = `rgb(${landmarks.z},${landmarks.z},${landmarks.z})`;
canvasCtx.beginPath();
canvasCtx.arc(landmarks.x * image_width, landmarks.y * image_height, 8, 0, 2 * Math.PI);
canvasCtx.fill();
}
function drawConnection(startNode, endNode, strokeColor, strokeWidth) {
var image_height = (video.videoHeight / video.videoWidth) * canvasElement.width
var image_width = canvasElement.width
canvasCtx.strokeStyle = strokeColor;
canvasCtx.lineWidth = strokeWidth - 1;
canvasCtx.beginPath();
canvasCtx.moveTo(startNode.x * image_width, startNode.y * image_height);
canvasCtx.lineTo(endNode.x * image_width, endNode.y * image_height);
canvasCtx.stroke();
}
function calculateCanvasBrightness(canvas) {
const context = canvas.getContext('2d', { willReadFrequently: true });
// Get the image data from the canvas
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
let totalBrightness = 0;
let pixelCount = 0;
// Loop through each pixel
for (let i = 0; i < data.length; i += 4) {
const r = data[i]; // Red
const g = data[i + 1]; // Green
const b = data[i + 2]; // Blue
// Calculate brightness for this pixel
const brightness = 0.299 * r + 0.587 * g + 0.114 * b;
totalBrightness += brightness;
pixelCount++;
}
// Calculate average brightness
const averageBrightness = totalBrightness / pixelCount;
return averageBrightness;
}
function triggerPulse() {
console.log('did nothing')
/* Apply after working on pulse css more
const resultWrapper = document.querySelector('.wrapper_result');
resultWrapper.classList.add('pulse');
// Remove the class after the animation ends to allow it to be triggered again
resultWrapper.addEventListener('animationend', () => {
resultWrapper.classList.remove('pulse');
}, { once: true });*/
}
</script>
</body>
</html>