From 36fe9f603eadaa90701231d3458bd5430ee000c9 Mon Sep 17 00:00:00 2001 From: "aime.rolandi" Date: Tue, 17 Jun 2025 08:48:55 -0300 Subject: [PATCH] minimo reconocimiento de voz --- minimal_server/RealtimeSTT/__init__.py | 3 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 329 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 391 bytes .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 342 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 338 bytes .../__pycache__/audio_input.cpython-310.pyc | Bin 0 -> 6508 bytes .../__pycache__/audio_input.cpython-311.pyc | Bin 0 -> 10701 bytes .../__pycache__/audio_input.cpython-313.pyc | Bin 0 -> 10044 bytes .../audio_recorder.cpython-310.pyc | Bin 0 -> 78630 bytes .../audio_recorder.cpython-311.pyc | Bin 0 -> 136487 bytes .../audio_recorder.cpython-312.pyc | Bin 0 -> 129311 bytes .../audio_recorder.cpython-313.pyc | Bin 0 -> 129541 bytes .../audio_recorder_client.cpython-310.pyc | Bin 0 -> 22462 bytes .../audio_recorder_client.cpython-311.pyc | Bin 0 -> 40148 bytes .../audio_recorder_client.cpython-313.pyc | Bin 0 -> 39445 bytes .../__pycache__/safepipe.cpython-310.pyc | Bin 0 -> 6475 bytes .../__pycache__/safepipe.cpython-311.pyc | Bin 0 -> 12742 bytes .../__pycache__/safepipe.cpython-313.pyc | Bin 0 -> 11663 bytes .../__pycache__/server.cpython-311.pyc | Bin 0 -> 1428 bytes minimal_server/RealtimeSTT/audio_input.py | 220 ++ minimal_server/RealtimeSTT/audio_recorder.py | 2850 +++++++++++++++++ .../RealtimeSTT/audio_recorder_client.py | 881 +++++ minimal_server/RealtimeSTT/safepipe.py | 245 ++ minimal_server/RealtimeSTT/server.py | 23 + minimal_server/RealtimeSTT/warmup_audio.wav | Bin 0 -> 51870 bytes .../install_packages.cpython-311.pyc | Bin 0 -> 2990 bytes .../__pycache__/stt_server.cpython-311.pyc | Bin 0 -> 55970 bytes .../install_packages.cpython-311.pyc | Bin 0 -> 2997 bytes .../__pycache__/stt_server.cpython-311.pyc | Bin 0 -> 55977 bytes minimal_server/server/install_packages.py | 55 + minimal_server/server/stt_server.py | 913 ++++++ stt_recorder/README.md | 18 + stt_recorder/assets/css/app.css | 86 + stt_recorder/assets/js/app.js | 55 + stt_recorder/assets/js/stt_recorder.js | 119 + stt_recorder/assets/tailwind.config.js | 74 + stt_recorder/assets/vendor/topbar.js | 165 + stt_recorder/config/config.exs | 65 + stt_recorder/config/dev.exs | 75 + stt_recorder/config/prod.exs | 21 + stt_recorder/config/runtime.exs | 24 + stt_recorder/config/test.exs | 24 + stt_recorder/lib/stt_recorder.ex | 9 + stt_recorder/lib/stt_recorder/application.ex | 35 + stt_recorder/lib/stt_recorder/mailer.ex | 3 + stt_recorder/lib/stt_recorder_web.ex | 116 + .../components/core_components.ex | 676 ++++ .../stt_recorder_web/components/layouts.ex | 14 + .../components/layouts/app.html.heex | 6 + .../components/layouts/root.html.heex | 18 + .../controllers/error_html.ex | 24 + .../controllers/error_json.ex | 21 + .../controllers/page_controller.ex | 9 + .../stt_recorder_web/controllers/page_html.ex | 10 + .../controllers/page_html/home.html.heex | 222 ++ stt_recorder/lib/stt_recorder_web/endpoint.ex | 52 + stt_recorder/lib/stt_recorder_web/gettext.ex | 25 + .../live/stt/test_recorder.ex | 160 + stt_recorder/lib/stt_recorder_web/router.ex | 47 + .../lib/stt_recorder_web/telemetry.ex | 70 + stt_recorder/mix.exs | 79 + stt_recorder/mix.lock | 35 + .../priv/gettext/en/LC_MESSAGES/errors.po | 11 + stt_recorder/priv/gettext/errors.pot | 10 + ...vicon-91f37b602a111216f1eef3aa337ad763.ico | Bin 0 -> 152 bytes stt_recorder/priv/static/favicon.ico | Bin 0 -> 152 bytes .../logo-06a11be1f2cdde2c851763d00bdd2e80.svg | 6 + ...go-06a11be1f2cdde2c851763d00bdd2e80.svg.gz | Bin 0 -> 1613 bytes stt_recorder/priv/static/images/logo.svg | 6 + stt_recorder/priv/static/images/logo.svg.gz | Bin 0 -> 1613 bytes ...obots-9e2c81b0855bbff2baa8371bc4a78186.txt | 5 + ...ts-9e2c81b0855bbff2baa8371bc4a78186.txt.gz | Bin 0 -> 164 bytes stt_recorder/priv/static/robots.txt | 5 + stt_recorder/priv/static/robots.txt.gz | Bin 0 -> 164 bytes .../controllers/error_html_test.exs | 14 + .../controllers/error_json_test.exs | 12 + .../controllers/page_controller_test.exs | 8 + stt_recorder/test/support/conn_case.ex | 37 + stt_recorder/test/test_helper.exs | 1 + 79 files changed, 7662 insertions(+) create mode 100644 minimal_server/RealtimeSTT/__init__.py create mode 100644 minimal_server/RealtimeSTT/__pycache__/__init__.cpython-310.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/__init__.cpython-311.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/__init__.cpython-312.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/__init__.cpython-313.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/audio_input.cpython-310.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/audio_input.cpython-311.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/audio_input.cpython-313.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-310.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-311.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-312.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-313.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/audio_recorder_client.cpython-310.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/audio_recorder_client.cpython-311.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/audio_recorder_client.cpython-313.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/safepipe.cpython-310.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/safepipe.cpython-311.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/safepipe.cpython-313.pyc create mode 100644 minimal_server/RealtimeSTT/__pycache__/server.cpython-311.pyc create mode 100644 minimal_server/RealtimeSTT/audio_input.py create mode 100644 minimal_server/RealtimeSTT/audio_recorder.py create mode 100644 minimal_server/RealtimeSTT/audio_recorder_client.py create mode 100644 minimal_server/RealtimeSTT/safepipe.py create mode 100644 minimal_server/RealtimeSTT/server.py create mode 100644 minimal_server/RealtimeSTT/warmup_audio.wav create mode 100644 minimal_server/__pycache__/install_packages.cpython-311.pyc create mode 100644 minimal_server/__pycache__/stt_server.cpython-311.pyc create mode 100644 minimal_server/server/__pycache__/install_packages.cpython-311.pyc create mode 100644 minimal_server/server/__pycache__/stt_server.cpython-311.pyc create mode 100644 minimal_server/server/install_packages.py create mode 100644 minimal_server/server/stt_server.py create mode 100644 stt_recorder/README.md create mode 100644 stt_recorder/assets/css/app.css create mode 100644 stt_recorder/assets/js/app.js create mode 100644 stt_recorder/assets/js/stt_recorder.js create mode 100644 stt_recorder/assets/tailwind.config.js create mode 100644 stt_recorder/assets/vendor/topbar.js create mode 100644 stt_recorder/config/config.exs create mode 100644 stt_recorder/config/dev.exs create mode 100644 stt_recorder/config/prod.exs create mode 100644 stt_recorder/config/runtime.exs create mode 100644 stt_recorder/config/test.exs create mode 100644 stt_recorder/lib/stt_recorder.ex create mode 100644 stt_recorder/lib/stt_recorder/application.ex create mode 100644 stt_recorder/lib/stt_recorder/mailer.ex create mode 100644 stt_recorder/lib/stt_recorder_web.ex create mode 100644 stt_recorder/lib/stt_recorder_web/components/core_components.ex create mode 100644 stt_recorder/lib/stt_recorder_web/components/layouts.ex create mode 100644 stt_recorder/lib/stt_recorder_web/components/layouts/app.html.heex create mode 100644 stt_recorder/lib/stt_recorder_web/components/layouts/root.html.heex create mode 100644 stt_recorder/lib/stt_recorder_web/controllers/error_html.ex create mode 100644 stt_recorder/lib/stt_recorder_web/controllers/error_json.ex create mode 100644 stt_recorder/lib/stt_recorder_web/controllers/page_controller.ex create mode 100644 stt_recorder/lib/stt_recorder_web/controllers/page_html.ex create mode 100644 stt_recorder/lib/stt_recorder_web/controllers/page_html/home.html.heex create mode 100644 stt_recorder/lib/stt_recorder_web/endpoint.ex create mode 100644 stt_recorder/lib/stt_recorder_web/gettext.ex create mode 100644 stt_recorder/lib/stt_recorder_web/live/stt/test_recorder.ex create mode 100644 stt_recorder/lib/stt_recorder_web/router.ex create mode 100644 stt_recorder/lib/stt_recorder_web/telemetry.ex create mode 100644 stt_recorder/mix.exs create mode 100644 stt_recorder/mix.lock create mode 100644 stt_recorder/priv/gettext/en/LC_MESSAGES/errors.po create mode 100644 stt_recorder/priv/gettext/errors.pot create mode 100644 stt_recorder/priv/static/favicon-91f37b602a111216f1eef3aa337ad763.ico create mode 100644 stt_recorder/priv/static/favicon.ico create mode 100644 stt_recorder/priv/static/images/logo-06a11be1f2cdde2c851763d00bdd2e80.svg create mode 100644 stt_recorder/priv/static/images/logo-06a11be1f2cdde2c851763d00bdd2e80.svg.gz create mode 100644 stt_recorder/priv/static/images/logo.svg create mode 100644 stt_recorder/priv/static/images/logo.svg.gz create mode 100644 stt_recorder/priv/static/robots-9e2c81b0855bbff2baa8371bc4a78186.txt create mode 100644 stt_recorder/priv/static/robots-9e2c81b0855bbff2baa8371bc4a78186.txt.gz create mode 100644 stt_recorder/priv/static/robots.txt create mode 100644 stt_recorder/priv/static/robots.txt.gz create mode 100644 stt_recorder/test/stt_recorder_web/controllers/error_html_test.exs create mode 100644 stt_recorder/test/stt_recorder_web/controllers/error_json_test.exs create mode 100644 stt_recorder/test/stt_recorder_web/controllers/page_controller_test.exs create mode 100644 stt_recorder/test/support/conn_case.ex create mode 100644 stt_recorder/test/test_helper.exs diff --git a/minimal_server/RealtimeSTT/__init__.py b/minimal_server/RealtimeSTT/__init__.py new file mode 100644 index 00000000..e6179cc3 --- /dev/null +++ b/minimal_server/RealtimeSTT/__init__.py @@ -0,0 +1,3 @@ +from .audio_recorder import AudioToTextRecorder +from .audio_recorder_client import AudioToTextRecorderClient +from .audio_input import AudioInput \ No newline at end of file diff --git a/minimal_server/RealtimeSTT/__pycache__/__init__.cpython-310.pyc b/minimal_server/RealtimeSTT/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..25fc887acf3875a7c7fccdd55b56b204fa1664a0 GIT binary patch literal 329 zcmd1j<>g`kg6oCa854l?V-N=!FabFZKwPW=BvKes7;_kM8KW2(L2RZRrd;MIW-yyM zhb5OaiWSIa31-k_eaQ$^uE}^y*s(MvGe0ChB(C^b31C?&NBC@hIZ*f}RNHLnCH z$px15%qu7@@zZ3B;!6Y>5nlw=T?BGblqjlTd@@*b5i>}R8zPqpGN6bBD6*2Fh!seI ziC?zPRxzQ)sYS&xMfo|2c`=#sxiLYhi8&>ixv9Y+A-cJV#U-gl$U-sk@tJv{yzTnIDoLl3Gy`l$xAhl#*Hm6qdvy?3|OCnpXmp z5nlw=UBnDD`IacEV0K;i>4BO~Jt2K@`D=mvw$1ypo{LHPnUR0Ilt0Qu^2 A@Bjb+ literal 0 HcmV?d00001 diff --git a/minimal_server/RealtimeSTT/__pycache__/__init__.cpython-312.pyc b/minimal_server/RealtimeSTT/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d6c7ea0a27d6b72b978de24cd0785c4d853e88c9 GIT binary patch literal 342 zcmX@j%ge<81lJ3-GbRA(#~=<2FhLogHGqui3@HpLj5!Rsj8Tk?AU0DDQ!aB9Gnmbs z!;;Gy#mdM~$)d^nk`bs}lkt|YV`)leen@^uYDGy%=(>U!anrFOUy6)w3R za&0fMGK|_e6+ZNo0&V~)m!d$Az4g+6pog9c@Km%XUwY^%P1Cr)H{`BXwp^fG@NsrH zGn{$v_ul;8L}O!R1=nA0zPVL@RZ;#uQVDSxc*HD%G(- zZy9x?W!BABsa}$GT42>Jywie_`UuzSWp30*J!`vCALC}ddP(6WZaq-Ab)VJ8xwfX% zMjqn}N=?gjKlGDKUy6j68EZ*5@K9-Pbdtmqnbq`zgzjuic(L1R2cFZ8g6_)WdlwYN zS9C>rtYVx74K{p@_q@hsuCaF5_F zdsef`M|tIef#rH`H!8s`&uj`B%9g=d8y;YaZt%7=H5nU(`iNA%O(?7YXmz5h^o$x~^_ERsbxHOv6j6};#GOMxWhM^b4+0;NyN#x{1G%To~e^Hs?p2r?I~hOhVMGOpm>wxj*i6oBmc2$I;{Si}SNL z-j6*I-w;vYhBthtbz{|YgT!xnYwPRhTW*XG4p-gi&lGl;X?HWrff@Y7acCX!Q3!L? zR1f~jYIs))Ni?8&wU8862sj6~Kxp_oY=yx5Z9(j_UURw9!#J;`d zH@56JaYOD3Zs&ToNzbwE3eS!^?RF#*kJ}qvJJ~`-+iUnu9~HT2W@=^s;u*Nrz1JZo z--P(<3@k#J@n88d#2-I^Pb;T0O*)Q|Xa0d{Aw4hfpfblYt&8)xJ6$ zsfTQQ(7C7>9W-x7^$M2a^rxBW5~2hyi7;`3hCd~JvaiX)JM*z#!TXqm#dNj8%BsZ} z;KSrkSIsZVrhd;*==?aiqtt z!9NqNr{7npq$`Qp{kp2`7^;$vC#7ABYdzzU@(CbFz`Cw*eaEPLQn0Hml_G(=_!ni$b6& zF9!C*&Ea8Y?UmYygoQYb*5WmYOoMSU4RJ-hM1MyX?=-x2;zwat3Zl(T43U*?yCOsZ zn37mH;x>|w8|=f1AOV2HzVNs-njm|LmuW!N%grneE^U3xA=A)mhwc1rG>)keV-=N* zY^r5etxTv@)w;*NOzk1l9y&mSHmv?Ng2KS6OL&w1D!rK~_u=4)y36(`B2&=NcJ)1F zM@`8g2we7L~gt-^(c^N2o#nKDTv%;U#{;lgcpPBAL^nFjEu;3W@04e z?a@aHHkG?I#v}t7i(%OS@HW@sS7kx1ijo@KwTG%IGtWQAT^E zK_7$q+&nS#wo;4+9MYR>N;nNHPIP|(ELy|TNev3;h-cmnqJ|s97f|T;OPOt_C)W zU7Q9Z_s%0iOs+j8Xf9c)-IY4cO7C^$r1Fhmf}F01%8jTKCYjL|aQn;Icin3>lXuUxr$O`N5wkvx_m6d}eO*fR33 zslE&fS$}6B3)iP;XE?Zx$*55um-|YwHDPZR=V)e6Z5owfMZbpz@pm9}iX1Y6RAeNn zAeNNXbE>W#RbNt1GIme@XLaHfMU^M)o~c#U@{@|no}?$9W5NSqBg_l}%X<`9K1#qm zWI&N2Lrf+*2l?BUTYz+QlsW6DVw!405WQ`-Ywm&7Pb2h@gNZlLM<<}>t}4kRcnZP~ zy|?y=Mvlmq3us9U2BUKtX#{;_07zfKKekzM1cjOXQ4WrQ5c9?|_@eIyAmp}$GC~W`MYPuj3$r5-6A&_@ zzv9scev5uR#)&lW3@MF~fY}vaNx(v-VQ@S{@SyYu6^l zH!xfAO%lWnWnv-TrV`~D+37q&NMcbqWf3p@5Nke94P|CQ96r;@3PbsYOgD~D8wv;F z9f+DH1AzeJRStDvBleO`JHqSu2M`Q?YO)D+LM4AB&oogs*-4ebr-|U{AbljQ&rNGe zwZILIqXqGUs!pxTIa~N5ofz_E`H==i`2I!gfOy|L?wIt^)42MAg46*V4^JZjsIj|a zpXbIy{RZ1Hu`jDgAK;MpwH>QxAwe+Rzrp{@c5%(M+hjsJh;E;6yK!uPU*_7kksEB; zlxut7aFN{>-YqYr9n^Q@91VHa;BSU*FjL+iPi)4sgMajqST?cGhV2>3MTFa}&DuHs zb88n3@Rj^~2YNR;Nz`mQP2t_N&o+a|P0)K@R@zkA@xn$Ia08|e^czYn_xnlWNwcRo zZAVSH1nCC-S>^)`Mp}iXMflW$_7?&aOAP_@!%NxM#!7^I>!3Q$+el22gW2DLg5g@w z@3Sf&On?-4-X<~D4^Gw~yRJT5! zn-au2AMNQ)u!`vr+?>4K$M}9s8<@Iccz0k3_($#=|F2W57Kd|vuaGMmEa_06=p)<>6aV}m$mHNaiitq(^=$ym z55~&fLpl^41U~H=lz?ven_K5^BJuN+?qNebCi)3KxQd6MlbU|MX!UKCO+9OY?C4?Y ztXHT{ANuu?K5F%R!S)BZ9S{o&Q#b(akYFoF zA&9fk5$8js4mB-3h2t;S!}jUd87q+~9{ zE^?kh^aYrsgyT?(QIa3(wEv6-F(s5lB`7_h1IRLnhWTYxdr>9>>C}*ti*B_kI=`e` z#c{H-LubexoRvFS#c{|LiZ_*o#kJ+t#RX?|ZhbK;&tHCj<%iDNa(ywY%)P&`eAQXH zy85HJ^~{>TJh!s4cx6p|6;sLdjVKDlNt*j>BqYW99+j3zNJm@DGIV-V$F{}KUb=*2;O%m(uuxe_=0c6X-Oiw0Ys^!J=!)@Vv}J}Vpuq@(U~n0)PR n@iVG&Nzi-`TS#WTo9p)id16eKKB^oA&9hV;XMaQir`Uf1MLxXh literal 0 HcmV?d00001 diff --git a/minimal_server/RealtimeSTT/__pycache__/audio_input.cpython-311.pyc b/minimal_server/RealtimeSTT/__pycache__/audio_input.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..16ea91fd80a4527bb21f4ed4ebc94e91c07a5282 GIT binary patch literal 10701 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 YdpF>%%6HTK-}dKimf?p4W7_%u0x{*_?EnA( literal 0 HcmV?d00001 diff --git a/minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-310.pyc b/minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15089ecc920b919c9e61b4cfaf9d4399bb9cae36 GIT binary patch literal 78630 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 ZcNwI$=0*OQSo$siUn+2CZBs+|{{T;IIi~;s literal 0 HcmV?d00001 diff --git a/minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-311.pyc b/minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68d808afbff2b7a3b1a0556f80809418576e726e GIT binary patch literal 136487 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

_I@D( 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 Wbw`5&CbXU5$L#*6yTr07@BaX^#lk@V literal 0 HcmV?d00001 diff --git a/minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-312.pyc b/minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7cedcb8fbbd71381590cf3dc2b9ef1a97ad19104 GIT binary patch literal 129311 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 literal 0 HcmV?d00001 diff --git a/minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-313.pyc b/minimal_server/RealtimeSTT/__pycache__/audio_recorder.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38996aab8bc339fd37ea7d19c5a863a9529ae76f GIT binary patch literal 129541 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 Xg_f3U+Org@6T`SBRD{wBv;X#gcjQ=g literal 0 HcmV?d00001 diff --git a/minimal_server/RealtimeSTT/__pycache__/safepipe.cpython-311.pyc b/minimal_server/RealtimeSTT/__pycache__/safepipe.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b04674e42bc76d280a0ce08e371b4313f724e3a2 GIT binary patch literal 12742 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 literal 0 HcmV?d00001 diff --git a/minimal_server/RealtimeSTT/__pycache__/server.cpython-311.pyc b/minimal_server/RealtimeSTT/__pycache__/server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e95b53422e407114c880a637fc7a0b7026178379 GIT binary patch literal 1428 zcmZ`2U27XhaPLdHJ6UJNM!5K^wiA-5Bo1wH9zqEO(?Dr4C01Jyg5z|%YA(^~j=hsp zr516TkUAyMmqPMX{N$#@eJTARCnNB&5HOTL@}}TCqQb}v zZdMRk`mz;-m%jdjY2T5`s%Kv(VL@Qymm1jdie8cY5MCj+M=_x*Ob(rTlgV}iTP$6z zc~q6LX1VY#P{EoN{qwoaQA+*G>HzbGBRw~gC?(5=ky96}klOD89 z_~D3K5@e<(XM_8?G<^4PMIPKQNGnC?v4VMj3K~mUwoUwyWht?VvqsEHGb8~M>ps(~ z)N?D1YL(Ep9sl$brh;8>BH)-1Y+8I)U^rk_m==&aHaT06X%?blGPXjCDLo@P0h)jp zZVuq}^74i9HvyqRnR+#=UUp2k40oGr-p%qAV%0*&C9B2aSr&_Puhyw(U^^qXv~ zKR+fs?R!o=q|@+%!JqmE$aDBM(7rytr%&$clWl#vqfbZj5!e+|%IP>iyO%$+n?KXe zFLv^a(dhfQDL>1N{g!=%|617jD9SChb4#7v(x&oM&F!gEyXw@V&)e!kM_q{2g{Pwv zzqWp9-M{tV)}|aA6MM$|t}(xLqHUb&7^foj2<&Ddhy#cl6*5qBNZs-s%7>8h!m-JL z$}IM(Y9{@ld~BIgZ@7Mwj&WYHe4pmIFYiUKE`K6A!}%#7uhq}s*KO3uMS2`W{+|So zfpjH75Mp#PItpWSAsWUQjYSFWr?MO0-*)ag8`6GeHplL+u(!RDPcMX(k%|ur17Wgt@M$3a1}yT?E@Z@=`+Ld~*6u-OJ<+Jz-Xq literal 0 HcmV?d00001 diff --git a/minimal_server/RealtimeSTT/audio_input.py b/minimal_server/RealtimeSTT/audio_input.py new file mode 100644 index 00000000..6fc68c2f --- /dev/null +++ b/minimal_server/RealtimeSTT/audio_input.py @@ -0,0 +1,220 @@ +from colorama import init, Fore, Style +from scipy.signal import butter, filtfilt, resample_poly +import pyaudio +import logging + +DESIRED_RATE = 16000 +CHUNK_SIZE = 1024 +AUDIO_FORMAT = pyaudio.paInt16 +CHANNELS = 1 + +class AudioInput: + def __init__( + self, + input_device_index: int = None, + debug_mode: bool = False, + target_samplerate: int = DESIRED_RATE, + chunk_size: int = CHUNK_SIZE, + audio_format: int = AUDIO_FORMAT, + channels: int = CHANNELS, + resample_to_target: bool = True, + ): + + self.input_device_index = input_device_index + self.debug_mode = debug_mode + self.audio_interface = None + self.stream = None + self.device_sample_rate = None + self.target_samplerate = target_samplerate + self.chunk_size = chunk_size + self.audio_format = audio_format + self.channels = channels + self.resample_to_target = resample_to_target + + def get_supported_sample_rates(self, device_index): + """Test which standard sample rates are supported by the specified device.""" + standard_rates = [8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000] + supported_rates = [] + + device_info = self.audio_interface.get_device_info_by_index(device_index) + max_channels = device_info.get('maxInputChannels') # Changed from maxOutputChannels + + for rate in standard_rates: + try: + if self.audio_interface.is_format_supported( + rate, + input_device=device_index, # Changed to input_device + input_channels=max_channels, # Changed to input_channels + input_format=self.audio_format, # Changed to input_format + ): + supported_rates.append(rate) + except: + continue + return supported_rates + + def _get_best_sample_rate(self, actual_device_index, desired_rate): + """Determines the best available sample rate for the device.""" + try: + device_info = self.audio_interface.get_device_info_by_index(actual_device_index) + supported_rates = self.get_supported_sample_rates(actual_device_index) + + if desired_rate in supported_rates: + return desired_rate + + return max(supported_rates) + + # lower_rates = [r for r in supported_rates if r <= desired_rate] + # if lower_rates: + # return max(lower_rates) + + # higher_rates = [r for r in supported_rates if r > desired_rate] + # if higher_rates: + # return min(higher_rates) + + return int(device_info.get('defaultSampleRate', 44100)) + + except Exception as e: + logging.warning(f"Error determining sample rate: {e}") + return 44100 # Safe fallback + + def list_devices(self): + """List all available audio input devices with supported sample rates.""" + try: + init() # Initialize colorama + self.audio_interface = pyaudio.PyAudio() + device_count = self.audio_interface.get_device_count() + + print(f"Available audio input devices:") + #print(f"{Fore.LIGHTBLUE_EX}Available audio input devices:{Style.RESET_ALL}") + for i in range(device_count): + device_info = self.audio_interface.get_device_info_by_index(i) + device_name = device_info.get('name') + max_input_channels = device_info.get('maxInputChannels', 0) + + if max_input_channels > 0: # Only consider devices with input capabilities + supported_rates = self.get_supported_sample_rates(i) + print(f"{Fore.LIGHTGREEN_EX}Device {Style.RESET_ALL}{i}{Fore.LIGHTGREEN_EX}: {device_name}{Style.RESET_ALL}") + + # Format each rate in cyan + if supported_rates: + rates_formatted = ", ".join([f"{Fore.CYAN}{rate}{Style.RESET_ALL}" for rate in supported_rates]) + print(f" {Fore.YELLOW}Supported sample rates: {rates_formatted}{Style.RESET_ALL}") + else: + print(f" {Fore.YELLOW}Supported sample rates: None{Style.RESET_ALL}") + + except Exception as e: + print(f"Error listing devices: {e}") + finally: + if self.audio_interface: + self.audio_interface.terminate() + + def setup(self): + """Initialize audio interface and open stream""" + try: + self.audio_interface = pyaudio.PyAudio() + + if self.debug_mode: + print(f"Input device index: {self.input_device_index}") + actual_device_index = (self.input_device_index if self.input_device_index is not None + else self.audio_interface.get_default_input_device_info()['index']) + + if self.debug_mode: + print(f"Actual selected device index: {actual_device_index}") + self.input_device_index = actual_device_index + self.device_sample_rate = self._get_best_sample_rate(actual_device_index, self.target_samplerate) + + if self.debug_mode: + print(f"Setting up audio on device {self.input_device_index} with sample rate {self.device_sample_rate}") + + try: + self.stream = self.audio_interface.open( + format=self.audio_format, + channels=self.channels, + rate=self.device_sample_rate, + input=True, + frames_per_buffer=self.chunk_size, + input_device_index=self.input_device_index, + ) + if self.debug_mode: + print(f"Audio recording initialized successfully at {self.device_sample_rate} Hz") + return True + except Exception as e: + print(f"Failed to initialize audio stream at {self.device_sample_rate} Hz: {e}") + return False + + except Exception as e: + print(f"Error initializing audio recording: {e}") + if self.audio_interface: + self.audio_interface.terminate() + return False + + def lowpass_filter(self, signal, cutoff_freq, sample_rate): + """ + Apply a low-pass Butterworth filter to prevent aliasing in the signal. + + Args: + signal (np.ndarray): Input audio signal to filter + cutoff_freq (float): Cutoff frequency in Hz + sample_rate (float): Sampling rate of the input signal in Hz + + Returns: + np.ndarray: Filtered audio signal + + Notes: + - Uses a 5th order Butterworth filter + - Applies zero-phase filtering using filtfilt + """ + # Calculate the Nyquist frequency (half the sample rate) + nyquist_rate = sample_rate / 2.0 + + # Normalize cutoff frequency to Nyquist rate (required by butter()) + normal_cutoff = cutoff_freq / nyquist_rate + + # Design the Butterworth filter + b, a = butter(5, normal_cutoff, btype='low', analog=False) + + # Apply zero-phase filtering (forward and backward) + filtered_signal = filtfilt(b, a, signal) + return filtered_signal + + def resample_audio(self, pcm_data, target_sample_rate, original_sample_rate): + """ + Filter and resample audio data to a target sample rate. + + Args: + pcm_data (np.ndarray): Input audio data + target_sample_rate (int): Desired output sample rate in Hz + original_sample_rate (int): Original sample rate of input in Hz + + Returns: + np.ndarray: Resampled audio data + + Notes: + - Applies anti-aliasing filter before resampling + - Uses polyphase filtering for high-quality resampling + """ + if target_sample_rate < original_sample_rate: + # Downsampling with low-pass filter + pcm_filtered = self.lowpass_filter(pcm_data, target_sample_rate / 2, original_sample_rate) + resampled = resample_poly(pcm_filtered, target_sample_rate, original_sample_rate) + else: + # Upsampling without low-pass filter + resampled = resample_poly(pcm_data, target_sample_rate, original_sample_rate) + return resampled + + def read_chunk(self): + """Read a chunk of audio data""" + return self.stream.read(self.chunk_size, exception_on_overflow=False) + + def cleanup(self): + """Clean up audio resources""" + try: + if self.stream: + self.stream.stop_stream() + self.stream.close() + self.stream = None + if self.audio_interface: + self.audio_interface.terminate() + self.audio_interface = None + except Exception as e: + print(f"Error cleaning up audio resources: {e}") diff --git a/minimal_server/RealtimeSTT/audio_recorder.py b/minimal_server/RealtimeSTT/audio_recorder.py new file mode 100644 index 00000000..215f1db2 --- /dev/null +++ b/minimal_server/RealtimeSTT/audio_recorder.py @@ -0,0 +1,2850 @@ +""" + +The AudioToTextRecorder class in the provided code facilitates +fast speech-to-text transcription. + +The class employs the faster_whisper library to transcribe the recorded audio +into text using machine learning models, which can be run either on a GPU or +CPU. Voice activity detection (VAD) is built in, meaning the software can +automatically start or stop recording based on the presence or absence of +speech. It integrates wake word detection through the pvporcupine library, +allowing the software to initiate recording when a specific word or phrase +is spoken. The system provides real-time feedback and can be further +customized. + +Features: +- Voice Activity Detection: Automatically starts/stops recording when speech + is detected or when speech ends. +- Wake Word Detection: Starts recording when a specified wake word (or words) + is detected. +- Event Callbacks: Customizable callbacks for when recording starts + or finishes. +- Fast Transcription: Returns the transcribed text from the audio as fast + as possible. + +Author: Kolja Beigel + +""" + +from faster_whisper import WhisperModel, BatchedInferencePipeline +from typing import Iterable, List, Optional, Union +from openwakeword.model import Model +import torch.multiprocessing as mp +from scipy.signal import resample +import signal as system_signal +from ctypes import c_bool +from scipy import signal +from .safepipe import SafePipe +import soundfile as sf +import faster_whisper +import openwakeword +import collections +import numpy as np +import pvporcupine +import traceback +import threading +import webrtcvad +import datetime +import platform +import logging +import struct +import base64 +import queue +import torch +import halo +import time +import copy +import os +import re +import gc + +# Named logger for this module. +logger = logging.getLogger("realtimestt") +logger.propagate = False + +# Set OpenMP runtime duplicate library handling to OK (Use only for development!) +os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE' + +INIT_MODEL_TRANSCRIPTION = "tiny" +INIT_MODEL_TRANSCRIPTION_REALTIME = "tiny" +INIT_REALTIME_PROCESSING_PAUSE = 0.2 +INIT_REALTIME_INITIAL_PAUSE = 0.2 +INIT_SILERO_SENSITIVITY = 0.4 +INIT_WEBRTC_SENSITIVITY = 3 +INIT_POST_SPEECH_SILENCE_DURATION = 0.6 +INIT_MIN_LENGTH_OF_RECORDING = 0.5 +INIT_MIN_GAP_BETWEEN_RECORDINGS = 0 +INIT_WAKE_WORDS_SENSITIVITY = 0.6 +INIT_PRE_RECORDING_BUFFER_DURATION = 1.0 +INIT_WAKE_WORD_ACTIVATION_DELAY = 0.0 +INIT_WAKE_WORD_TIMEOUT = 5.0 +INIT_WAKE_WORD_BUFFER_DURATION = 0.1 +ALLOWED_LATENCY_LIMIT = 100 + +TIME_SLEEP = 0.02 +SAMPLE_RATE = 16000 +BUFFER_SIZE = 512 +INT16_MAX_ABS_VALUE = 32768.0 + +INIT_HANDLE_BUFFER_OVERFLOW = False +if platform.system() != 'Darwin': + INIT_HANDLE_BUFFER_OVERFLOW = True + + +class TranscriptionWorker: + def __init__(self, conn, stdout_pipe, model_path, download_root, compute_type, gpu_device_index, device, + ready_event, shutdown_event, interrupt_stop_event, beam_size, initial_prompt, suppress_tokens, + batch_size, faster_whisper_vad_filter, normalize_audio): + self.conn = conn + self.stdout_pipe = stdout_pipe + self.model_path = model_path + self.download_root = download_root + self.compute_type = compute_type + self.gpu_device_index = gpu_device_index + self.device = device + self.ready_event = ready_event + self.shutdown_event = shutdown_event + self.interrupt_stop_event = interrupt_stop_event + self.beam_size = beam_size + self.initial_prompt = initial_prompt + self.suppress_tokens = suppress_tokens + self.batch_size = batch_size + self.faster_whisper_vad_filter = faster_whisper_vad_filter + self.normalize_audio = normalize_audio + self.queue = queue.Queue() + + def custom_print(self, *args, **kwargs): + message = ' '.join(map(str, args)) + try: + self.stdout_pipe.send(message) + except (BrokenPipeError, EOFError, OSError): + pass + + def poll_connection(self): + while not self.shutdown_event.is_set(): + try: + if self.conn.poll(0.01): + data = self.conn.recv() + self.queue.put(data) + else: + time.sleep(TIME_SLEEP) + except OSError as e: + if hasattr(e, "winerror") and e.winerror == 6: + logging.info("Conexión cerrada (WinError 6), saliendo del bucle de polling.") + break + else: + logging.error(f"Error OSError recibiendo datos: {e}", exc_info=True) + time.sleep(TIME_SLEEP) + except Exception as e: + logging.error(f"Error recibiendo datos de la conexión: {e}", exc_info=True) + time.sleep(TIME_SLEEP) + + def run(self): + if __name__ == "__main__": + system_signal.signal(system_signal.SIGINT, system_signal.SIG_IGN) + __builtins__['print'] = self.custom_print + + logging.info(f"Initializing faster_whisper main transcription model {self.model_path}") + + try: + model = faster_whisper.WhisperModel( + model_size_or_path=self.model_path, + device=self.device, + compute_type=self.compute_type, + device_index=self.gpu_device_index, + download_root=self.download_root, + ) + # Create a short dummy audio array, for example 1 second of silence at 16 kHz + if self.batch_size > 0: + model = BatchedInferencePipeline(model=model) + + # Run a warm-up transcription + current_dir = os.path.dirname(os.path.realpath(__file__)) + warmup_audio_path = os.path.join( + current_dir, "warmup_audio.wav" + ) + warmup_audio_data, _ = sf.read(warmup_audio_path, dtype="float32") + segments, info = model.transcribe(warmup_audio_data, language="es", beam_size=1) + model_warmup_transcription = " ".join(segment.text for segment in segments) + except Exception as e: + logging.exception(f"Error initializing main faster_whisper transcription model: {e}") + raise + + self.ready_event.set() + logging.debug("Faster_whisper main speech to text transcription model initialized successfully") + + # Start the polling thread + polling_thread = threading.Thread(target=self.poll_connection) + polling_thread.start() + + try: + while not self.shutdown_event.is_set(): + try: + audio, language, use_prompt = self.queue.get(timeout=0.1) + try: + logging.debug(f"Transcribing audio with language {language}") + start_t = time.time() + + # normalize audio to -0.95 dBFS + if audio is not None and audio .size > 0: + if self.normalize_audio: + peak = np.max(np.abs(audio)) + if peak > 0: + audio = (audio / peak) * 0.95 + else: + logging.error("Received None audio for transcription") + self.conn.send(('error', "Received None audio for transcription")) + continue + + prompt = None + if use_prompt: + prompt = self.initial_prompt if self.initial_prompt else None + + if self.batch_size > 0: + segments, info = model.transcribe( + audio, + # vad_filter=True, + language=language if language else None, + beam_size=self.beam_size, + initial_prompt=prompt, + suppress_tokens=self.suppress_tokens, + batch_size=self.batch_size, + vad_filter=self.faster_whisper_vad_filter + ) + else: + segments, info = model.transcribe( + audio, + # vad_filter=True, + language=language if language else None, + beam_size=self.beam_size, + initial_prompt=prompt, + suppress_tokens=self.suppress_tokens, + vad_filter=self.faster_whisper_vad_filter + ) + elapsed = time.time() - start_t + transcription = " ".join(seg.text for seg in segments).strip() + logging.debug(f"Final text detected with main model: {transcription} in {elapsed:.4f}s") + self.conn.send(('success', (transcription, info))) + except Exception as e: + logging.error(f"General error in transcription: {e}", exc_info=True) + self.conn.send(('error', str(e))) + except queue.Empty: + continue + except KeyboardInterrupt: + self.interrupt_stop_event.set() + logging.debug("Transcription worker process finished due to KeyboardInterrupt") + break + except Exception as e: + logging.error(f"General error in processing queue item: {e}", exc_info=True) + finally: + __builtins__['print'] = print # Restore the original print function + self.conn.close() + self.stdout_pipe.close() + self.shutdown_event.set() # Ensure the polling thread will stop + polling_thread.join() # Wait for the polling thread to finish + + +class bcolors: + OKGREEN = '\033[92m' # Green for active speech detection + WARNING = '\033[93m' # Yellow for silence detection + ENDC = '\033[0m' # Reset to default color + + +class AudioToTextRecorder: + """ + A class responsible for capturing audio from the microphone, detecting + voice activity, and then transcribing the captured audio using the + `faster_whisper` model. + """ + + def __init__(self, + model: str = INIT_MODEL_TRANSCRIPTION, + download_root: str = None, + language: str = "", + compute_type: str = "default", + input_device_index: int = None, + gpu_device_index: Union[int, List[int]] = 0, + device: str = "cuda", + on_recording_start=None, + on_recording_stop=None, + on_transcription_start=None, + ensure_sentence_starting_uppercase=True, + ensure_sentence_ends_with_period=True, + use_microphone=True, + spinner=True, + level=logging.WARNING, + batch_size: int = 16, + + # Realtime transcription parameters + enable_realtime_transcription=False, + use_main_model_for_realtime=False, + realtime_model_type=INIT_MODEL_TRANSCRIPTION_REALTIME, + realtime_processing_pause=INIT_REALTIME_PROCESSING_PAUSE, + init_realtime_after_seconds=INIT_REALTIME_INITIAL_PAUSE, + on_realtime_transcription_update=None, + on_realtime_transcription_stabilized=None, + realtime_batch_size: int = 16, + + # Voice activation parameters + silero_sensitivity: float = INIT_SILERO_SENSITIVITY, + silero_use_onnx: bool = False, + silero_deactivity_detection: bool = False, + webrtc_sensitivity: int = INIT_WEBRTC_SENSITIVITY, + post_speech_silence_duration: float = ( + INIT_POST_SPEECH_SILENCE_DURATION + ), + min_length_of_recording: float = ( + INIT_MIN_LENGTH_OF_RECORDING + ), + min_gap_between_recordings: float = ( + INIT_MIN_GAP_BETWEEN_RECORDINGS + ), + pre_recording_buffer_duration: float = ( + INIT_PRE_RECORDING_BUFFER_DURATION + ), + on_vad_start=None, + on_vad_stop=None, + on_vad_detect_start=None, + on_vad_detect_stop=None, + on_turn_detection_start=None, + on_turn_detection_stop=None, + + # Wake word parameters + wakeword_backend: str = "", + openwakeword_model_paths: str = None, + openwakeword_inference_framework: str = "onnx", + wake_words: str = "", + wake_words_sensitivity: float = INIT_WAKE_WORDS_SENSITIVITY, + wake_word_activation_delay: float = ( + INIT_WAKE_WORD_ACTIVATION_DELAY + ), + wake_word_timeout: float = INIT_WAKE_WORD_TIMEOUT, + wake_word_buffer_duration: float = INIT_WAKE_WORD_BUFFER_DURATION, + on_wakeword_detected=None, + on_wakeword_timeout=None, + on_wakeword_detection_start=None, + on_wakeword_detection_end=None, + on_recorded_chunk=None, + debug_mode=False, + handle_buffer_overflow: bool = INIT_HANDLE_BUFFER_OVERFLOW, + beam_size: int = 5, + beam_size_realtime: int = 3, + buffer_size: int = BUFFER_SIZE, + sample_rate: int = SAMPLE_RATE, + initial_prompt: Optional[Union[str, Iterable[int]]] = None, + initial_prompt_realtime: Optional[Union[str, Iterable[int]]] = None, + suppress_tokens: Optional[List[int]] = [-1], + print_transcription_time: bool = False, + early_transcription_on_silence: int = 0, + allowed_latency_limit: int = ALLOWED_LATENCY_LIMIT, + no_log_file: bool = False, + use_extended_logging: bool = False, + faster_whisper_vad_filter: bool = True, + normalize_audio: bool = False, + start_callback_in_new_thread: bool = False, + ): + """ + Initializes an audio recorder and transcription + and wake word detection. + + Args: + - model (str, default="tiny"): Specifies the size of the transcription + model to use or the path to a converted model directory. + Valid options are 'tiny', 'tiny', 'base', 'base', + 'small', 'small', 'medium', 'medium', 'large-v1', + 'large-v2'. + If a specific size is provided, the model is downloaded + from the Hugging Face Hub. + - download_root (str, default=None): Specifies the root path were the Whisper models + are downloaded to. When empty, the default is used. + - language (str, default=""): Language code for speech-to-text engine. + If not specified, the model will attempt to detect the language + automatically. + - compute_type (str, default="default"): Specifies the type of + computation to be used for transcription. + See https://opennmt.net/CTranslate2/quantization.html. + - input_device_index (int, default=0): The index of the audio input + device to use. + - gpu_device_index (int, default=0): Device ID to use. + The model can also be loaded on multiple GPUs by passing a list of + IDs (e.g. [0, 1, 2, 3]). In that case, multiple transcriptions can + run in parallel when transcribe() is called from multiple Python + threads + - device (str, default="cuda"): Device for model to use. Can either be + "cuda" or "cpu". + - on_recording_start (callable, default=None): Callback function to be + called when recording of audio to be transcripted starts. + - on_recording_stop (callable, default=None): Callback function to be + called when recording of audio to be transcripted stops. + - on_transcription_start (callable, default=None): Callback function + to be called when transcription of audio to text starts. + - ensure_sentence_starting_uppercase (bool, default=True): Ensures + that every sentence detected by the algorithm starts with an + uppercase letter. + - ensure_sentence_ends_with_period (bool, default=True): Ensures that + every sentence that doesn't end with punctuation such as "?", "!" + ends with a period + - use_microphone (bool, default=True): Specifies whether to use the + microphone as the audio input source. If set to False, the + audio input source will be the audio data sent through the + feed_audio() method. + - spinner (bool, default=True): Show spinner animation with current + state. + - level (int, default=logging.WARNING): Logging level. + - batch_size (int, default=16): Batch size for the main transcription + - enable_realtime_transcription (bool, default=False): Enables or + disables real-time transcription of audio. When set to True, the + audio will be transcribed continuously as it is being recorded. + - use_main_model_for_realtime (str, default=False): + If True, use the main transcription model for both regular and + real-time transcription. If False, use a separate model specified + by realtime_model_type for real-time transcription. + Using a single model can save memory and potentially improve + performance, but may not be optimized for real-time processing. + Using separate models allows for a smaller, faster model for + real-time transcription while keeping a more accurate model for + final transcription. + - realtime_model_type (str, default="tiny"): Specifies the machine + learning model to be used for real-time transcription. Valid + options include 'tiny', 'tiny', 'base', 'base', 'small', + 'small', 'medium', 'medium', 'large-v1', 'large-v2'. + - realtime_processing_pause (float, default=0.1): Specifies the time + interval in seconds after a chunk of audio gets transcribed. Lower + values will result in more "real-time" (frequent) transcription + updates but may increase computational load. + - init_realtime_after_seconds (float, default=0.2): Specifies the + initial waiting time after the recording was initiated before + yielding the first realtime transcription + - on_realtime_transcription_update = A callback function that is + triggered whenever there's an update in the real-time + transcription. The function is called with the newly transcribed + text as its argument. + - on_realtime_transcription_stabilized = A callback function that is + triggered when the transcribed text stabilizes in quality. The + stabilized text is generally more accurate but may arrive with a + slight delay compared to the regular real-time updates. + - realtime_batch_size (int, default=16): Batch size for the real-time + transcription model. + - silero_sensitivity (float, default=SILERO_SENSITIVITY): Sensitivity + for the Silero Voice Activity Detection model ranging from 0 + (least sensitive) to 1 (most sensitive). Default is 0.5. + - silero_use_onnx (bool, default=False): Enables usage of the + pre-trained model from Silero in the ONNX (Open Neural Network + Exchange) format instead of the PyTorch format. This is + recommended for faster performance. + - silero_deactivity_detection (bool, default=False): Enables the Silero + model for end-of-speech detection. More robust against background + noise. Utilizes additional GPU resources but improves accuracy in + noisy environments. When False, uses the default WebRTC VAD, + which is more sensitive but may continue recording longer due + to background sounds. + - webrtc_sensitivity (int, default=WEBRTC_SENSITIVITY): Sensitivity + for the WebRTC Voice Activity Detection engine ranging from 0 + (least aggressive / most sensitive) to 3 (most aggressive, + least sensitive). Default is 3. + - post_speech_silence_duration (float, default=0.2): Duration in + seconds of silence that must follow speech before the recording + is considered to be completed. This ensures that any brief + pauses during speech don't prematurely end the recording. + - min_gap_between_recordings (float, default=1.0): Specifies the + minimum time interval in seconds that should exist between the + end of one recording session and the beginning of another to + prevent rapid consecutive recordings. + - min_length_of_recording (float, default=1.0): Specifies the minimum + duration in seconds that a recording session should last to ensure + meaningful audio capture, preventing excessively short or + fragmented recordings. + - pre_recording_buffer_duration (float, default=0.2): Duration in + seconds for the audio buffer to maintain pre-roll audio + (compensates speech activity detection latency) + - on_vad_start (callable, default=None): Callback function to be called + when the system detected the start of voice activity presence. + - on_vad_stop (callable, default=None): Callback function to be called + when the system detected the stop (end) of voice activity presence. + - on_vad_detect_start (callable, default=None): Callback function to + be called when the system listens for voice activity. This is not + called when VAD actually happens (use on_vad_start for this), but + when the system starts listening for it. + - on_vad_detect_stop (callable, default=None): Callback function to be + called when the system stops listening for voice activity. This is + not called when VAD actually stops (use on_vad_stop for this), but + when the system stops listening for it. + - on_turn_detection_start (callable, default=None): Callback function + to be called when the system starts to listen for a turn of speech. + - on_turn_detection_stop (callable, default=None): Callback function to + be called when the system stops listening for a turn of speech. + - wakeword_backend (str, default=""): Specifies the backend library to + use for wake word detection. Supported options include 'pvporcupine' + for using the Porcupine wake word engine or 'oww' for using the + OpenWakeWord engine. + - wakeword_backend (str, default="pvporcupine"): Specifies the backend + library to use for wake word detection. Supported options include + 'pvporcupine' for using the Porcupine wake word engine or 'oww' for + using the OpenWakeWord engine. + - openwakeword_model_paths (str, default=None): Comma-separated paths + to model files for the openwakeword library. These paths point to + custom models that can be used for wake word detection when the + openwakeword library is selected as the wakeword_backend. + - openwakeword_inference_framework (str, default="onnx"): Specifies + the inference framework to use with the openwakeword library. + Can be either 'onnx' for Open Neural Network Exchange format + or 'tflite' for TensorFlow Lite. + - wake_words (str, default=""): Comma-separated string of wake words to + initiate recording when using the 'pvporcupine' wakeword backend. + Supported wake words include: 'alexa', 'americano', 'blueberry', + 'bumblebee', 'computer', 'grapefruits', 'grasshopper', 'hey google', + 'hey siri', 'jarvis', 'ok google', 'picovoice', 'porcupine', + 'terminator'. For the 'openwakeword' backend, wake words are + automatically extracted from the provided model files, so specifying + them here is not necessary. + - wake_words_sensitivity (float, default=0.5): Sensitivity for wake + word detection, ranging from 0 (least sensitive) to 1 (most + sensitive). Default is 0.5. + - wake_word_activation_delay (float, default=0): Duration in seconds + after the start of monitoring before the system switches to wake + word activation if no voice is initially detected. If set to + zero, the system uses wake word activation immediately. + - wake_word_timeout (float, default=5): Duration in seconds after a + wake word is recognized. If no subsequent voice activity is + detected within this window, the system transitions back to an + inactive state, awaiting the next wake word or voice activation. + - wake_word_buffer_duration (float, default=0.1): Duration in seconds + to buffer audio data during wake word detection. This helps in + cutting out the wake word from the recording buffer so it does not + falsely get detected along with the following spoken text, ensuring + cleaner and more accurate transcription start triggers. + Increase this if parts of the wake word get detected as text. + - on_wakeword_detected (callable, default=None): Callback function to + be called when a wake word is detected. + - on_wakeword_timeout (callable, default=None): Callback function to + be called when the system goes back to an inactive state after when + no speech was detected after wake word activation + - on_wakeword_detection_start (callable, default=None): Callback + function to be called when the system starts to listen for wake + words + - on_wakeword_detection_end (callable, default=None): Callback + function to be called when the system stops to listen for + wake words (e.g. because of timeout or wake word detected) + - on_recorded_chunk (callable, default=None): Callback function to be + called when a chunk of audio is recorded. The function is called + with the recorded audio chunk as its argument. + - debug_mode (bool, default=False): If set to True, the system will + print additional debug information to the console. + - handle_buffer_overflow (bool, default=True): If set to True, the system + will log a warning when an input overflow occurs during recording and + remove the data from the buffer. + - beam_size (int, default=5): The beam size to use for beam search + decoding. + - beam_size_realtime (int, default=3): The beam size to use for beam + search decoding in the real-time transcription model. + - buffer_size (int, default=512): The buffer size to use for audio + recording. Changing this may break functionality. + - sample_rate (int, default=16000): The sample rate to use for audio + recording. Changing this will very probably functionality (as the + WebRTC VAD model is very sensitive towards the sample rate). + - initial_prompt (str or iterable of int, default=None): Initial + prompt to be fed to the main transcription model. + - initial_prompt_realtime (str or iterable of int, default=None): + Initial prompt to be fed to the real-time transcription model. + - suppress_tokens (list of int, default=[-1]): Tokens to be suppressed + from the transcription output. + - print_transcription_time (bool, default=False): Logs processing time + of main model transcription + - early_transcription_on_silence (int, default=0): If set, the + system will transcribe audio faster when silence is detected. + Transcription will start after the specified milliseconds, so + keep this value lower than post_speech_silence_duration. + Ideally around post_speech_silence_duration minus the estimated + transcription time with the main model. + If silence lasts longer than post_speech_silence_duration, the + recording is stopped, and the transcription is submitted. If + voice activity resumes within this period, the transcription + is discarded. Results in faster final transcriptions to the cost + of additional GPU load due to some unnecessary final transcriptions. + - allowed_latency_limit (int, default=100): Maximal amount of chunks + that can be unprocessed in queue before discarding chunks. + - no_log_file (bool, default=False): Skips writing of debug log file. + - use_extended_logging (bool, default=False): Writes extensive + log messages for the recording worker, that processes the audio + chunks. + - faster_whisper_vad_filter (bool, default=True): If set to True, + the system will additionally use the VAD filter from the faster_whisper library + for voice activity detection. This filter is more robust against + background noise but requires additional GPU resources. + - normalize_audio (bool, default=False): If set to True, the system will + normalize the audio to a specific range before processing. This can + help improve the quality of the transcription. + - start_callback_in_new_thread (bool, default=False): If set to True, + the callback functions will be executed in a + new thread. This can help improve performance by allowing the + callback to run concurrently with other operations. + + Raises: + Exception: Errors related to initializing transcription + model, wake word detection, or audio recording. + """ + + self.language = language + self.compute_type = compute_type + self.input_device_index = input_device_index + self.gpu_device_index = gpu_device_index + self.device = device + self.wake_words = wake_words + self.wake_word_activation_delay = wake_word_activation_delay + self.wake_word_timeout = wake_word_timeout + self.wake_word_buffer_duration = wake_word_buffer_duration + self.ensure_sentence_starting_uppercase = ( + ensure_sentence_starting_uppercase + ) + self.ensure_sentence_ends_with_period = ( + ensure_sentence_ends_with_period + ) + self.use_microphone = mp.Value(c_bool, use_microphone) + self.min_gap_between_recordings = min_gap_between_recordings + self.min_length_of_recording = min_length_of_recording + self.pre_recording_buffer_duration = pre_recording_buffer_duration + self.post_speech_silence_duration = post_speech_silence_duration + self.on_recording_start = on_recording_start + self.on_recording_stop = on_recording_stop + self.on_wakeword_detected = on_wakeword_detected + self.on_wakeword_timeout = on_wakeword_timeout + self.on_vad_start = on_vad_start + self.on_vad_stop = on_vad_stop + self.on_vad_detect_start = on_vad_detect_start + self.on_vad_detect_stop = on_vad_detect_stop + self.on_turn_detection_start = on_turn_detection_start + self.on_turn_detection_stop = on_turn_detection_stop + self.on_wakeword_detection_start = on_wakeword_detection_start + self.on_wakeword_detection_end = on_wakeword_detection_end + self.on_recorded_chunk = on_recorded_chunk + self.on_transcription_start = on_transcription_start + self.enable_realtime_transcription = enable_realtime_transcription + self.use_main_model_for_realtime = use_main_model_for_realtime + self.main_model_type = model + if not download_root: + download_root = None + self.download_root = download_root + self.realtime_model_type = realtime_model_type + self.realtime_processing_pause = realtime_processing_pause + self.init_realtime_after_seconds = init_realtime_after_seconds + self.on_realtime_transcription_update = ( + on_realtime_transcription_update + ) + self.on_realtime_transcription_stabilized = ( + on_realtime_transcription_stabilized + ) + self.debug_mode = debug_mode + self.handle_buffer_overflow = handle_buffer_overflow + self.beam_size = beam_size + self.beam_size_realtime = beam_size_realtime + self.allowed_latency_limit = allowed_latency_limit + self.batch_size = batch_size + self.realtime_batch_size = realtime_batch_size + + self.level = level + self.audio_queue = mp.Queue() + self.buffer_size = buffer_size + self.sample_rate = sample_rate + self.recording_start_time = 0 + self.recording_stop_time = 0 + self.last_recording_start_time = 0 + self.last_recording_stop_time = 0 + self.wake_word_detect_time = 0 + self.silero_check_time = 0 + self.silero_working = False + self.speech_end_silence_start = 0 + self.silero_sensitivity = silero_sensitivity + self.silero_deactivity_detection = silero_deactivity_detection + self.listen_start = 0 + self.spinner = spinner + self.halo = None + self.state = "inactive" + self.wakeword_detected = False + self.text_storage = [] + self.realtime_stabilized_text = "" + self.realtime_stabilized_safetext = "" + self.is_webrtc_speech_active = False + self.is_silero_speech_active = False + self.recording_thread = None + self.realtime_thread = None + self.audio_interface = None + self.audio = None + self.stream = None + self.start_recording_event = threading.Event() + self.stop_recording_event = threading.Event() + self.backdate_stop_seconds = 0.0 + self.backdate_resume_seconds = 0.0 + self.last_transcription_bytes = None + self.last_transcription_bytes_b64 = None + self.initial_prompt = initial_prompt + self.initial_prompt_realtime = initial_prompt_realtime + self.suppress_tokens = suppress_tokens + self.use_wake_words = wake_words or wakeword_backend in {'oww', 'openwakeword', 'openwakewords'} + self.detected_language = None + self.detected_language_probability = 0 + self.detected_realtime_language = None + self.detected_realtime_language_probability = 0 + self.transcription_lock = threading.Lock() + self.shutdown_lock = threading.Lock() + self.transcribe_count = 0 + self.print_transcription_time = print_transcription_time + self.early_transcription_on_silence = early_transcription_on_silence + self.use_extended_logging = use_extended_logging + self.faster_whisper_vad_filter = faster_whisper_vad_filter + self.normalize_audio = normalize_audio + self.awaiting_speech_end = False + self.start_callback_in_new_thread = start_callback_in_new_thread + + # ---------------------------------------------------------------------------- + # Named logger configuration + # By default, let's set it up so it logs at 'level' to the console. + # If you do NOT want this default configuration, remove the lines below + # and manage your "realtimestt" logger from your application code. + logger.setLevel(logging.DEBUG) # We capture all, then filter via handlers + + log_format = "RealTimeSTT: %(name)s - %(levelname)s - %(message)s" + file_log_format = "%(asctime)s.%(msecs)03d - " + log_format + + # Create and set up console handler + console_handler = logging.StreamHandler() + console_handler.setLevel(self.level) + console_handler.setFormatter(logging.Formatter(log_format)) + + logger.addHandler(console_handler) + + if not no_log_file: + file_handler = logging.FileHandler('realtimesst.log') + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(logging.Formatter(file_log_format, datefmt='%Y-%m-%d %H:%M:%S')) + logger.addHandler(file_handler) + # ---------------------------------------------------------------------------- + + self.is_shut_down = False + self.shutdown_event = mp.Event() + + try: + # Only set the start method if it hasn't been set already + if mp.get_start_method(allow_none=True) is None: + mp.set_start_method("spawn") + except RuntimeError as e: + logger.info(f"Start method has already been set. Details: {e}") + + logger.info("Starting RealTimeSTT") + + if use_extended_logging: + logger.info("RealtimeSTT was called with these parameters:") + for param, value in locals().items(): + logger.info(f"{param}: {value}") + + self.interrupt_stop_event = mp.Event() + self.was_interrupted = mp.Event() + self.main_transcription_ready_event = mp.Event() + + self.parent_transcription_pipe, child_transcription_pipe = SafePipe() + self.parent_stdout_pipe, child_stdout_pipe = SafePipe() + + # Set device for model + self.device = "cuda" if self.device == "cuda" and torch.cuda.is_available() else "cpu" + + self.transcript_process = self._start_thread( + target=AudioToTextRecorder._transcription_worker, + args=( + child_transcription_pipe, + child_stdout_pipe, + self.main_model_type, + self.download_root, + self.compute_type, + self.gpu_device_index, + self.device, + self.main_transcription_ready_event, + self.shutdown_event, + self.interrupt_stop_event, + self.beam_size, + self.initial_prompt, + self.suppress_tokens, + self.batch_size, + self.faster_whisper_vad_filter, + self.normalize_audio, + ) + ) + + # Start audio data reading process + if self.use_microphone.value: + logger.info("Initializing audio recording" + " (creating pyAudio input stream," + f" sample rate: {self.sample_rate}" + f" buffer size: {self.buffer_size}" + ) + self.reader_process = self._start_thread( + target=AudioToTextRecorder._audio_data_worker, + args=( + self.audio_queue, + self.sample_rate, + self.buffer_size, + self.input_device_index, + self.shutdown_event, + self.interrupt_stop_event, + self.use_microphone + ) + ) + + # Initialize the realtime transcription model + if self.enable_realtime_transcription and not self.use_main_model_for_realtime: + try: + logger.info("Initializing faster_whisper realtime " + f"transcription model {self.realtime_model_type}, " + f"default device: {self.device}, " + f"compute type: {self.compute_type}, " + f"device index: {self.gpu_device_index}, " + f"download root: {self.download_root}" + ) + self.realtime_model_type = faster_whisper.WhisperModel( + model_size_or_path=self.realtime_model_type, + device=self.device, + compute_type=self.compute_type, + device_index=self.gpu_device_index, + download_root=self.download_root, + ) + if self.realtime_batch_size > 0: + self.realtime_model_type = BatchedInferencePipeline(model=self.realtime_model_type) + + # Run a warm-up transcription + current_dir = os.path.dirname(os.path.realpath(__file__)) + warmup_audio_path = os.path.join( + current_dir, "warmup_audio.wav" + ) + warmup_audio_data, _ = sf.read(warmup_audio_path, dtype="float32") + segments, info = self.realtime_model_type.transcribe(warmup_audio_data, language="es", beam_size=1) + model_warmup_transcription = " ".join(segment.text for segment in segments) + except Exception as e: + logger.exception("Error initializing faster_whisper " + f"realtime transcription model: {e}" + ) + raise + + logger.debug("Faster_whisper realtime speech to text " + "transcription model initialized successfully") + + # Setup wake word detection + if wake_words or wakeword_backend in {'oww', 'openwakeword', 'openwakewords', 'pvp', 'pvporcupine'}: + self.wakeword_backend = wakeword_backend + + self.wake_words_list = [ + word.strip() for word in wake_words.lower().split(',') + ] + self.wake_words_sensitivity = wake_words_sensitivity + self.wake_words_sensitivities = [ + float(wake_words_sensitivity) + for _ in range(len(self.wake_words_list)) + ] + + if wake_words and self.wakeword_backend in {'pvp', 'pvporcupine'}: + + try: + self.porcupine = pvporcupine.create( + keywords=self.wake_words_list, + sensitivities=self.wake_words_sensitivities + ) + self.buffer_size = self.porcupine.frame_length + self.sample_rate = self.porcupine.sample_rate + + except Exception as e: + logger.exception( + "Error initializing porcupine " + f"wake word detection engine: {e}. " + f"Wakewords: {self.wake_words_list}." + ) + raise + + logger.debug( + "Porcupine wake word detection engine initialized successfully" + ) + + elif wake_words and self.wakeword_backend in {'oww', 'openwakeword', 'openwakewords'}: + + openwakeword.utils.download_models() + + try: + if openwakeword_model_paths: + model_paths = openwakeword_model_paths.split(',') + self.owwModel = Model( + wakeword_models=model_paths, + inference_framework=openwakeword_inference_framework + ) + logger.info( + "Successfully loaded wakeword model(s): " + f"{openwakeword_model_paths}" + ) + else: + self.owwModel = Model( + inference_framework=openwakeword_inference_framework) + + self.oww_n_models = len(self.owwModel.models.keys()) + if not self.oww_n_models: + logger.error( + "No wake word models loaded." + ) + + for model_key in self.owwModel.models.keys(): + logger.info( + "Successfully loaded openwakeword model: " + f"{model_key}" + ) + + except Exception as e: + logger.exception( + "Error initializing openwakeword " + f"wake word detection engine: {e}" + ) + raise + + logger.debug( + "Open wake word detection engine initialized successfully" + ) + + else: + logger.exception(f"Wakeword engine {self.wakeword_backend} unknown/unsupported or wake_words not specified. Please specify one of: pvporcupine, openwakeword.") + + + # Setup voice activity detection model WebRTC + try: + logger.info("Initializing WebRTC voice with " + f"Sensitivity {webrtc_sensitivity}" + ) + self.webrtc_vad_model = webrtcvad.Vad() + self.webrtc_vad_model.set_mode(webrtc_sensitivity) + + except Exception as e: + logger.exception("Error initializing WebRTC voice " + f"activity detection engine: {e}" + ) + raise + + logger.debug("WebRTC VAD voice activity detection " + "engine initialized successfully" + ) + + # Setup voice activity detection model Silero VAD + try: + self.silero_vad_model, _ = torch.hub.load( + repo_or_dir="snakers4/silero-vad", + model="silero_vad", + verbose=False, + onnx=silero_use_onnx + ) + + except Exception as e: + logger.exception(f"Error initializing Silero VAD " + f"voice activity detection engine: {e}" + ) + raise + + logger.debug("Silero VAD voice activity detection " + "engine initialized successfully" + ) + + self.audio_buffer = collections.deque( + maxlen=int((self.sample_rate // self.buffer_size) * + self.pre_recording_buffer_duration) + ) + self.last_words_buffer = collections.deque( + maxlen=int((self.sample_rate // self.buffer_size) * + 0.3) + ) + self.frames = [] + self.last_frames = [] + + # Recording control flags + self.is_recording = False + self.is_running = True + self.start_recording_on_voice_activity = False + self.stop_recording_on_voice_deactivity = False + + # Start the recording worker thread + self.recording_thread = threading.Thread(target=self._recording_worker) + self.recording_thread.daemon = True + self.recording_thread.start() + + # Start the realtime transcription worker thread + self.realtime_thread = threading.Thread(target=self._realtime_worker) + self.realtime_thread.daemon = True + self.realtime_thread.start() + + # Wait for transcription models to start + logger.debug('Waiting for main transcription model to start') + self.main_transcription_ready_event.wait() + logger.debug('Main transcription model ready') + + self.stdout_thread = threading.Thread(target=self._read_stdout) + self.stdout_thread.daemon = True + self.stdout_thread.start() + + logger.debug('RealtimeSTT initialization completed successfully') + + def _start_thread(self, target=None, args=()): + """ + Implement a consistent threading model across the library. + + This method is used to start any thread in this library. It uses the + standard threading. Thread for Linux and for all others uses the pytorch + MultiProcessing library 'Process'. + Args: + target (callable object): is the callable object to be invoked by + the run() method. Defaults to None, meaning nothing is called. + args (tuple): is a list or tuple of arguments for the target + invocation. Defaults to (). + """ + if (platform.system() == 'Linux'): + thread = threading.Thread(target=target, args=args) + thread.deamon = True + thread.start() + return thread + else: + thread = mp.Process(target=target, args=args) + thread.start() + return thread + + def _read_stdout(self): + while not self.shutdown_event.is_set(): + try: + if self.parent_stdout_pipe.poll(0.1): + logger.debug("Receive from stdout pipe") + message = self.parent_stdout_pipe.recv() + logger.info(message) + except (BrokenPipeError, EOFError, OSError): + # The pipe probably has been closed, so we ignore the error + pass + except KeyboardInterrupt: # handle manual interruption (Ctrl+C) + logger.info("KeyboardInterrupt in read from stdout detected, exiting...") + break + except Exception as e: + logger.error(f"Unexpected error in read from stdout: {e}", exc_info=True) + logger.error(traceback.format_exc()) # Log the full traceback here + break + time.sleep(0.1) + + def _transcription_worker(*args, **kwargs): + worker = TranscriptionWorker(*args, **kwargs) + worker.run() + + def _run_callback(self, cb, *args, **kwargs): + if self.start_callback_in_new_thread: + # Run the callback in a new thread to avoid blocking the main thread + threading.Thread(target=cb, args=args, kwargs=kwargs, daemon=True).start() + else: + # Run the callback in the main thread to avoid threading issues + cb(*args, **kwargs) + + @staticmethod + def _audio_data_worker( + audio_queue, + target_sample_rate, + buffer_size, + input_device_index, + shutdown_event, + interrupt_stop_event, + use_microphone + ): + """ + Worker method that handles the audio recording process. + + This method runs in a separate process and is responsible for: + - Setting up the audio input stream for recording at the highest possible sample rate. + - Continuously reading audio data from the input stream, resampling if necessary, + preprocessing the data, and placing complete chunks in a queue. + - Handling errors during the recording process. + - Gracefully terminating the recording process when a shutdown event is set. + + Args: + audio_queue (queue.Queue): A queue where recorded audio data is placed. + target_sample_rate (int): The desired sample rate for the output audio (for Silero VAD). + buffer_size (int): The number of samples expected by the Silero VAD model. + input_device_index (int): The index of the audio input device. + shutdown_event (threading.Event): An event that, when set, signals this worker method to terminate. + interrupt_stop_event (threading.Event): An event to signal keyboard interrupt. + use_microphone (multiprocessing.Value): A shared value indicating whether to use the microphone. + + Raises: + Exception: If there is an error while initializing the audio recording. + """ + import pyaudio + import numpy as np + from scipy import signal + + if __name__ == '__main__': + system_signal.signal(system_signal.SIGINT, system_signal.SIG_IGN) + + def get_highest_sample_rate(audio_interface, device_index): + """Get the highest supported sample rate for the specified device.""" + try: + device_info = audio_interface.get_device_info_by_index(device_index) + logger.debug(f"Retrieving highest sample rate for device index {device_index}: {device_info}") + max_rate = int(device_info['defaultSampleRate']) + + if 'supportedSampleRates' in device_info: + supported_rates = [int(rate) for rate in device_info['supportedSampleRates']] + if supported_rates: + max_rate = max(supported_rates) + + logger.debug(f"Highest supported sample rate for device index {device_index} is {max_rate}") + return max_rate + except Exception as e: + logger.warning(f"Failed to get highest sample rate: {e}") + return 48000 # Fallback to a common high sample rate + + def initialize_audio_stream(audio_interface, sample_rate, chunk_size): + nonlocal input_device_index + + def validate_device(device_index): + """Validate that the device exists and is actually available for input.""" + try: + device_info = audio_interface.get_device_info_by_index(device_index) + logger.debug(f"Validating device index {device_index} with info: {device_info}") + if not device_info.get('maxInputChannels', 0) > 0: + logger.debug("Device has no input channels, invalid for recording.") + return False + + # Try to actually read from the device + test_stream = audio_interface.open( + format=pyaudio.paInt16, + channels=1, + rate=target_sample_rate, + input=True, + frames_per_buffer=chunk_size, + input_device_index=device_index, + start=False # Don't start the stream yet + ) + + test_stream.start_stream() + test_data = test_stream.read(chunk_size, exception_on_overflow=False) + test_stream.stop_stream() + test_stream.close() + + if len(test_data) == 0: + logger.debug("Device produced no data, invalid for recording.") + return False + + logger.debug(f"Device index {device_index} successfully validated.") + return True + + except Exception as e: + logger.debug(f"Device validation failed for index {device_index}: {e}") + return False + + """Initialize the audio stream with error handling.""" + while not shutdown_event.is_set(): + try: + # First, get a list of all available input devices + input_devices = [] + device_count = audio_interface.get_device_count() + logger.debug(f"Found {device_count} total audio devices on the system.") + for i in range(device_count): + try: + device_info = audio_interface.get_device_info_by_index(i) + if device_info.get('maxInputChannels', 0) > 0: + input_devices.append(i) + except Exception as e: + logger.debug(f"Could not retrieve info for device index {i}: {e}") + continue + + logger.debug(f"Available input devices with input channels: {input_devices}") + if not input_devices: + raise Exception("No input devices found") + + # If input_device_index is None or invalid, try to find a working device + if input_device_index is None or input_device_index not in input_devices: + # First try the default device + try: + default_device = audio_interface.get_default_input_device_info() + logger.debug(f"Default device info: {default_device}") + if validate_device(default_device['index']): + input_device_index = default_device['index'] + logger.debug(f"Default device {input_device_index} selected.") + except Exception: + # If default device fails, try other available input devices + logger.debug("Default device validation failed, checking other devices...") + for device_index in input_devices: + if validate_device(device_index): + input_device_index = device_index + logger.debug(f"Device {input_device_index} selected.") + break + else: + raise Exception("No working input devices found") + + # Validate the selected device one final time + if not validate_device(input_device_index): + raise Exception("Selected device validation failed") + + # If we get here, we have a validated device + logger.debug(f"Opening stream with device index {input_device_index}, " + f"sample_rate={sample_rate}, chunk_size={chunk_size}") + stream = audio_interface.open( + format=pyaudio.paInt16, + channels=1, + rate=sample_rate, + input=True, + frames_per_buffer=chunk_size, + input_device_index=input_device_index, + ) + + logger.info(f"Microphone connected and validated (device index: {input_device_index}, " + f"sample rate: {sample_rate}, chunk size: {chunk_size})") + return stream + + except Exception as e: + logger.error(f"Microphone connection failed: {e}. Retrying...", exc_info=True) + input_device_index = None + time.sleep(3) # Wait before retrying + continue + + def preprocess_audio(chunk, original_sample_rate, target_sample_rate): + """Preprocess audio chunk similar to feed_audio method.""" + if isinstance(chunk, np.ndarray): + # Handle stereo to mono conversion if necessary + if chunk.ndim == 2: + chunk = np.mean(chunk, axis=1) + + # Resample to target_sample_rate if necessary + if original_sample_rate != target_sample_rate: + logger.debug(f"Resampling from {original_sample_rate} Hz to {target_sample_rate} Hz.") + num_samples = int(len(chunk) * target_sample_rate / original_sample_rate) + chunk = signal.resample(chunk, num_samples) + + chunk = chunk.astype(np.int16) + else: + # If chunk is bytes, convert to numpy array + chunk = np.frombuffer(chunk, dtype=np.int16) + + # Resample if necessary + if original_sample_rate != target_sample_rate: + logger.debug(f"Resampling from {original_sample_rate} Hz to {target_sample_rate} Hz.") + num_samples = int(len(chunk) * target_sample_rate / original_sample_rate) + chunk = signal.resample(chunk, num_samples) + chunk = chunk.astype(np.int16) + + return chunk.tobytes() + + audio_interface = None + stream = None + device_sample_rate = None + chunk_size = 1024 # Increased chunk size for better performance + + def setup_audio(): + nonlocal audio_interface, stream, device_sample_rate, input_device_index + try: + if audio_interface is None: + logger.debug("Creating PyAudio interface...") + audio_interface = pyaudio.PyAudio() + + if input_device_index is None: + try: + default_device = audio_interface.get_default_input_device_info() + input_device_index = default_device['index'] + logger.debug(f"No device index supplied; using default device {input_device_index}") + except OSError as e: + logger.debug(f"Default device retrieval failed: {e}") + input_device_index = None + + # We'll try 16000 Hz first, then the highest rate we detect, then fallback if needed + sample_rates_to_try = [16000] + if input_device_index is not None: + highest_rate = get_highest_sample_rate(audio_interface, input_device_index) + if highest_rate != 16000: + sample_rates_to_try.append(highest_rate) + else: + sample_rates_to_try.append(48000) + + logger.debug(f"Sample rates to try for device {input_device_index}: {sample_rates_to_try}") + + for rate in sample_rates_to_try: + try: + device_sample_rate = rate + logger.debug(f"Attempting to initialize audio stream at {device_sample_rate} Hz.") + stream = initialize_audio_stream(audio_interface, device_sample_rate, chunk_size) + if stream is not None: + logger.debug( + f"Audio recording initialized successfully at {device_sample_rate} Hz, " + f"reading {chunk_size} frames at a time" + ) + return True + except Exception as e: + logger.warning(f"Failed to initialize audio stream at {device_sample_rate} Hz: {e}") + continue + + # If we reach here, none of the sample rates worked + raise Exception("Failed to initialize audio stream with all sample rates.") + + except Exception as e: + logger.exception(f"Error initializing pyaudio audio recording: {e}") + if audio_interface: + audio_interface.terminate() + return False + + logger.debug(f"Starting audio data worker with target_sample_rate={target_sample_rate}, " + f"buffer_size={buffer_size}, input_device_index={input_device_index}") + + if not setup_audio(): + raise Exception("Failed to set up audio recording.") + + buffer = bytearray() + silero_buffer_size = 2 * buffer_size # Silero complains if too short + + time_since_last_buffer_message = 0 + + try: + while not shutdown_event.is_set(): + try: + data = stream.read(chunk_size, exception_on_overflow=False) + + if use_microphone.value: + processed_data = preprocess_audio(data, device_sample_rate, target_sample_rate) + buffer += processed_data + + # Check if the buffer has reached or exceeded the silero_buffer_size + while len(buffer) >= silero_buffer_size: + # Extract silero_buffer_size amount of data from the buffer + to_process = buffer[:silero_buffer_size] + buffer = buffer[silero_buffer_size:] + + # Feed the extracted data to the audio_queue + if time_since_last_buffer_message: + time_passed = time.time() - time_since_last_buffer_message + if time_passed > 1: + logger.debug("_audio_data_worker writing audio data into queue.") + time_since_last_buffer_message = time.time() + else: + time_since_last_buffer_message = time.time() + + audio_queue.put(to_process) + + except OSError as e: + if e.errno == pyaudio.paInputOverflowed: + logger.warning("Input overflowed. Frame dropped.") + else: + logger.error(f"OSError during recording: {e}", exc_info=True) + # Attempt to reinitialize the stream + logger.error("Attempting to reinitialize the audio stream...") + + try: + if stream: + stream.stop_stream() + stream.close() + except Exception: + pass + + time.sleep(1) + if not setup_audio(): + logger.error("Failed to reinitialize audio stream. Exiting.") + break + else: + logger.error("Audio stream reinitialized successfully.") + continue + + except Exception as e: + logger.error(f"Unknown error during recording: {e}") + tb_str = traceback.format_exc() + logger.error(f"Traceback: {tb_str}") + logger.error(f"Error: {e}") + # Attempt to reinitialize the stream + logger.info("Attempting to reinitialize the audio stream...") + try: + if stream: + stream.stop_stream() + stream.close() + except Exception: + pass + + time.sleep(1) + if not setup_audio(): + logger.error("Failed to reinitialize audio stream. Exiting.") + break + else: + logger.info("Audio stream reinitialized successfully.") + continue + + except KeyboardInterrupt: + interrupt_stop_event.set() + logger.debug("Audio data worker process finished due to KeyboardInterrupt") + finally: + # After recording stops, feed any remaining audio data + if buffer: + audio_queue.put(bytes(buffer)) + + try: + if stream: + stream.stop_stream() + stream.close() + except Exception: + pass + if audio_interface: + audio_interface.terminate() + + def wakeup(self): + """ + If in wake work modus, wake up as if a wake word was spoken. + """ + self.listen_start = time.time() + + def abort(self): + state = self.state + self.start_recording_on_voice_activity = False + self.stop_recording_on_voice_deactivity = False + self.interrupt_stop_event.set() + if self.state != "inactive": # if inactive, was_interrupted will never be set + self.was_interrupted.wait() + self._set_state("transcribing") + self.was_interrupted.clear() + if self.is_recording: # if recording, make sure to stop the recorder + self.stop() + + + def wait_audio(self): + """ + Waits for the start and completion of the audio recording process. + + This method is responsible for: + - Waiting for voice activity to begin recording if not yet started. + - Waiting for voice inactivity to complete the recording. + - Setting the audio buffer from the recorded frames. + - Resetting recording-related attributes. + + Side effects: + - Updates the state of the instance. + - Modifies the audio attribute to contain the processed audio data. + """ + + try: + logger.info("Setting listen time") + if self.listen_start == 0: + self.listen_start = time.time() + + # If not yet started recording, wait for voice activity to initiate. + if not self.is_recording and not self.frames: + self._set_state("listening") + self.start_recording_on_voice_activity = True + + # Wait until recording starts + logger.debug('Waiting for recording start') + while not self.interrupt_stop_event.is_set(): + if self.start_recording_event.wait(timeout=0.02): + break + + # If recording is ongoing, wait for voice inactivity + # to finish recording. + if self.is_recording: + self.stop_recording_on_voice_deactivity = True + + # Wait until recording stops + logger.debug('Waiting for recording stop') + while not self.interrupt_stop_event.is_set(): + if (self.stop_recording_event.wait(timeout=0.02)): + break + + frames = self.frames + if len(frames) == 0: + frames = self.last_frames + + # Calculate samples needed for backdating resume + samples_to_keep = int(self.sample_rate * self.backdate_resume_seconds) + + # First convert all current frames to audio array + full_audio_array = np.frombuffer(b''.join(frames), dtype=np.int16) + full_audio = full_audio_array.astype(np.float32) / INT16_MAX_ABS_VALUE + + # Calculate how many samples we need to keep for backdating resume + if samples_to_keep > 0: + samples_to_keep = min(samples_to_keep, len(full_audio)) + # Keep the last N samples for backdating resume + frames_to_read_audio = full_audio[-samples_to_keep:] + + # Convert the audio back to int16 bytes for frames + frames_to_read_int16 = (frames_to_read_audio * INT16_MAX_ABS_VALUE).astype(np.int16) + frame_bytes = frames_to_read_int16.tobytes() + + # Split into appropriate frame sizes (assuming standard frame size) + FRAME_SIZE = 2048 # Typical frame size + frames_to_read = [] + for i in range(0, len(frame_bytes), FRAME_SIZE): + frame = frame_bytes[i:i + FRAME_SIZE] + if frame: # Only add non-empty frames + frames_to_read.append(frame) + else: + frames_to_read = [] + + # Process backdate stop seconds + samples_to_remove = int(self.sample_rate * self.backdate_stop_seconds) + + if samples_to_remove > 0: + if samples_to_remove < len(full_audio): + self.audio = full_audio[:-samples_to_remove] + logger.debug(f"Removed {samples_to_remove} samples " + f"({samples_to_remove/self.sample_rate:.3f}s) from end of audio") + else: + self.audio = np.array([], dtype=np.float32) + logger.debug("Cleared audio (samples_to_remove >= audio length)") + else: + self.audio = full_audio + logger.debug(f"No samples removed, final audio length: {len(self.audio)}") + + self.frames.clear() + self.last_frames.clear() + self.frames.extend(frames_to_read) + + # Reset backdating parameters + self.backdate_stop_seconds = 0.0 + self.backdate_resume_seconds = 0.0 + + self.listen_start = 0 + + self._set_state("inactive") + + except KeyboardInterrupt: + logger.info("KeyboardInterrupt in wait_audio, shutting down") + self.shutdown() + raise # Re-raise the exception after cleanup + + + def perform_final_transcription(self, audio_bytes=None, use_prompt=True): + start_time = 0 + with self.transcription_lock: + if audio_bytes is None: + audio_bytes = copy.deepcopy(self.audio) + + if audio_bytes is None or len(audio_bytes) == 0: + print("No audio data available for transcription") + #logger.info("No audio data available for transcription") + return "" + + try: + if self.transcribe_count == 0: + logger.debug("Adding transcription request, no early transcription started") + start_time = time.time() # Start timing + self.parent_transcription_pipe.send((audio_bytes, self.language, use_prompt)) + self.transcribe_count += 1 + + while self.transcribe_count > 0: + logger.debug(F"Receive from parent_transcription_pipe after sendiung transcription request, transcribe_count: {self.transcribe_count}") + if not self.parent_transcription_pipe.poll(0.1): # check if transcription done + if self.interrupt_stop_event.is_set(): # check if interrupted + self.was_interrupted.set() + self._set_state("inactive") + return "" # return empty string if interrupted + continue + status, result = self.parent_transcription_pipe.recv() + self.transcribe_count -= 1 + + self.allowed_to_early_transcribe = True + self._set_state("inactive") + if status == 'success': + segments, info = result + self.detected_language = info.language if info.language_probability > 0 else None + self.detected_language_probability = info.language_probability + self.last_transcription_bytes = copy.deepcopy(audio_bytes) + self.last_transcription_bytes_b64 = base64.b64encode(self.last_transcription_bytes.tobytes()).decode('utf-8') + transcription = self._preprocess_output(segments) + end_time = time.time() # End timing + transcription_time = end_time - start_time + + if start_time: + if self.print_transcription_time: + print(f"Model {self.main_model_type} completed transcription in {transcription_time:.2f} seconds") + else: + logger.debug(f"Model {self.main_model_type} completed transcription in {transcription_time:.2f} seconds") + return "" if self.interrupt_stop_event.is_set() else transcription # if interrupted return empty string + else: + logger.error(f"Transcription error: {result}") + raise Exception(result) + except Exception as e: + logger.error(f"Error during transcription: {str(e)}", exc_info=True) + raise e + + + def transcribe(self): + """ + Transcribes audio captured by this class instance using the + `faster_whisper` model. + + Automatically starts recording upon voice activity if not manually + started using `recorder.start()`. + Automatically stops recording upon voice deactivity if not manually + stopped with `recorder.stop()`. + Processes the recorded audio to generate transcription. + + Args: + on_transcription_finished (callable, optional): Callback function + to be executed when transcription is ready. + If provided, transcription will be performed asynchronously, + and the callback will receive the transcription as its argument. + If omitted, the transcription will be performed synchronously, + and the result will be returned. + + Returns (if no callback is set): + str: The transcription of the recorded audio. + + Raises: + Exception: If there is an error during the transcription process. + """ + audio_copy = copy.deepcopy(self.audio) + self._set_state("transcribing") + if self.on_transcription_start: + abort_value = self.on_transcription_start(audio_copy) + if not abort_value: + return self.perform_final_transcription(audio_copy) + return None + else: + return self.perform_final_transcription(audio_copy) + + + def _process_wakeword(self, data): + """ + Processes audio data to detect wake words. + """ + if self.wakeword_backend in {'pvp', 'pvporcupine'}: + pcm = struct.unpack_from( + "h" * self.buffer_size, + data + ) + porcupine_index = self.porcupine.process(pcm) + if self.debug_mode: + logger.info(f"wake words porcupine_index: {porcupine_index}") + return porcupine_index + + elif self.wakeword_backend in {'oww', 'openwakeword', 'openwakewords'}: + pcm = np.frombuffer(data, dtype=np.int16) + prediction = self.owwModel.predict(pcm) + max_score = -1 + max_index = -1 + wake_words_in_prediction = len(self.owwModel.prediction_buffer.keys()) + self.wake_words_sensitivities + if wake_words_in_prediction: + for idx, mdl in enumerate(self.owwModel.prediction_buffer.keys()): + scores = list(self.owwModel.prediction_buffer[mdl]) + if scores[-1] >= self.wake_words_sensitivity and scores[-1] > max_score: + max_score = scores[-1] + max_index = idx + if self.debug_mode: + logger.info(f"wake words oww max_index, max_score: {max_index} {max_score}") + return max_index + else: + if self.debug_mode: + logger.info(f"wake words oww_index: -1") + return -1 + + if self.debug_mode: + logger.info("wake words no match") + + return -1 + + def text(self, + on_transcription_finished=None, + ): + """ + Transcribes audio captured by this class instance + using the `faster_whisper` model. + + - Automatically starts recording upon voice activity if not manually + started using `recorder.start()`. + - Automatically stops recording upon voice deactivity if not manually + stopped with `recorder.stop()`. + - Processes the recorded audio to generate transcription. + + Args: + on_transcription_finished (callable, optional): Callback function + to be executed when transcription is ready. + If provided, transcription will be performed asynchronously, and + the callback will receive the transcription as its argument. + If omitted, the transcription will be performed synchronously, + and the result will be returned. + + Returns (if not callback is set): + str: The transcription of the recorded audio + """ + self.interrupt_stop_event.clear() + self.was_interrupted.clear() + try: + self.wait_audio() + except KeyboardInterrupt: + logger.info("KeyboardInterrupt in text() method") + self.shutdown() + raise # Re-raise the exception after cleanup + + if self.is_shut_down or self.interrupt_stop_event.is_set(): + if self.interrupt_stop_event.is_set(): + self.was_interrupted.set() + return "" + + if on_transcription_finished: + threading.Thread(target=on_transcription_finished, + args=(self.transcribe(),)).start() + else: + return self.transcribe() + + + def format_number(self, num): + # Convert the number to a string + num_str = f"{num:.10f}" # Ensure precision is sufficient + # Split the number into integer and decimal parts + integer_part, decimal_part = num_str.split('.') + # Take the last two digits of the integer part and the first two digits of the decimal part + result = f"{integer_part[-2:]}.{decimal_part[:2]}" + return result + + def start(self, frames = None): + """ + Starts recording audio directly without waiting for voice activity. + """ + + # Ensure there's a minimum interval + # between stopping and starting recording + if (time.time() - self.recording_stop_time + < self.min_gap_between_recordings): + logger.info("Attempted to start recording " + "too soon after stopping." + ) + return self + + logger.info("recording started") + self._set_state("recording") + self.text_storage = [] + self.realtime_stabilized_text = "" + self.realtime_stabilized_safetext = "" + self.wakeword_detected = False + self.wake_word_detect_time = 0 + self.frames = [] + if frames: + self.frames = frames + self.is_recording = True + + self.recording_start_time = time.time() + self.is_silero_speech_active = False + self.is_webrtc_speech_active = False + self.stop_recording_event.clear() + self.start_recording_event.set() + + if self.on_recording_start: + self._run_callback(self.on_recording_start) + + return self + + def stop(self, + backdate_stop_seconds: float = 0.0, + backdate_resume_seconds: float = 0.0, + ): + """ + Stops recording audio. + + Args: + - backdate_stop_seconds (float, default="0.0"): Specifies the number of + seconds to backdate the stop time. This is useful when the stop + command is issued after the actual stop time. + - backdate_resume_seconds (float, default="0.0"): Specifies the number + of seconds to backdate the time relistening is initiated. + """ + + # Ensure there's a minimum interval + # between starting and stopping recording + if (time.time() - self.recording_start_time + < self.min_length_of_recording): + logger.info("Attempted to stop recording " + "too soon after starting." + ) + return self + + logger.info("recording stopped") + self.last_frames = copy.deepcopy(self.frames) + self.backdate_stop_seconds = backdate_stop_seconds + self.backdate_resume_seconds = backdate_resume_seconds + self.is_recording = False + self.recording_stop_time = time.time() + self.is_silero_speech_active = False + self.is_webrtc_speech_active = False + self.silero_check_time = 0 + self.start_recording_event.clear() + self.stop_recording_event.set() + + self.last_recording_start_time = self.recording_start_time + self.last_recording_stop_time = self.recording_stop_time + + if self.on_recording_stop: + self._run_callback(self.on_recording_stop) + + return self + + def listen(self): + """ + Puts recorder in immediate "listen" state. + This is the state after a wake word detection, for example. + The recorder now "listens" for voice activation. + Once voice is detected we enter "recording" state. + """ + self.listen_start = time.time() + self._set_state("listening") + self.start_recording_on_voice_activity = True + + def feed_audio(self, chunk, original_sample_rate=16000): + """ + Feed an audio chunk into the processing pipeline. Chunks are + accumulated until the buffer size is reached, and then the accumulated + data is fed into the audio_queue. + """ + # Check if the buffer attribute exists, if not, initialize it + if not hasattr(self, 'buffer'): + self.buffer = bytearray() + + # Check if input is a NumPy array + if isinstance(chunk, np.ndarray): + # Handle stereo to mono conversion if necessary + if chunk.ndim == 2: + chunk = np.mean(chunk, axis=1) + + # Resample to 16000 Hz if necessary + if original_sample_rate != 16000: + num_samples = int(len(chunk) * 16000 / original_sample_rate) + chunk = resample(chunk, num_samples) + + # Ensure data type is int16 + chunk = chunk.astype(np.int16) + + # Convert the NumPy array to bytes + chunk = chunk.tobytes() + + # Append the chunk to the buffer + self.buffer += chunk + buf_size = 2 * self.buffer_size # silero complains if too short + + # Check if the buffer has reached or exceeded the buffer_size + while len(self.buffer) >= buf_size: + # Extract self.buffer_size amount of data from the buffer + to_process = self.buffer[:buf_size] + self.buffer = self.buffer[buf_size:] + + # Feed the extracted data to the audio_queue + self.audio_queue.put(to_process) + + def set_microphone(self, microphone_on=True): + """ + Set the microphone on or off. + """ + logger.info("Setting microphone to: " + str(microphone_on)) + self.use_microphone.value = microphone_on + + def shutdown(self): + """ + Safely shuts down the audio recording by stopping the + recording worker and closing the audio stream. + """ + + with self.shutdown_lock: + if self.is_shut_down: + return + + print("\033[91mRealtimeSTT shutting down\033[0m") + + # Force wait_audio() and text() to exit + self.is_shut_down = True + self.start_recording_event.set() + self.stop_recording_event.set() + + self.shutdown_event.set() + self.is_recording = False + self.is_running = False + + logger.debug('Finishing recording thread') + if self.recording_thread: + self.recording_thread.join() + + logger.debug('Terminating reader process') + + # Give it some time to finish the loop and cleanup. + if self.use_microphone.value: + self.reader_process.join(timeout=10) + + if self.reader_process.is_alive(): + logger.warning("Reader process did not terminate " + "in time. Terminating forcefully." + ) + self.reader_process.terminate() + + logger.debug('Terminating transcription process') + self.transcript_process.join(timeout=10) + + if self.transcript_process.is_alive(): + logger.warning("Transcript process did not terminate " + "in time. Terminating forcefully." + ) + self.transcript_process.terminate() + + self.parent_transcription_pipe.close() + + logger.debug('Finishing realtime thread') + if self.realtime_thread: + self.realtime_thread.join() + + if self.enable_realtime_transcription: + if self.realtime_model_type: + del self.realtime_model_type + self.realtime_model_type = None + gc.collect() + + def _recording_worker(self): + """ + The main worker method which constantly monitors the audio + input for voice activity and accordingly starts/stops the recording. + """ + + if self.use_extended_logging: + logger.debug('Debug: Entering try block') + + last_inner_try_time = 0 + try: + if self.use_extended_logging: + logger.debug('Debug: Initializing variables') + time_since_last_buffer_message = 0 + was_recording = False + delay_was_passed = False + wakeword_detected_time = None + wakeword_samples_to_remove = None + self.allowed_to_early_transcribe = True + + if self.use_extended_logging: + logger.debug('Debug: Starting main loop') + # Continuously monitor audio for voice activity + while self.is_running: + + # if self.use_extended_logging: + # logger.debug('Debug: Entering inner try block') + if last_inner_try_time: + last_processing_time = time.time() - last_inner_try_time + if last_processing_time > 0.1: + if self.use_extended_logging: + logger.warning('### WARNING: PROCESSING TOOK TOO LONG') + last_inner_try_time = time.time() + try: + # if self.use_extended_logging: + # logger.debug('Debug: Trying to get data from audio queue') + try: + data = self.audio_queue.get(timeout=0.01) + self.last_words_buffer.append(data) + except queue.Empty: + # if self.use_extended_logging: + # logger.debug('Debug: Queue is empty, checking if still running') + if not self.is_running: + if self.use_extended_logging: + logger.debug('Debug: Not running, breaking loop') + break + # if self.use_extended_logging: + # logger.debug('Debug: Continuing to next iteration') + continue + + if self.use_extended_logging: + logger.debug('Debug: Checking for on_recorded_chunk callback') + if self.on_recorded_chunk: + if self.use_extended_logging: + logger.debug('Debug: Calling on_recorded_chunk') + self._run_callback(self.on_recorded_chunk, data) + + if self.use_extended_logging: + logger.debug('Debug: Checking if handle_buffer_overflow is True') + if self.handle_buffer_overflow: + if self.use_extended_logging: + logger.debug('Debug: Handling buffer overflow') + # Handle queue overflow + if (self.audio_queue.qsize() > + self.allowed_latency_limit): + if self.use_extended_logging: + logger.debug('Debug: Queue size exceeds limit, logging warnings') + logger.warning("Audio queue size exceeds " + "latency limit. Current size: " + f"{self.audio_queue.qsize()}. " + "Discarding old audio chunks." + ) + + if self.use_extended_logging: + logger.debug('Debug: Discarding old chunks if necessary') + while (self.audio_queue.qsize() > + self.allowed_latency_limit): + + data = self.audio_queue.get() + + except BrokenPipeError: + logger.error("BrokenPipeError _recording_worker", exc_info=True) + self.is_running = False + break + + if self.use_extended_logging: + logger.debug('Debug: Updating time_since_last_buffer_message') + # Feed the extracted data to the audio_queue + if time_since_last_buffer_message: + time_passed = time.time() - time_since_last_buffer_message + if time_passed > 1: + if self.use_extended_logging: + logger.debug("_recording_worker processing audio data") + time_since_last_buffer_message = time.time() + else: + time_since_last_buffer_message = time.time() + + if self.use_extended_logging: + logger.debug('Debug: Initializing failed_stop_attempt') + failed_stop_attempt = False + + if self.use_extended_logging: + logger.debug('Debug: Checking if not recording') + if not self.is_recording: + if self.use_extended_logging: + logger.debug('Debug: Handling not recording state') + # Handle not recording state + time_since_listen_start = (time.time() - self.listen_start + if self.listen_start else 0) + + wake_word_activation_delay_passed = ( + time_since_listen_start > + self.wake_word_activation_delay + ) + + if self.use_extended_logging: + logger.debug('Debug: Handling wake-word timeout callback') + # Handle wake-word timeout callback + if wake_word_activation_delay_passed \ + and not delay_was_passed: + + if self.use_wake_words and self.wake_word_activation_delay: + if self.on_wakeword_timeout: + if self.use_extended_logging: + logger.debug('Debug: Calling on_wakeword_timeout') + self._run_callback(self.on_wakeword_timeout) + delay_was_passed = wake_word_activation_delay_passed + + if self.use_extended_logging: + logger.debug('Debug: Setting state and spinner text') + # Set state and spinner text + if not self.recording_stop_time: + if self.use_wake_words \ + and wake_word_activation_delay_passed \ + and not self.wakeword_detected: + if self.use_extended_logging: + logger.debug('Debug: Setting state to "wakeword"') + self._set_state("wakeword") + else: + if self.listen_start: + if self.use_extended_logging: + logger.debug('Debug: Setting state to "listening"') + self._set_state("listening") + else: + if self.use_extended_logging: + logger.debug('Debug: Setting state to "inactive"') + self._set_state("inactive") + + if self.use_extended_logging: + logger.debug('Debug: Checking wake word conditions') + if self.use_wake_words and wake_word_activation_delay_passed: + try: + if self.use_extended_logging: + logger.debug('Debug: Processing wakeword') + wakeword_index = self._process_wakeword(data) + + except struct.error: + logger.error("Error unpacking audio data " + "for wake word processing.", exc_info=True) + continue + + except Exception as e: + logger.error(f"Wake word processing error: {e}", exc_info=True) + continue + + if self.use_extended_logging: + logger.debug('Debug: Checking if wake word detected') + # If a wake word is detected + if wakeword_index >= 0: + if self.use_extended_logging: + logger.debug('Debug: Wake word detected, updating variables') + self.wake_word_detect_time = time.time() + wakeword_detected_time = time.time() + wakeword_samples_to_remove = int(self.sample_rate * self.wake_word_buffer_duration) + self.wakeword_detected = True + if self.on_wakeword_detected: + if self.use_extended_logging: + logger.debug('Debug: Calling on_wakeword_detected') + self._run_callback(self.on_wakeword_detected) + + if self.use_extended_logging: + logger.debug('Debug: Checking voice activity conditions') + # Check for voice activity to + # trigger the start of recording + if ((not self.use_wake_words + or not wake_word_activation_delay_passed) + and self.start_recording_on_voice_activity) \ + or self.wakeword_detected: + + if self.use_extended_logging: + logger.debug('Debug: Checking if voice is active') + + if self._is_voice_active(): + + if self.on_vad_start: + self._run_callback(self.on_vad_start) + + if self.use_extended_logging: + logger.debug('Debug: Voice activity detected') + logger.info("voice activity detected") + + if self.use_extended_logging: + logger.debug('Debug: Starting recording') + self.start() + + self.start_recording_on_voice_activity = False + + if self.use_extended_logging: + logger.debug('Debug: Adding buffered audio to frames') + # Add the buffered audio + # to the recording frames + self.frames.extend(list(self.audio_buffer)) + self.audio_buffer.clear() + + if self.use_extended_logging: + logger.debug('Debug: Resetting Silero VAD model states') + self.silero_vad_model.reset_states() + else: + if self.use_extended_logging: + logger.debug('Debug: Checking voice activity') + data_copy = data[:] + self._check_voice_activity(data_copy) + + if self.use_extended_logging: + logger.debug('Debug: Resetting speech_end_silence_start') + + if self.speech_end_silence_start != 0: + self.speech_end_silence_start = 0 + if self.on_turn_detection_stop: + if self.use_extended_logging: + logger.debug('Debug: Calling on_turn_detection_stop') + self._run_callback(self.on_turn_detection_stop) + + else: + if self.use_extended_logging: + logger.debug('Debug: Handling recording state') + # If we are currently recording + if wakeword_samples_to_remove and wakeword_samples_to_remove > 0: + if self.use_extended_logging: + logger.debug('Debug: Removing wakeword samples') + # Remove samples from the beginning of self.frames + samples_removed = 0 + while wakeword_samples_to_remove > 0 and self.frames: + frame = self.frames[0] + frame_samples = len(frame) // 2 # Assuming 16-bit audio + if wakeword_samples_to_remove >= frame_samples: + self.frames.pop(0) + samples_removed += frame_samples + wakeword_samples_to_remove -= frame_samples + else: + self.frames[0] = frame[wakeword_samples_to_remove * 2:] + samples_removed += wakeword_samples_to_remove + samples_to_remove = 0 + + wakeword_samples_to_remove = 0 + + if self.use_extended_logging: + logger.debug('Debug: Checking if stop_recording_on_voice_deactivity is True') + # Stop the recording if silence is detected after speech + if self.stop_recording_on_voice_deactivity: + if self.use_extended_logging: + logger.debug('Debug: Determining if speech is detected') + is_speech = ( + self._is_silero_speech(data) if self.silero_deactivity_detection + else self._is_webrtc_speech(data, True) + ) + + if self.use_extended_logging: + logger.debug('Debug: Formatting speech_end_silence_start') + if not self.speech_end_silence_start: + str_speech_end_silence_start = "0" + else: + str_speech_end_silence_start = datetime.datetime.fromtimestamp(self.speech_end_silence_start).strftime('%H:%M:%S.%f')[:-3] + if self.use_extended_logging: + logger.debug(f"is_speech: {is_speech}, str_speech_end_silence_start: {str_speech_end_silence_start}") + + if self.use_extended_logging: + logger.debug('Debug: Checking if speech is not detected') + if not is_speech: + if self.use_extended_logging: + logger.debug('Debug: Handling voice deactivity') + # Voice deactivity was detected, so we start + # measuring silence time before stopping recording + if self.speech_end_silence_start == 0 and \ + (time.time() - self.recording_start_time > self.min_length_of_recording): + + self.speech_end_silence_start = time.time() + self.awaiting_speech_end = True + if self.on_turn_detection_start: + if self.use_extended_logging: + logger.debug('Debug: Calling on_turn_detection_start') + + self._run_callback(self.on_turn_detection_start) + + if self.use_extended_logging: + logger.debug('Debug: Checking early transcription conditions') + if self.speech_end_silence_start and self.early_transcription_on_silence and len(self.frames) > 0 and \ + (time.time() - self.speech_end_silence_start > self.early_transcription_on_silence) and \ + self.allowed_to_early_transcribe: + if self.use_extended_logging: + logger.debug("Debug:Adding early transcription request") + self.transcribe_count += 1 + audio_array = np.frombuffer(b''.join(self.frames), dtype=np.int16) + audio = audio_array.astype(np.float32) / INT16_MAX_ABS_VALUE + + if self.use_extended_logging: + logger.debug("Debug: early transcription request pipe send") + self.parent_transcription_pipe.send((audio, self.language, True)) + if self.use_extended_logging: + logger.debug("Debug: early transcription request pipe send return") + self.allowed_to_early_transcribe = False + + else: + self.awaiting_speech_end = False + if self.use_extended_logging: + logger.debug('Debug: Handling speech detection') + if self.speech_end_silence_start: + if self.use_extended_logging: + logger.info("Resetting self.speech_end_silence_start") + + if self.speech_end_silence_start != 0: + self.speech_end_silence_start = 0 + if self.on_turn_detection_stop: + if self.use_extended_logging: + logger.debug('Debug: Calling on_turn_detection_stop') + self._run_callback(self.on_turn_detection_stop) + + self.allowed_to_early_transcribe = True + + if self.use_extended_logging: + logger.debug('Debug: Checking if silence duration exceeds threshold') + # Wait for silence to stop recording after speech + if self.speech_end_silence_start and time.time() - \ + self.speech_end_silence_start >= \ + self.post_speech_silence_duration: + + if self.on_vad_stop: + self._run_callback(self.on_vad_stop) + + if self.use_extended_logging: + logger.debug('Debug: Formatting silence start time') + # Get time in desired format (HH:MM:SS.nnn) + silence_start_time = datetime.datetime.fromtimestamp(self.speech_end_silence_start).strftime('%H:%M:%S.%f')[:-3] + + if self.use_extended_logging: + logger.debug('Debug: Calculating time difference') + # Calculate time difference + time_diff = time.time() - self.speech_end_silence_start + + if self.use_extended_logging: + logger.debug('Debug: Logging voice deactivity detection') + logger.info(f"voice deactivity detected at {silence_start_time}, " + f"time since silence start: {time_diff:.3f} seconds") + + logger.debug('Debug: Appending data to frames and stopping recording') + self.frames.append(data) + self.stop() + if not self.is_recording: + if self.speech_end_silence_start != 0: + self.speech_end_silence_start = 0 + if self.on_turn_detection_stop: + if self.use_extended_logging: + logger.debug('Debug: Calling on_turn_detection_stop') + self._run_callback(self.on_turn_detection_stop) + + if self.use_extended_logging: + logger.debug('Debug: Handling non-wake word scenario') + else: + if self.use_extended_logging: + logger.debug('Debug: Setting failed_stop_attempt to True') + failed_stop_attempt = True + + self.awaiting_speech_end = False + + if self.use_extended_logging: + logger.debug('Debug: Checking if recording stopped') + if not self.is_recording and was_recording: + if self.use_extended_logging: + logger.debug('Debug: Resetting after stopping recording') + # Reset after stopping recording to ensure clean state + self.stop_recording_on_voice_deactivity = False + + if self.use_extended_logging: + logger.debug('Debug: Checking Silero time') + if time.time() - self.silero_check_time > 0.1: + self.silero_check_time = 0 + + if self.use_extended_logging: + logger.debug('Debug: Handling wake word timeout') + # Handle wake word timeout (waited to long initiating + # speech after wake word detection) + if self.wake_word_detect_time and time.time() - \ + self.wake_word_detect_time > self.wake_word_timeout: + + self.wake_word_detect_time = 0 + if self.wakeword_detected and self.on_wakeword_timeout: + if self.use_extended_logging: + logger.debug('Debug: Calling on_wakeword_timeout') + self._run_callback(self.on_wakeword_timeout) + self.wakeword_detected = False + + if self.use_extended_logging: + logger.debug('Debug: Updating was_recording') + was_recording = self.is_recording + + if self.use_extended_logging: + logger.debug('Debug: Checking if recording and not failed stop attempt') + if self.is_recording and not failed_stop_attempt: + if self.use_extended_logging: + logger.debug('Debug: Appending data to frames') + self.frames.append(data) + + if self.use_extended_logging: + logger.debug('Debug: Checking if not recording or speech end silence start') + if not self.is_recording or self.speech_end_silence_start: + if self.use_extended_logging: + logger.debug('Debug: Appending data to audio buffer') + self.audio_buffer.append(data) + + except Exception as e: + logger.debug('Debug: Caught exception in main try block') + if not self.interrupt_stop_event.is_set(): + logger.error(f"Unhandled exeption in _recording_worker: {e}", exc_info=True) + raise + + if self.use_extended_logging: + logger.debug('Debug: Exiting _recording_worker method') + + + + + def _realtime_worker(self): + """ + Performs real-time transcription if the feature is enabled. + + The method is responsible transcribing recorded audio frames + in real-time based on the specified resolution interval. + The transcribed text is stored in `self.realtime_transcription_text` + and a callback + function is invoked with this text if specified. + """ + + try: + + logger.debug('Starting realtime worker') + + # Return immediately if real-time transcription is not enabled + if not self.enable_realtime_transcription: + return + + # Track time of last transcription + last_transcription_time = time.time() + + while self.is_running: + + if self.is_recording: + + # MODIFIED SLEEP LOGIC: + # Wait until realtime_processing_pause has elapsed, + # but check often so we can respond to changes quickly. + while ( + time.time() - last_transcription_time + ) < self.realtime_processing_pause: + time.sleep(0.001) + if not self.is_running or not self.is_recording: + break + + if self.awaiting_speech_end: + time.sleep(0.001) + continue + + # Update transcription time + last_transcription_time = time.time() + + # Convert the buffer frames to a NumPy array + audio_array = np.frombuffer( + b''.join(self.frames), + dtype=np.int16 + ) + + logger.debug(f"Current realtime buffer size: {len(audio_array)}") + + # Normalize the array to a [-1, 1] range + audio_array = audio_array.astype(np.float32) / \ + INT16_MAX_ABS_VALUE + + if self.use_main_model_for_realtime: + with self.transcription_lock: + try: + self.parent_transcription_pipe.send((audio_array, self.language, True)) + if self.parent_transcription_pipe.poll(timeout=5): # Wait for 5 seconds + logger.debug("Receive from realtime worker after transcription request to main model") + status, result = self.parent_transcription_pipe.recv() + if status == 'success': + segments, info = result + self.detected_realtime_language = info.language if info.language_probability > 0 else None + self.detected_realtime_language_probability = info.language_probability + realtime_text = segments + logger.debug(f"Realtime text detected with main model: {realtime_text}") + else: + logger.error(f"Realtime transcription error: {result}") + continue + else: + logger.warning("Realtime transcription timed out") + continue + except Exception as e: + logger.error(f"Error in realtime transcription: {str(e)}", exc_info=True) + continue + else: + # Perform transcription and assemble the text + if self.normalize_audio: + # normalize audio to -0.95 dBFS + if audio_array is not None and audio_array.size > 0: + peak = np.max(np.abs(audio_array)) + if peak > 0: + audio_array = (audio_array / peak) * 0.95 + + if self.realtime_batch_size > 0: + segments, info = self.realtime_model_type.transcribe( + audio_array, + language=self.language if self.language else None, + beam_size=self.beam_size_realtime, + initial_prompt=self.initial_prompt_realtime, + suppress_tokens=self.suppress_tokens, + batch_size=self.realtime_batch_size, + vad_filter=self.faster_whisper_vad_filter + ) + else: + segments, info = self.realtime_model_type.transcribe( + audio_array, + language=self.language if self.language else None, + beam_size=self.beam_size_realtime, + initial_prompt=self.initial_prompt_realtime, + suppress_tokens=self.suppress_tokens, + vad_filter=self.faster_whisper_vad_filter + ) + + self.detected_realtime_language = info.language if info.language_probability > 0 else None + self.detected_realtime_language_probability = info.language_probability + realtime_text = " ".join( + seg.text for seg in segments + ) + logger.debug(f"Realtime text detected: {realtime_text}") + + # double check recording state + # because it could have changed mid-transcription + if self.is_recording and time.time() - \ + self.recording_start_time > self.init_realtime_after_seconds: + + self.realtime_transcription_text = realtime_text + self.realtime_transcription_text = \ + self.realtime_transcription_text.strip() + + self.text_storage.append( + self.realtime_transcription_text + ) + + # Take the last two texts in storage, if they exist + if len(self.text_storage) >= 2: + last_two_texts = self.text_storage[-2:] + + # Find the longest common prefix + # between the two texts + prefix = os.path.commonprefix( + [last_two_texts[0], last_two_texts[1]] + ) + + # This prefix is the text that was transcripted + # two times in the same way + # Store as "safely detected text" + if len(prefix) >= \ + len(self.realtime_stabilized_safetext): + + # Only store when longer than the previous + # as additional security + self.realtime_stabilized_safetext = prefix + + # Find parts of the stabilized text + # in the freshly transcripted text + matching_pos = self._find_tail_match_in_text( + self.realtime_stabilized_safetext, + self.realtime_transcription_text + ) + + if matching_pos < 0: + # pick which text to send + text_to_send = ( + self.realtime_stabilized_safetext + if self.realtime_stabilized_safetext + else self.realtime_transcription_text + ) + # preprocess once + processed = self._preprocess_output(text_to_send, True) + # invoke on its own thread + self._run_callback(self._on_realtime_transcription_stabilized, processed) + + else: + # We found parts of the stabilized text + # in the transcripted text + # We now take the stabilized text + # and add only the freshly transcripted part to it + output_text = self.realtime_stabilized_safetext + \ + self.realtime_transcription_text[matching_pos:] + + # This yields us the "left" text part as stabilized + # AND at the same time delivers fresh detected + # parts on the first run without the need for + # two transcriptions + self._run_callback(self._on_realtime_transcription_stabilized, self._preprocess_output(output_text, True)) + + # Invoke the callback with the transcribed text + self._run_callback(self._on_realtime_transcription_update, self._preprocess_output(self.realtime_transcription_text,True)) + + # If not recording, sleep briefly before checking again + else: + time.sleep(TIME_SLEEP) + + except Exception as e: + logger.error(f"Unhandled exeption in _realtime_worker: {e}", exc_info=True) + raise + + def _is_silero_speech(self, chunk): + """ + Returns true if speech is detected in the provided audio data + + Args: + data (bytes): raw bytes of audio data (1024 raw bytes with + 16000 sample rate and 16 bits per sample) + """ + if self.sample_rate != 16000: + pcm_data = np.frombuffer(chunk, dtype=np.int16) + data_16000 = signal.resample_poly( + pcm_data, 16000, self.sample_rate) + chunk = data_16000.astype(np.int16).tobytes() + + self.silero_working = True + audio_chunk = np.frombuffer(chunk, dtype=np.int16) + audio_chunk = audio_chunk.astype(np.float32) / INT16_MAX_ABS_VALUE + vad_prob = self.silero_vad_model( + torch.from_numpy(audio_chunk), + SAMPLE_RATE).item() + is_silero_speech_active = vad_prob > (1 - self.silero_sensitivity) + if is_silero_speech_active: + if not self.is_silero_speech_active and self.use_extended_logging: + logger.info(f"{bcolors.OKGREEN}Silero VAD detected speech{bcolors.ENDC}") + elif self.is_silero_speech_active and self.use_extended_logging: + logger.info(f"{bcolors.WARNING}Silero VAD detected silence{bcolors.ENDC}") + self.is_silero_speech_active = is_silero_speech_active + self.silero_working = False + return is_silero_speech_active + + def _is_webrtc_speech(self, chunk, all_frames_must_be_true=False): + """ + Returns true if speech is detected in the provided audio data + + Args: + data (bytes): raw bytes of audio data (1024 raw bytes with + 16000 sample rate and 16 bits per sample) + """ + speech_str = f"{bcolors.OKGREEN}WebRTC VAD detected speech{bcolors.ENDC}" + silence_str = f"{bcolors.WARNING}WebRTC VAD detected silence{bcolors.ENDC}" + if self.sample_rate != 16000: + pcm_data = np.frombuffer(chunk, dtype=np.int16) + data_16000 = signal.resample_poly( + pcm_data, 16000, self.sample_rate) + chunk = data_16000.astype(np.int16).tobytes() + + # Number of audio frames per millisecond + frame_length = int(16000 * 0.01) # for 10ms frame + num_frames = int(len(chunk) / (2 * frame_length)) + speech_frames = 0 + + for i in range(num_frames): + start_byte = i * frame_length * 2 + end_byte = start_byte + frame_length * 2 + frame = chunk[start_byte:end_byte] + if self.webrtc_vad_model.is_speech(frame, 16000): + speech_frames += 1 + if not all_frames_must_be_true: + if self.debug_mode: + logger.info(f"Speech detected in frame {i + 1}" + f" of {num_frames}") + if not self.is_webrtc_speech_active and self.use_extended_logging: + logger.info(speech_str) + self.is_webrtc_speech_active = True + return True + if all_frames_must_be_true: + if self.debug_mode and speech_frames == num_frames: + logger.info(f"Speech detected in {speech_frames} of " + f"{num_frames} frames") + elif self.debug_mode: + logger.info(f"Speech not detected in all {num_frames} frames") + speech_detected = speech_frames == num_frames + if speech_detected and not self.is_webrtc_speech_active and self.use_extended_logging: + logger.info(speech_str) + elif not speech_detected and self.is_webrtc_speech_active and self.use_extended_logging: + logger.info(silence_str) + self.is_webrtc_speech_active = speech_detected + return speech_detected + else: + if self.debug_mode: + logger.info(f"Speech not detected in any of {num_frames} frames") + if self.is_webrtc_speech_active and self.use_extended_logging: + logger.info(silence_str) + self.is_webrtc_speech_active = False + return False + + def _check_voice_activity(self, data): + """ + Initiate check if voice is active based on the provided data. + + Args: + data: The audio data to be checked for voice activity. + """ + self._is_webrtc_speech(data) + + # First quick performing check for voice activity using WebRTC + if self.is_webrtc_speech_active: + + if not self.silero_working: + self.silero_working = True + + # Run the intensive check in a separate thread + threading.Thread( + target=self._is_silero_speech, + args=(data,)).start() + + def clear_audio_queue(self): + """ + Safely empties the audio queue to ensure no remaining audio + fragments get processed e.g. after waking up the recorder. + """ + self.audio_buffer.clear() + try: + while True: + self.audio_queue.get_nowait() + except: + # PyTorch's mp.Queue doesn't have a specific Empty exception + # so we catch any exception that might occur when the queue is empty + pass + + def _is_voice_active(self): + """ + Determine if voice is active. + + Returns: + bool: True if voice is active, False otherwise. + """ + return self.is_webrtc_speech_active and self.is_silero_speech_active + + def _set_state(self, new_state): + """ + Update the current state of the recorder and execute + corresponding state-change callbacks. + + Args: + new_state (str): The new state to set. + + """ + # Check if the state has actually changed + if new_state == self.state: + return + + # Store the current state for later comparison + old_state = self.state + + # Update to the new state + self.state = new_state + + # Log the state change + logger.info(f"State changed from '{old_state}' to '{new_state}'") + + # Execute callbacks based on transitioning FROM a particular state + if old_state == "listening": + if self.on_vad_detect_stop: + self._run_callback(self.on_vad_detect_stop) + elif old_state == "wakeword": + if self.on_wakeword_detection_end: + self._run_callback(self.on_wakeword_detection_end) + + # Execute callbacks based on transitioning TO a particular state + if new_state == "listening": + if self.on_vad_detect_start: + self._run_callback(self.on_vad_detect_start) + self._set_spinner("speak now") + if self.spinner and self.halo: + self.halo._interval = 250 + elif new_state == "wakeword": + if self.on_wakeword_detection_start: + self._run_callback(self.on_wakeword_detection_start) + self._set_spinner(f"say {self.wake_words}") + if self.spinner and self.halo: + self.halo._interval = 500 + elif new_state == "transcribing": + self._set_spinner("transcribing") + if self.spinner and self.halo: + self.halo._interval = 50 + elif new_state == "recording": + self._set_spinner("recording") + if self.spinner and self.halo: + self.halo._interval = 100 + elif new_state == "inactive": + if self.spinner and self.halo: + self.halo.stop() + self.halo = None + + def _set_spinner(self, text): + """ + Update the spinner's text or create a new + spinner with the provided text. + + Args: + text (str): The text to be displayed alongside the spinner. + """ + if self.spinner: + # If the Halo spinner doesn't exist, create and start it + if self.halo is None: + self.halo = halo.Halo(text=text) + self.halo.start() + # If the Halo spinner already exists, just update the text + else: + self.halo.text = text + + def _preprocess_output(self, text, preview=False): + """ + Preprocesses the output text by removing any leading or trailing + whitespace, converting all whitespace sequences to a single space + character, and capitalizing the first character of the text. + + Args: + text (str): The text to be preprocessed. + + Returns: + str: The preprocessed text. + """ + text = re.sub(r'\s+', ' ', text.strip()) + + if self.ensure_sentence_starting_uppercase: + if text: + text = text[0].upper() + text[1:] + + # Ensure the text ends with a proper punctuation + # if it ends with an alphanumeric character + if not preview: + if self.ensure_sentence_ends_with_period: + if text and text[-1].isalnum(): + text += '.' + + return text + + def _find_tail_match_in_text(self, text1, text2, length_of_match=10): + """ + Find the position where the last 'n' characters of text1 + match with a substring in text2. + + This method takes two texts, extracts the last 'n' characters from + text1 (where 'n' is determined by the variable 'length_of_match'), and + searches for an occurrence of this substring in text2, starting from + the end of text2 and moving towards the beginning. + + Parameters: + - text1 (str): The text containing the substring that we want to find + in text2. + - text2 (str): The text in which we want to find the matching + substring. + - length_of_match(int): The length of the matching string that we are + looking for + + Returns: + int: The position (0-based index) in text2 where the matching + substring starts. If no match is found or either of the texts is + too short, returns -1. + """ + + # Check if either of the texts is too short + if len(text1) < length_of_match or len(text2) < length_of_match: + return -1 + + # The end portion of the first text that we want to compare + target_substring = text1[-length_of_match:] + + # Loop through text2 from right to left + for i in range(len(text2) - length_of_match + 1): + # Extract the substring from text2 + # to compare with the target_substring + current_substring = text2[len(text2) - i - length_of_match: + len(text2) - i] + + # Compare the current_substring with the target_substring + if current_substring == target_substring: + # Position in text2 where the match starts + return len(text2) - i + + return -1 + + def _on_realtime_transcription_stabilized(self, text): + """ + Callback method invoked when the real-time transcription stabilizes. + + This method is called internally when the transcription text is + considered "stable" meaning it's less likely to change significantly + with additional audio input. It notifies any registered external + listener about the stabilized text if recording is still ongoing. + This is particularly useful for applications that need to display + live transcription results to users and want to highlight parts of the + transcription that are less likely to change. + + Args: + text (str): The stabilized transcription text. + """ + if self.on_realtime_transcription_stabilized: + if self.is_recording: + self._run_callback(self.on_realtime_transcription_stabilized, text) + + def _on_realtime_transcription_update(self, text): + """ + Callback method invoked when there's an update in the real-time + transcription. + + This method is called internally whenever there's a change in the + transcription text, notifying any registered external listener about + the update if recording is still ongoing. This provides a mechanism + for applications to receive and possibly display live transcription + updates, which could be partial and still subject to change. + + Args: + text (str): The updated transcription text. + """ + if self.on_realtime_transcription_update: + if self.is_recording: + self._run_callback(self.on_realtime_transcription_update, text) + + def __enter__(self): + """ + Method to setup the context manager protocol. + + This enables the instance to be used in a `with` statement, ensuring + proper resource management. When the `with` block is entered, this + method is automatically called. + + Returns: + self: The current instance of the class. + """ + return self + + def __exit__(self, exc_type, exc_value, traceback): + """ + Method to define behavior when the context manager protocol exits. + + This is called when exiting the `with` block and ensures that any + necessary cleanup or resource release processes are executed, such as + shutting down the system properly. + + Args: + exc_type (Exception or None): The type of the exception that + caused the context to be exited, if any. + exc_value (Exception or None): The exception instance that caused + the context to be exited, if any. + traceback (Traceback or None): The traceback corresponding to the + exception, if any. + """ + self.shutdown() \ No newline at end of file diff --git a/minimal_server/RealtimeSTT/audio_recorder_client.py b/minimal_server/RealtimeSTT/audio_recorder_client.py new file mode 100644 index 00000000..89478c82 --- /dev/null +++ b/minimal_server/RealtimeSTT/audio_recorder_client.py @@ -0,0 +1,881 @@ +log_outgoing_chunks = False +debug_mode = False + +from typing import Iterable, List, Optional, Union +from urllib.parse import urlparse +from datetime import datetime +from websocket import WebSocketApp +from websocket import ABNF +import numpy as np +import subprocess +import threading +import platform +import logging +import struct +import base64 +import wave +import json +import time +import sys +import os + +# Import the AudioInput class +from .audio_input import AudioInput + +DEFAULT_CONTROL_URL = "ws://127.0.0.1:8011" +DEFAULT_DATA_URL = "ws://127.0.0.1:8012" + +INIT_MODEL_TRANSCRIPTION = "tiny" +INIT_MODEL_TRANSCRIPTION_REALTIME = "tiny" +INIT_REALTIME_PROCESSING_PAUSE = 0.2 +INIT_REALTIME_INITIAL_PAUSE = 0.2 +INIT_SILERO_SENSITIVITY = 0.4 +INIT_WEBRTC_SENSITIVITY = 3 +INIT_POST_SPEECH_SILENCE_DURATION = 0.6 +INIT_MIN_LENGTH_OF_RECORDING = 0.5 +INIT_MIN_GAP_BETWEEN_RECORDINGS = 0 +INIT_WAKE_WORDS_SENSITIVITY = 0.6 +INIT_PRE_RECORDING_BUFFER_DURATION = 1.0 +INIT_WAKE_WORD_ACTIVATION_DELAY = 0.0 +INIT_WAKE_WORD_TIMEOUT = 5.0 +INIT_WAKE_WORD_BUFFER_DURATION = 0.1 +ALLOWED_LATENCY_LIMIT = 100 + +BUFFER_SIZE = 512 +SAMPLE_RATE = 16000 + +INIT_HANDLE_BUFFER_OVERFLOW = False +if platform.system() != 'Darwin': + INIT_HANDLE_BUFFER_OVERFLOW = True + +# Define ANSI color codes for terminal output +class bcolors: + HEADER = '\033[95m' # Magenta + OKBLUE = '\033[94m' # Blue + OKCYAN = '\033[96m' # Cyan + OKGREEN = '\033[92m' # Green + WARNING = '\033[93m' # Yellow + FAIL = '\033[91m' # Red + ENDC = '\033[0m' # Reset to default + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +def format_timestamp_ns(timestamp_ns: int) -> str: + # Split into whole seconds and the nanosecond remainder + seconds = timestamp_ns // 1_000_000_000 + remainder_ns = timestamp_ns % 1_000_000_000 + + # Convert seconds part into a datetime object (local time) + dt = datetime.fromtimestamp(seconds) + + # Format the main time as HH:MM:SS + time_str = dt.strftime("%H:%M:%S") + + # For instance, if you want milliseconds, divide the remainder by 1e6 and format as 3-digit + milliseconds = remainder_ns // 1_000_000 + formatted_timestamp = f"{time_str}.{milliseconds:03d}" + + return formatted_timestamp + +class AudioToTextRecorderClient: + """ + A class responsible for capturing audio from the microphone, detecting + voice activity, and then transcribing the captured audio using the + `faster_whisper` model. + """ + + def __init__(self, + model: str = INIT_MODEL_TRANSCRIPTION, + download_root: str = None, + language: str = "", + compute_type: str = "default", + input_device_index: int = None, + gpu_device_index: Union[int, List[int]] = 0, + device: str = "cuda", + on_recording_start=None, + on_recording_stop=None, + on_transcription_start=None, + ensure_sentence_starting_uppercase=True, + ensure_sentence_ends_with_period=True, + use_microphone=True, + spinner=True, + level=logging.WARNING, + batch_size: int = 16, + + # Realtime transcription parameters + enable_realtime_transcription=False, + use_main_model_for_realtime=False, + realtime_model_type=INIT_MODEL_TRANSCRIPTION_REALTIME, + realtime_processing_pause=INIT_REALTIME_PROCESSING_PAUSE, + init_realtime_after_seconds=INIT_REALTIME_INITIAL_PAUSE, + on_realtime_transcription_update=None, + on_realtime_transcription_stabilized=None, + realtime_batch_size: int = 16, + + # Voice activation parameters + silero_sensitivity: float = INIT_SILERO_SENSITIVITY, + silero_use_onnx: bool = False, + silero_deactivity_detection: bool = False, + webrtc_sensitivity: int = INIT_WEBRTC_SENSITIVITY, + post_speech_silence_duration: float = ( + INIT_POST_SPEECH_SILENCE_DURATION + ), + min_length_of_recording: float = ( + INIT_MIN_LENGTH_OF_RECORDING + ), + min_gap_between_recordings: float = ( + INIT_MIN_GAP_BETWEEN_RECORDINGS + ), + pre_recording_buffer_duration: float = ( + INIT_PRE_RECORDING_BUFFER_DURATION + ), + on_vad_start=None, + on_vad_stop=None, + on_vad_detect_start=None, + on_vad_detect_stop=None, + on_turn_detection_start=None, + on_turn_detection_stop=None, + + # Wake word parameters + wakeword_backend: str = "pvporcupine", + openwakeword_model_paths: str = None, + openwakeword_inference_framework: str = "onnx", + wake_words: str = "", + wake_words_sensitivity: float = INIT_WAKE_WORDS_SENSITIVITY, + wake_word_activation_delay: float = ( + INIT_WAKE_WORD_ACTIVATION_DELAY + ), + wake_word_timeout: float = INIT_WAKE_WORD_TIMEOUT, + wake_word_buffer_duration: float = INIT_WAKE_WORD_BUFFER_DURATION, + on_wakeword_detected=None, + on_wakeword_timeout=None, + on_wakeword_detection_start=None, + on_wakeword_detection_end=None, + on_recorded_chunk=None, + debug_mode=False, + handle_buffer_overflow: bool = INIT_HANDLE_BUFFER_OVERFLOW, + beam_size: int = 5, + beam_size_realtime: int = 3, + buffer_size: int = BUFFER_SIZE, + sample_rate: int = SAMPLE_RATE, + initial_prompt: Optional[Union[str, Iterable[int]]] = None, + initial_prompt_realtime: Optional[Union[str, Iterable[int]]] = None, + suppress_tokens: Optional[List[int]] = [-1], + print_transcription_time: bool = False, + early_transcription_on_silence: int = 0, + allowed_latency_limit: int = ALLOWED_LATENCY_LIMIT, + no_log_file: bool = False, + use_extended_logging: bool = False, + + # Server urls + control_url: str = DEFAULT_CONTROL_URL, + data_url: str = DEFAULT_DATA_URL, + autostart_server: bool = True, + output_wav_file: str = None, + faster_whisper_vad_filter: bool = False, + ): + + # Set instance variables from constructor parameters + self.model = model + self.language = language + self.compute_type = compute_type + self.input_device_index = input_device_index + self.gpu_device_index = gpu_device_index + self.device = device + self.on_recording_start = on_recording_start + self.on_recording_stop = on_recording_stop + self.on_transcription_start = on_transcription_start + self.ensure_sentence_starting_uppercase = ensure_sentence_starting_uppercase + self.ensure_sentence_ends_with_period = ensure_sentence_ends_with_period + self.use_microphone = use_microphone + self.spinner = spinner + self.level = level + self.batch_size = batch_size + self.init_realtime_after_seconds = init_realtime_after_seconds + self.realtime_batch_size = realtime_batch_size + + # Real-time transcription parameters + self.enable_realtime_transcription = enable_realtime_transcription + self.use_main_model_for_realtime = use_main_model_for_realtime + self.download_root = download_root + self.realtime_model_type = realtime_model_type + self.realtime_processing_pause = realtime_processing_pause + self.on_realtime_transcription_update = on_realtime_transcription_update + self.on_realtime_transcription_stabilized = on_realtime_transcription_stabilized + + # Voice activation parameters + self.silero_sensitivity = silero_sensitivity + self.silero_use_onnx = silero_use_onnx + self.silero_deactivity_detection = silero_deactivity_detection + self.webrtc_sensitivity = webrtc_sensitivity + self.post_speech_silence_duration = post_speech_silence_duration + self.min_length_of_recording = min_length_of_recording + self.min_gap_between_recordings = min_gap_between_recordings + self.pre_recording_buffer_duration = pre_recording_buffer_duration + + self.on_vad_start = on_vad_start + self.on_vad_stop = on_vad_stop + self.on_vad_detect_start = on_vad_detect_start + self.on_vad_detect_stop = on_vad_detect_stop + self.on_turn_detection_start = on_turn_detection_start + self.on_turn_detection_stop = on_turn_detection_stop + + # Wake word parameters + self.wakeword_backend = wakeword_backend + self.openwakeword_model_paths = openwakeword_model_paths + self.openwakeword_inference_framework = openwakeword_inference_framework + self.wake_words = wake_words + self.wake_words_sensitivity = wake_words_sensitivity + self.wake_word_activation_delay = wake_word_activation_delay + self.wake_word_timeout = wake_word_timeout + self.wake_word_buffer_duration = wake_word_buffer_duration + self.on_wakeword_detected = on_wakeword_detected + self.on_wakeword_timeout = on_wakeword_timeout + self.on_wakeword_detection_start = on_wakeword_detection_start + self.on_wakeword_detection_end = on_wakeword_detection_end + self.on_recorded_chunk = on_recorded_chunk + self.debug_mode = debug_mode + self.handle_buffer_overflow = handle_buffer_overflow + self.beam_size = beam_size + self.beam_size_realtime = beam_size_realtime + self.buffer_size = buffer_size + self.sample_rate = sample_rate + self.initial_prompt = initial_prompt + self.initial_prompt_realtime = initial_prompt_realtime + self.suppress_tokens = suppress_tokens + self.print_transcription_time = print_transcription_time + self.early_transcription_on_silence = early_transcription_on_silence + self.allowed_latency_limit = allowed_latency_limit + self.no_log_file = no_log_file + self.use_extended_logging = use_extended_logging + self.faster_whisper_vad_filter = faster_whisper_vad_filter + + # Server URLs + self.control_url = control_url + self.data_url = data_url + self.autostart_server = autostart_server + self.output_wav_file = output_wav_file + + # Instance variables + self.muted = False + self.recording_thread = None + self.is_running = True + self.connection_established = threading.Event() + self.recording_start = threading.Event() + self.final_text_ready = threading.Event() + self.realtime_text = "" + self.final_text = "" + self._recording = False + self.server_already_running = False + self.wav_file = None + + self.request_counter = 0 + self.pending_requests = {} # Map from request_id to threading.Event and value + + if self.debug_mode: + print("Checking STT server") + if not self.connect(): + print("Failed to connect to the server.", file=sys.stderr) + else: + if self.debug_mode: + print("STT server is running and connected.") + + if self.use_microphone: + self.start_recording() + + + if self.server_already_running: + if not self.connection_established.wait(timeout=10): + print("Server connection not established within 10 seconds.") + else: + self.set_parameter("language", self.language) + print(f"Language set to {self.language}") + self.set_parameter("wake_word_activation_delay", self.wake_word_activation_delay) + print(f"Wake word activation delay set to {self.wake_word_activation_delay}") + + def text(self, on_transcription_finished=None): + self.realtime_text = "" + self.submitted_realtime_text = "" + self.final_text = "" + self.final_text_ready.clear() + + self.recording_start.set() + + try: + total_wait_time = 0 + wait_interval = 0.02 # Wait in small intervals, e.g., 100ms + max_wait_time = 60 # Timeout after 60 seconds + + while total_wait_time < max_wait_time and self.is_running and self._recording: + if self.final_text_ready.wait(timeout=wait_interval): + break # Break if transcription is ready + + if not self.is_running or not self._recording: + break + + total_wait_time += wait_interval + + # Check if a manual interrupt has occurred + if total_wait_time >= max_wait_time: + if self.debug_mode: + print("Timeout while waiting for text from the server.") + self.recording_start.clear() + if on_transcription_finished: + threading.Thread(target=on_transcription_finished, args=("",)).start() + return "" + + self.recording_start.clear() + + if not self.is_running or not self._recording: + return "" + + if on_transcription_finished: + threading.Thread(target=on_transcription_finished, args=(self.final_text,)).start() + + return self.final_text + + except KeyboardInterrupt: + if self.debug_mode: + print("KeyboardInterrupt in text(), exiting...") + raise KeyboardInterrupt + + except Exception as e: + print(f"Error in AudioToTextRecorderClient.text(): {e}") + return "" + + def feed_audio(self, chunk, audio_meta_data, original_sample_rate=16000): + # Start with the base metadata + metadata = {"sampleRate": original_sample_rate} + + # Merge additional metadata if provided + if audio_meta_data: + server_sent_to_stt_ns = time.time_ns() + audio_meta_data["server_sent_to_stt"] = server_sent_to_stt_ns + metadata["server_sent_to_stt_formatted"] = format_timestamp_ns(server_sent_to_stt_ns) + + metadata.update(audio_meta_data) + + # Convert metadata to JSON and prepare the message + metadata_json = json.dumps(metadata) + metadata_length = len(metadata_json) + message = struct.pack(' %s", self.name, data) + self._pipe.send(data) + request["result_queue"].put(None) + + elif request["type"] == "RECV": + logger.debug("[%s] Worker: receiving...", self.name) + data = self._pipe.recv() + request["result_queue"].put(data) + + elif request["type"] == "POLL": + timeout = request.get("timeout", 0.0) + logger.debug("[%s] Worker: poll() with timeout: %s", self.name, timeout) + result = self._pipe.poll(timeout) + request["result_queue"].put(result) + + except (EOFError, BrokenPipeError, OSError) as e: + # When the other end has closed or an error occurs, + # log and notify the waiting thread. + logger.debug("[%s] Worker: pipe closed or error occurred (%s). Shutting down.", self.name, e) + request["result_queue"].put(None) + break + + except Exception as e: + logger.exception("[%s] Worker: unexpected error.", self.name) + request["result_queue"].put(e) + break + + logger.debug("[%s] Worker: stopping.", self.name) + try: + self._pipe.close() + except Exception as e: + logger.debug("[%s] Worker: error during pipe close: %s", self.name, e) + + def send(self, data): + """ + Synchronously asks the worker thread to perform .send(). + """ + if self._closed: + logger.debug("[%s] send() called but pipe is already closed", self.name) + return + logger.debug("[%s] send() requested with: %s", self.name, data) + result_queue = queue.Queue() + request = { + "type": "SEND", + "data": data, + "result_queue": result_queue + } + self._request_queue.put(request) + result_queue.get() # Wait until sending completes. + logger.debug("[%s] send() completed", self.name) + + def recv(self): + """ + Synchronously asks the worker to perform .recv() and returns the data. + """ + if self._closed: + logger.debug("[%s] recv() called but pipe is already closed", self.name) + return None + logger.debug("[%s] recv() requested", self.name) + result_queue = queue.Queue() + request = { + "type": "RECV", + "result_queue": result_queue + } + self._request_queue.put(request) + data = result_queue.get() + + # Log a preview for huge byte blobs. + if isinstance(data, tuple) and len(data) == 2 and isinstance(data[1], bytes): + data_preview = (data[0], f"<{len(data[1])} bytes>") + else: + data_preview = data + logger.debug("[%s] recv() returning => %s", self.name, data_preview) + return data + + def poll(self, timeout=0.0): + """ + Synchronously checks whether data is available. + Returns True if data is ready, or False otherwise. + """ + if self._closed: + return False + logger.debug("[%s] poll() requested with timeout: %s", self.name, timeout) + result_queue = queue.Queue() + request = { + "type": "POLL", + "timeout": timeout, + "result_queue": result_queue + } + self._request_queue.put(request) + try: + # Use a slightly longer timeout to give the worker a chance. + result = result_queue.get(timeout=timeout + 0.1) + except queue.Empty: + result = False + logger.debug("[%s] poll() returning => %s", self.name, result) + return result + + def close(self): + """ + Closes the pipe and stops the worker thread. The _closed flag makes + sure no further operations are attempted. + """ + if self._closed: + return + logger.debug("[%s] close() called", self.name) + self._closed = True + stop_request = {"type": "CLOSE", "result_queue": queue.Queue()} + self._request_queue.put(stop_request) + self._stop_event.set() + self._worker_thread.join() + logger.debug("[%s] closed", self.name) + + +def SafePipe(debug=False): + """ + Returns a pair: (thread-safe parent pipe, raw child pipe). + """ + parent_synthesize_pipe, child_synthesize_pipe = mp.Pipe() + parent_pipe = ParentPipe(parent_synthesize_pipe) + return parent_pipe, child_synthesize_pipe + + +def child_process_code(child_end): + """ + Example child process code that receives messages, logs them, + sends acknowledgements, and then closes. + """ + for i in range(3): + msg = child_end.recv() + logger.debug("[Child] got: %s", msg) + child_end.send(f"ACK: {msg}") + child_end.close() + + +if __name__ == "__main__": + parent_pipe, child_pipe = SafePipe() + + # Create child process with the child_process_code function. + p = mp.Process(target=child_process_code, args=(child_pipe,)) + p.start() + + # Event to signal sender threads to stop if needed. + stop_polling_event = threading.Event() + + def sender_thread(n): + try: + parent_pipe.send(f"hello_from_thread_{n}") + except Exception as e: + logger.debug("[sender_thread_%s] send exception: %s", n, e) + return + + # Use a poll loop with error handling. + for _ in range(10): + try: + if parent_pipe.poll(0.1): + reply = parent_pipe.recv() + logger.debug("[sender_thread_%s] got: %s", n, reply) + break + else: + logger.debug("[sender_thread_%s] no data yet...", n) + except (OSError, EOFError, BrokenPipeError) as e: + logger.debug("[sender_thread_%s] poll/recv exception: %s. Exiting thread.", n, e) + break + + # Allow exit if a shutdown is signaled. + if stop_polling_event.is_set(): + logger.debug("[sender_thread_%s] stop event set. Exiting thread.", n) + break + + threads = [] + for i in range(3): + t = threading.Thread(target=sender_thread, args=(i,)) + t.start() + threads.append(t) + + for t in threads: + t.join() + + # Signal shutdown to any polling threads, then close the pipe. + stop_polling_event.set() + parent_pipe.close() + p.join() diff --git a/minimal_server/RealtimeSTT/server.py b/minimal_server/RealtimeSTT/server.py new file mode 100644 index 00000000..516e10c4 --- /dev/null +++ b/minimal_server/RealtimeSTT/server.py @@ -0,0 +1,23 @@ +from fastapi import FastAPI, WebSocket +from RealtimeSTT.audio_recorder import AudioToTextRecorder +import numpy as np + +app = FastAPI() + +recorder = AudioToTextRecorder( + model="tiny", + device="cuda", + compute_type="float16", + use_microphone=False, +) + +@app.websocket("/ws/transcribe") +async def websocket_endpoint(websocket: WebSocket): + await websocket.accept() + while True: + data = await websocket.receive_bytes() + # Convierte los bytes a numpy array (ajusta según tu formato) + audio = np.frombuffer(data, dtype=np.float32) + recorder.feed_audio(audio) + text = recorder.text() + await websocket.send_text(text) \ No newline at end of file diff --git a/minimal_server/RealtimeSTT/warmup_audio.wav b/minimal_server/RealtimeSTT/warmup_audio.wav new file mode 100644 index 0000000000000000000000000000000000000000..31458f5e691356588764220bf402ddb671b5a4eb GIT binary patch literal 51870 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 z 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 N`QJMH_v%jP{{cC}%{%}A literal 0 HcmV?d00001 diff --git a/minimal_server/__pycache__/install_packages.cpython-311.pyc b/minimal_server/__pycache__/install_packages.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5402f01e132a88d14fcef9272adf896ed880c5b5 GIT binary patch literal 2990 zcmb_dOK9BI8a|R{Jg?XzJF;i&*v@H_>&e)YNpX7#_00pfwQu7pcF)_kZU<(!V-8;|SW*&&HPKV+j3+OoB$&2QQApU;~LrqzdSU|Dt+MDcu4FNyB0&MQ8TO! zn9(hAwg(zPYKFf<_x7yV#{ z1ws>EmW?G@@$&VMJ?lH9$>ZME;zsHSE9t9cL6CHPpLvczN`+7SchlU$JfWL+_{JS zc5bG|GC&{CB@--*<+S6p_Nit(6vraO* zef3H54J-LZjb^*?^UUZ!zuYmtzxMEgHF9bF1IWFGnBKG1bmnO~|0JE?$yui^JdA*V zZl`CQ^o-^8XMx7HX{XOP=`&XP%yVwo;j-JOY;MBgCM<5^`Ot_n^w!SD_Ryp=G`T)q zO<~}t_s!k((XEc{n4KPX(&JWoe3$FD2BvmqLHOO~rX6nD^7`H`v7)b-QOneCe{;s|F8WOj*FP~L+N-$q@RP*`hgvpJWc;_dUEjZUEv?Ocek55_#MghLy)h!m#ofkHYw;PNI_yX#?OyOA(PIf|TsP(F>4B z`wNJd(KG(FI;Uk}O5QU1c@Q;*qNpnBwt`+o@vppJ6|q*(YoT!pz#bIZ3Gnedn6U_$ literal 0 HcmV?d00001 diff --git a/minimal_server/__pycache__/stt_server.cpython-311.pyc b/minimal_server/__pycache__/stt_server.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6359bdc0afc16c1471d20b5af916bf0c2a198a7 GIT binary patch literal 55970 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 literal 0 HcmV?d00001 diff --git a/minimal_server/server/__pycache__/install_packages.cpython-311.pyc b/minimal_server/server/__pycache__/install_packages.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23155347323abde6e7c6c9ee308e9f77b67b9b11 GIT binary patch literal 2997 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 literal 0 HcmV?d00001 diff --git a/minimal_server/server/install_packages.py b/minimal_server/server/install_packages.py new file mode 100644 index 00000000..9b5a9b16 --- /dev/null +++ b/minimal_server/server/install_packages.py @@ -0,0 +1,55 @@ +import subprocess +import sys +import importlib + +def check_and_install_packages(packages): + """ + Checks if the specified packages are installed, and if not, prompts the user + to install them. + + Parameters: + - packages: A list of dictionaries, each containing: + - 'module_name': The module or package name to import. + - 'attribute': (Optional) The attribute or class to check within the module. + - 'install_name': The name used in the pip install command. + - 'version': (Optional) Version constraint for the package. + """ + for package in packages: + module_name = package['module_name'] + attribute = package.get('attribute') + install_name = package.get('install_name', module_name) + version = package.get('version', '') + + try: + # Attempt to import the module + module = importlib.import_module(module_name) + # If an attribute is specified, check if it exists + if attribute: + getattr(module, attribute) + except (ImportError, AttributeError): + user_input = input( + f"This program requires '{module_name}'" + f"{'' if not attribute else ' with attribute ' + attribute}, which is not installed or missing.\n" + f"Do you want to install '{install_name}' now? (y/n): " + ) + if user_input.strip().lower() == 'y': + try: + # Build the pip install command + install_command = [sys.executable, "-m", "pip", "install"] + if version: + install_command.append(f"{install_name}{version}") + else: + install_command.append(install_name) + + subprocess.check_call(install_command) + # Try to import again after installation + module = importlib.import_module(module_name) + if attribute: + getattr(module, attribute) + print(f"Successfully installed '{install_name}'.") + except Exception as e: + print(f"An error occurred while installing '{install_name}': {e}") + sys.exit(1) + else: + print(f"The program requires '{install_name}' to run. Exiting...") + sys.exit(1) diff --git a/minimal_server/server/stt_server.py b/minimal_server/server/stt_server.py new file mode 100644 index 00000000..880ccac4 --- /dev/null +++ b/minimal_server/server/stt_server.py @@ -0,0 +1,913 @@ +""" +Speech-to-Text (STT) Server with Real-Time Transcription and WebSocket Interface + +This server provides real-time speech-to-text (STT) transcription using the RealtimeSTT library. It allows clients to connect via WebSocket to send audio data and receive real-time transcription updates. The server supports configurable audio recording parameters, voice activity detection (VAD), and wake word detection. It is designed to handle continuous transcription as well as post-recording processing, enabling real-time feedback with the option to improve final transcription quality after the complete sentence is recognized. + +### Features: +- Real-time transcription using pre-configured or user-defined STT models. +- WebSocket-based communication for control and data handling. +- Flexible recording and transcription options, including configurable pauses for sentence detection. +- Supports Silero and WebRTC VAD for robust voice activity detection. + +### Starting the Server: +You can start the server using the command-line interface (CLI) command `stt-server`, passing the desired configuration options. + +```bash +stt-server [OPTIONS] +``` + +### Available Parameters: + - `-m, --model`: Model path or size; default 'large-v2'. + - `-r, --rt-model, --realtime_model_type`: Real-time model size; default 'tiny'. + - `-l, --lang, --language`: Language code for transcription; default 'es'. + - `-i, --input-device, --input_device_index`: Audio input device index; default 1. + - `-c, --control, --control_port`: WebSocket control port; default 8011. + - `-d, --data, --data_port`: WebSocket data port; default 8012. + - `-w, --wake_words`: Wake word(s) to trigger listening; default "". + - `-D, --debug`: Enable debug logging. + - `-W, --write`: Save audio to WAV file. + - `-s, --silence_timing`: Enable dynamic silence duration for sentence detection; default True. + - `-b, --batch, --batch_size`: Batch size for inference; default 16. + - `--root, --download_root`: Specifies the root path were the Whisper models are downloaded to. + - `--silero_sensitivity`: Silero VAD sensitivity (0-1); default 0.05. + - `--silero_use_onnx`: Use Silero ONNX model; default False. + - `--webrtc_sensitivity`: WebRTC VAD sensitivity (0-3); default 3. + - `--min_length_of_recording`: Minimum recording duration in seconds; default 1.1. + - `--min_gap_between_recordings`: Min time between recordings in seconds; default 0. + - `--enable_realtime_transcription`: Enable real-time transcription; default True. + - `--realtime_processing_pause`: Pause between audio chunk processing; default 0.02. + - `--silero_deactivity_detection`: Use Silero for end-of-speech detection; default True. + - `--early_transcription_on_silence`: Start transcription after silence in seconds; default 0.2. + - `--beam_size`: Beam size for main model; default 5. + - `--beam_size_realtime`: Beam size for real-time model; default 3. + - `--init_realtime_after_seconds`: Initial waiting time for realtime transcription; default 0.2. + - `--realtime_batch_size`: Batch size for the real-time transcription model; default 16. + - `--initial_prompt`: Initial main transcription guidance prompt. + - `--initial_prompt_realtime`: Initial realtime transcription guidance prompt. + - `--end_of_sentence_detection_pause`: Silence duration for sentence end detection; default 0.5. + - `--unknown_sentence_detection_pause`: Pause duration for incomplete sentence detection; default 0.5. + - `--mid_sentence_detection_pause`: Pause for mid-sentence break; default 0.5. + - `--wake_words_sensitivity`: Wake word detection sensitivity (0-1); default 0.5. + - `--wake_word_timeout`: Wake word timeout in seconds; default 5.0. + - `--wake_word_activation_delay`: Delay before wake word activation; default 20. + - `--wakeword_backend`: Backend for wake word detection; default 'none'. + - `--openwakeword_model_paths`: Paths to OpenWakeWord models. + - `--openwakeword_inference_framework`: OpenWakeWord inference framework; default 'tensorflow'. + - `--wake_word_buffer_duration`: Wake word buffer duration in seconds; default 1.0. + - `--use_main_model_for_realtime`: Use main model for real-time transcription. + - `--use_extended_logging`: Enable extensive log messages. + - `--logchunks`: Log incoming audio chunks. + - `--compute_type`: Type of computation to use. + - `--input_device_index`: Index of the audio input device. + - `--gpu_device_index`: Index of the GPU device. + - `--device`: Device to use for computation. + - `--handle_buffer_overflow`: Handle buffer overflow during transcription. + - `--suppress_tokens`: Suppress tokens during transcription. + - `--allowed_latency_limit`: Allowed latency limit for real-time transcription. + - `--faster_whisper_vad_filter`: Enable VAD filter for Faster Whisper; default False. + + +### WebSocket Interface: +The server supports two WebSocket connections: +1. **Control WebSocket**: Used to send and receive commands, such as setting parameters or calling recorder methods. +2. **Data WebSocket**: Used to send audio data for transcription and receive real-time transcription updates. + +The server will broadcast real-time transcription updates to all connected clients on the data WebSocket. +""" + +from .install_packages import check_and_install_packages +from difflib import SequenceMatcher +from collections import deque +from datetime import datetime +import logging +import asyncio +import pyaudio +import base64 +import sys + + +debug_logging = False +extended_logging = False +send_recorded_chunk = False +log_incoming_chunks = False +silence_timing = False +writechunks = False +wav_file = None + +hard_break_even_on_background_noise = 3.0 +hard_break_even_on_background_noise_min_texts = 3 +hard_break_even_on_background_noise_min_similarity = 0.99 +hard_break_even_on_background_noise_min_chars = 15 + + +text_time_deque = deque() +loglevel = logging.WARNING + +FORMAT = pyaudio.paInt16 +CHANNELS = 1 + + +if sys.platform == 'win32': + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + + +check_and_install_packages([ + { + 'module_name': 'RealtimeSTT', # Import module + 'attribute': 'AudioToTextRecorder', # Specific class to check + 'install_name': 'RealtimeSTT', # Package name for pip install + }, + { + 'module_name': 'websockets', # Import module + 'install_name': 'websockets', # Package name for pip install + }, + { + 'module_name': 'numpy', # Import module + 'install_name': 'numpy', # Package name for pip install + }, + { + 'module_name': 'scipy.signal', # Submodule of scipy + 'attribute': 'resample', # Specific function to check + 'install_name': 'scipy', # Package name for pip install + } +]) + +# Define ANSI color codes for terminal output +class bcolors: + HEADER = '\033[95m' # Magenta + OKBLUE = '\033[94m' # Blue + OKCYAN = '\033[96m' # Cyan + OKGREEN = '\033[92m' # Green + WARNING = '\033[93m' # Yellow + FAIL = '\033[91m' # Red + ENDC = '\033[0m' # Reset to default + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + +print(f"{bcolors.BOLD}{bcolors.OKCYAN}Starting server, please wait...{bcolors.ENDC}") + +# Initialize colorama +from colorama import init, Fore, Style +init() + +from RealtimeSTT import AudioToTextRecorder +from scipy.signal import resample +import numpy as np +import websockets +import threading +import logging +import wave +import json +import time + +global_args = None +recorder = None +recorder_config = {} +recorder_ready = threading.Event() +recorder_thread = None +stop_recorder = False +prev_text = "" + +# Define allowed methods and parameters for security +allowed_methods = [ + 'set_microphone', + 'abort', + 'stop', + 'clear_audio_queue', + 'wakeup', + 'shutdown', + 'text', +] +allowed_parameters = [ + 'language', + 'silero_sensitivity', + 'wake_word_activation_delay', + 'post_speech_silence_duration', + 'listen_start', + 'recording_stop_time', + 'last_transcription_bytes', + 'last_transcription_bytes_b64', + 'speech_end_silence_start', + 'is_recording', + 'use_wake_words', +] + +# Queues and connections for control and data +control_connections = set() +data_connections = set() +control_queue = asyncio.Queue() +audio_queue = asyncio.Queue() + +def preprocess_text(text): + # Remove leading whitespaces + text = text.lstrip() + + # Remove starting ellipses if present + if text.startswith("..."): + text = text[3:] + + if text.endswith("...'."): + text = text[:-1] + + if text.endswith("...'"): + text = text[:-1] + + # Remove any leading whitespaces again after ellipses removal + text = text.lstrip() + + # Uppercase the first letter + if text: + text = text[0].upper() + text[1:] + + return text + +def debug_print(message): + if debug_logging: + timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + thread_name = threading.current_thread().name + print(f"{Fore.CYAN}[DEBUG][{timestamp}][{thread_name}] {message}{Style.RESET_ALL}", file=sys.stderr) + +def format_timestamp_ns(timestamp_ns: int) -> str: + # Split into whole seconds and the nanosecond remainder + seconds = timestamp_ns // 1_000_000_000 + remainder_ns = timestamp_ns % 1_000_000_000 + + # Convert seconds part into a datetime object (local time) + dt = datetime.fromtimestamp(seconds) + + # Format the main time as HH:MM:SS + time_str = dt.strftime("%H:%M:%S") + + # For instance, if you want milliseconds, divide the remainder by 1e6 and format as 3-digit + milliseconds = remainder_ns // 1_000_000 + formatted_timestamp = f"{time_str}.{milliseconds:03d}" + + return formatted_timestamp + +def text_detected(text, loop): + global prev_text + + text = preprocess_text(text) + + # if silence_timing: + # def ends_with_ellipsis(text: str): + # if text.endswith("..."): + # return True + # if len(text) > 1 and text[:-1].endswith("..."): + # return True + # return False + + # def sentence_end(text: str): + # sentence_end_marks = ['.', '!', '?', '。'] + # if text and text[-1] in sentence_end_marks: + # return True + # return False + + + # if ends_with_ellipsis(text): + # recorder.post_speech_silence_duration = global_args.mid_sentence_detection_pause + # elif sentence_end(text) and sentence_end(prev_text) and not ends_with_ellipsis(prev_text): + # recorder.post_speech_silence_duration = global_args.end_of_sentence_detection_pause + # else: + # recorder.post_speech_silence_duration = global_args.unknown_sentence_detection_pause + + + # # Append the new text with its timestamp + # current_time = time.time() + # text_time_deque.append((current_time, text)) + + # # Remove texts older than hard_break_even_on_background_noise seconds + # while text_time_deque and text_time_deque[0][0] < current_time - hard_break_even_on_background_noise: + # text_time_deque.popleft() + + # # Check if at least hard_break_even_on_background_noise_min_texts texts have arrived within the last hard_break_even_on_background_noise seconds + # if len(text_time_deque) >= hard_break_even_on_background_noise_min_texts: + # texts = [t[1] for t in text_time_deque] + # first_text = texts[0] + # last_text = texts[-1] + + # # Compute the similarity ratio between the first and last texts + # similarity = SequenceMatcher(None, first_text, last_text).ratio() + + # if similarity > hard_break_even_on_background_noise_min_similarity and len(first_text) > hard_break_even_on_background_noise_min_chars: + # recorder.stop() + # recorder.clear_audio_queue() + # prev_text = "" + + prev_text = text + + # Put the message in the audio queue to be sent to clients + message = json.dumps({ + 'type': 'realtime', + 'text': text + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + + # Get current timestamp in HH:MM:SS.nnn format + timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3] + + if extended_logging: + print(f" [{timestamp}] Realtime text: {bcolors.OKCYAN}{text}{bcolors.ENDC}\n", flush=True, end="") + else: + print(f"\r[{timestamp}] {bcolors.OKCYAN}{text}{bcolors.ENDC}", flush=True, end='') + +def on_recording_start(loop): + message = json.dumps({ + 'type': 'recording_start' + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +def on_recording_stop(loop): + message = json.dumps({ + 'type': 'recording_stop' + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +def on_vad_detect_start(loop): + message = json.dumps({ + 'type': 'vad_detect_start' + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +def on_vad_detect_stop(loop): + message = json.dumps({ + 'type': 'vad_detect_stop' + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +def on_wakeword_detected(loop): + message = json.dumps({ + 'type': 'wakeword_detected' + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +def on_wakeword_detection_start(loop): + message = json.dumps({ + 'type': 'wakeword_detection_start' + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +def on_wakeword_detection_end(loop): + message = json.dumps({ + 'type': 'wakeword_detection_end' + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +def on_transcription_start(_audio_bytes, loop): + bytes_b64 = base64.b64encode(_audio_bytes.tobytes()).decode('utf-8') + message = json.dumps({ + 'type': 'transcription_start', + 'audio_bytes_base64': bytes_b64 + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +def on_turn_detection_start(loop): + print("&&& stt_server on_turn_detection_start") + message = json.dumps({ + 'type': 'start_turn_detection' + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +def on_turn_detection_stop(loop): + # print("&&& stt_server on_turn_detection_stop") + # message = json.dumps({ + # 'type': 'stop_turn_detection' + # }) + # asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + pass + + +# def on_realtime_transcription_update(text, loop): +# # Send real-time transcription updates to the client +# text = preprocess_text(text) +# message = json.dumps({ +# 'type': 'realtime_update', +# 'text': text +# }) +# asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +# def on_recorded_chunk(chunk, loop): +# if send_recorded_chunk: +# bytes_b64 = base64.b64encode(chunk.tobytes()).decode('utf-8') +# message = json.dumps({ +# 'type': 'recorded_chunk', +# 'bytes': bytes_b64 +# }) +# asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + +# Define the server's arguments +def parse_arguments(): + global debug_logging, extended_logging, loglevel, writechunks, log_incoming_chunks, dynamic_silence_timing + + import argparse + parser = argparse.ArgumentParser(description='Start the Speech-to-Text (STT) server with various configuration options.') + + parser.add_argument('-m', '--model', type=str, default='large-v2', + help='Path to the STT model or model size. Options include: tiny, tiny.en, base, base.en, small, small.en, medium, medium.en, large-v1, large-v2, or any huggingface CTranslate2 STT model such as deepdml/faster-whisper-large-v3-turbo-ct2. Default is large-v2.') + + parser.add_argument('-r', '--rt-model', '--realtime_model_type', type=str, default='tiny', + help='Model size for real-time transcription. Options same as --model. This is used only if real-time transcription is enabled (enable_realtime_transcription). Default is tiny.en.') + + parser.add_argument('-l', '--lang', '--language', type=str, default='es', + help='Language code for the STT model to transcribe in a specific language. Leave this empty for auto-detection based on input audio. Default is en. List of supported language codes: https://github.com/openai/whisper/blob/main/whisper/tokenizer.py#L11-L110') + + parser.add_argument('-i', '--input-device', '--input-device-index', type=int, default=1, + help='Index of the audio input device to use. Use this option to specify a particular microphone or audio input device based on your system. Default is 1.') + + parser.add_argument('-c', '--control', '--control_port', type=int, default=8011, + help='The port number used for the control WebSocket connection. Control connections are used to send and receive commands to the server. Default is port 8011.') + + parser.add_argument('-d', '--data', '--data_port', type=int, default=8012, + help='The port number used for the data WebSocket connection. Data connections are used to send audio data and receive transcription updates in real time. Default is port 8012.') + + parser.add_argument('-w', '--wake_words', type=str, default="", + help='Specify the wake word(s) that will trigger the server to start listening. For example, setting this to "Jarvis" will make the system start transcribing when it detects the wake word "Jarvis". Default is "Jarvis".') + + parser.add_argument('-D', '--debug', action='store_true', help='Enable debug logging for detailed server operations') + + parser.add_argument('--debug_websockets', action='store_true', help='Enable debug logging for detailed server websocket operations') + + parser.add_argument('-W', '--write', metavar='FILE', help='Save received audio to a WAV file') + + parser.add_argument('-b', '--batch', '--batch_size', type=int, default=16, help='Batch size for inference. This parameter controls the number of audio chunks processed in parallel during transcription. Default is 16.') + + parser.add_argument('--root', '--download_root', type=str,default=None, help='Specifies the root path where the Whisper models are downloaded to. Default is None.') + + parser.add_argument('-s', '--silence_timing', action='store_true', default=True, + help='Enable dynamic adjustment of silence duration for sentence detection. Adjusts post-speech silence duration based on detected sentence structure and punctuation. Default is False.') + + parser.add_argument('--init_realtime_after_seconds', type=float, default=0.2, + help='The initial waiting time in seconds before real-time transcription starts. This delay helps prevent false positives at the beginning of a session. Default is 0.2 seconds.') + + parser.add_argument('--realtime_batch_size', type=int, default=16, + help='Batch size for the real-time transcription model. This parameter controls the number of audio chunks processed in parallel during real-time transcription. Default is 16.') + + parser.add_argument('--initial_prompt_realtime', type=str, default="", help='Initial prompt that guides the real-time transcription model to produce transcriptions in a particular style or format.') + + parser.add_argument('--silero_sensitivity', type=float, default=0.05, + help='Sensitivity level for Silero Voice Activity Detection (VAD), with a range from 0 to 1. Lower values make the model less sensitive, useful for noisy environments. Default is 0.05.') + + parser.add_argument('--silero_use_onnx', action='store_true', default=False, + help='Enable ONNX version of Silero model for faster performance with lower resource usage. Default is False.') + + parser.add_argument('--webrtc_sensitivity', type=int, default=3, + help='Sensitivity level for WebRTC Voice Activity Detection (VAD), with a range from 0 to 3. Higher values make the model less sensitive, useful for cleaner environments. Default is 3.') + + parser.add_argument('--min_length_of_recording', type=float, default=1.1, + help='Minimum duration of valid recordings in seconds. This prevents very short recordings from being processed, which could be caused by noise or accidental sounds. Default is 1.1 seconds.') + + parser.add_argument('--min_gap_between_recordings', type=float, default=0, + help='Minimum time (in seconds) between consecutive recordings. Setting this helps avoid overlapping recordings when there’s a brief silence between them. Default is 0 seconds.') + + parser.add_argument('--enable_realtime_transcription', action='store_true', default=True, + help='Enable continuous real-time transcription of audio as it is received. When enabled, transcriptions are sent in near real-time. Default is True.') + + parser.add_argument('--realtime_processing_pause', type=float, default=0.02, + help='Time interval (in seconds) between processing audio chunks for real-time transcription. Lower values increase responsiveness but may put more load on the CPU. Default is 0.02 seconds.') + + parser.add_argument('--silero_deactivity_detection', action='store_true', default=True, + help='Use the Silero model for end-of-speech detection. This option can provide more robust silence detection in noisy environments, though it consumes more GPU resources. Default is True.') + + parser.add_argument('--early_transcription_on_silence', type=float, default=0.2, + help='Start transcription after the specified seconds of silence. This is useful when you want to trigger transcription mid-speech when there is a brief pause. Should be lower than post_speech_silence_duration. Set to 0 to disable. Default is 0.2 seconds.') + + parser.add_argument('--beam_size', type=int, default=5, + help='Beam size for the main transcription model. Larger values may improve transcription accuracy but increase the processing time. Default is 5.') + + parser.add_argument('--beam_size_realtime', type=int, default=3, + help='Beam size for the real-time transcription model. A smaller beam size allows for faster real-time processing but may reduce accuracy. Default is 3.') + + parser.add_argument('--initial_prompt', type=str, + default="Incomplete thoughts should end with '...'. Examples of complete thoughts: 'The sky is blue.' 'She walked home.' Examples of incomplete thoughts: 'When the sky...' 'Because he...'", + help='Initial prompt that guides the transcription model to produce transcriptions in a particular style or format. The default provides instructions for handling sentence completions and ellipsis usage.') + + parser.add_argument('--end_of_sentence_detection_pause', type=float, default=5.0, + help='The duration of silence (in seconds) that the model should interpret as the end of a sentence. This helps the system detect when to finalize the transcription of a sentence. Default is 0.45 seconds.') + + parser.add_argument('--unknown_sentence_detection_pause', type=float, default=5.0, + help='The duration of pause (in seconds) that the model should interpret as an incomplete or unknown sentence. This is useful for identifying when a sentence is trailing off or unfinished. Default is 0.7 seconds.') + + parser.add_argument('--mid_sentence_detection_pause', type=float, default=5.0, + help='The duration of pause (in seconds) that the model should interpret as a mid-sentence break. Longer pauses can indicate a pause in speech but not necessarily the end of a sentence. Default is 2.0 seconds.') + + parser.add_argument('--wake_words_sensitivity', type=float, default=0.5, + help='Sensitivity level for wake word detection, with a range from 0 (most sensitive) to 1 (least sensitive). Adjust this value based on your environment to ensure reliable wake word detection. Default is 0.5.') + + parser.add_argument('--wake_word_timeout', type=float, default=5.0, + help='Maximum time in seconds that the system will wait for a wake word before timing out. After this timeout, the system stops listening for wake words until reactivated. Default is 5.0 seconds.') + + parser.add_argument('--wake_word_activation_delay', type=float, default=0, + help='The delay in seconds before the wake word detection is activated after the system starts listening. This prevents false positives during the start of a session. Default is 0 seconds.') + + parser.add_argument('--wakeword_backend', type=str, default='none', + help='The backend used for wake word detection. You can specify different backends such as "default" or any custom implementations depending on your setup. Default is "pvporcupine".') + + parser.add_argument('--openwakeword_model_paths', type=str, nargs='*', + help='A list of file paths to OpenWakeWord models. This is useful if you are using OpenWakeWord for wake word detection and need to specify custom models.') + + parser.add_argument('--openwakeword_inference_framework', type=str, default='tensorflow', + help='The inference framework to use for OpenWakeWord models. Supported frameworks could include "tensorflow", "pytorch", etc. Default is "tensorflow".') + + parser.add_argument('--wake_word_buffer_duration', type=float, default=1.0, + help='Duration of the buffer in seconds for wake word detection. This sets how long the system will store the audio before and after detecting the wake word. Default is 1.0 seconds.') + + parser.add_argument('--use_main_model_for_realtime', action='store_true', + help='Enable this option if you want to use the main model for real-time transcription, instead of the smaller, faster real-time model. Using the main model may provide better accuracy but at the cost of higher processing time.') + + parser.add_argument('--use_extended_logging', action='store_true', + help='Writes extensive log messages for the recording worker, that processes the audio chunks.') + + parser.add_argument('--compute_type', type=str, default='default', + help='Type of computation to use. See https://opennmt.net/CTranslate2/quantization.html') + + parser.add_argument('--gpu_device_index', type=int, default=0, + help='Index of the GPU device to use. Default is None.') + + parser.add_argument('--device', type=str, default='cuda', + help='Device for model to use. Can either be "cuda" or "cpu". Default is cuda.') + + parser.add_argument('--handle_buffer_overflow', action='store_true', + help='Handle buffer overflow during transcription. Default is False.') + + parser.add_argument('--suppress_tokens', type=int, default=[-1], nargs='*', help='Suppress tokens during transcription. Default is [-1].') + + parser.add_argument('--allowed_latency_limit', type=int, default=100, + help='Maximal amount of chunks that can be unprocessed in queue before discarding chunks.. Default is 100.') + + parser.add_argument('--faster_whisper_vad_filter', action='store_true', + help='Enable VAD filter for Faster Whisper. Default is False.') + + parser.add_argument('--logchunks', action='store_true', help='Enable logging of incoming audio chunks (periods)') + + # Parse arguments + args = parser.parse_args() + + debug_logging = args.debug + extended_logging = args.use_extended_logging + writechunks = args.write + log_incoming_chunks = args.logchunks + dynamic_silence_timing = args.silence_timing + + + ws_logger = logging.getLogger('websockets') + if args.debug_websockets: + # If app debug is on, let websockets be verbose too + ws_logger.setLevel(logging.DEBUG) + # Ensure it uses the handler configured by basicConfig + ws_logger.propagate = False # Prevent duplicate messages if it also propagates to root + else: + # If app debug is off, silence websockets below WARNING + ws_logger.setLevel(logging.WARNING) + ws_logger.propagate = True # Allow WARNING/ERROR messages to reach root logger's handler + + # Replace escaped newlines with actual newlines in initial_prompt + if args.initial_prompt: + args.initial_prompt = args.initial_prompt.replace("\\n", "\n") + + if args.initial_prompt_realtime: + args.initial_prompt_realtime = args.initial_prompt_realtime.replace("\\n", "\n") + + return args + +def _recorder_thread(loop): + global recorder, stop_recorder + print(f"{bcolors.OKGREEN}Initializing RealtimeSTT server with parameters:{bcolors.ENDC}") + for key, value in recorder_config.items(): + print(f" {bcolors.OKBLUE}{key}{bcolors.ENDC}: {value}") + recorder = AudioToTextRecorder(**recorder_config) + print(f"{bcolors.OKGREEN}{bcolors.BOLD}RealtimeSTT initialized{bcolors.ENDC}") + recorder_ready.set() + + def process_text(full_sentence): + global prev_text + prev_text = "" + full_sentence = preprocess_text(full_sentence) + message = json.dumps({ + 'type': 'fullSentence', # <- Mensaje final y preciso + 'text': full_sentence + }) + asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop) + + timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3] + if extended_logging: + print(f" [{timestamp}] Full text: {bcolors.BOLD}Sentence:{bcolors.ENDC} {bcolors.OKGREEN}{full_sentence}{bcolors.ENDC}\n", flush=True, end="") + else: + print(f"\r[{timestamp}] {bcolors.BOLD}Sentence:{bcolors.ENDC} {bcolors.OKGREEN}{full_sentence}{bcolors.ENDC}\n") + try: + while not stop_recorder: + recorder.text(process_text) # <- Esto llama al modelo grande al terminar la frase + except KeyboardInterrupt: + print(f"{bcolors.WARNING}Exiting application due to keyboard interrupt{bcolors.ENDC}") + +def decode_and_resample( + audio_data, + original_sample_rate, + target_sample_rate): + + # Decode 16-bit PCM data to numpy array + if original_sample_rate == target_sample_rate: + return audio_data + + audio_np = np.frombuffer(audio_data, dtype=np.int16) + + # Calculate the number of samples after resampling + num_original_samples = len(audio_np) + num_target_samples = int(num_original_samples * target_sample_rate / + original_sample_rate) + + # Resample the audio + resampled_audio = resample(audio_np, num_target_samples) + + return resampled_audio.astype(np.int16).tobytes() + +async def control_handler(websocket): + debug_print(f"New control connection from {websocket.remote_address}") + print(f"{bcolors.OKGREEN}Control client connected{bcolors.ENDC}") + global recorder + control_connections.add(websocket) + try: + async for message in websocket: + debug_print(f"Received control message: {message[:200]}...") + if not recorder_ready.is_set(): + print(f"{bcolors.WARNING}Recorder not ready{bcolors.ENDC}") + continue + if isinstance(message, str): + # Handle text message (command) + try: + command_data = json.loads(message) + command = command_data.get("command") + if command == "set_parameter": + parameter = command_data.get("parameter") + value = command_data.get("value") + if parameter in allowed_parameters and hasattr(recorder, parameter): + setattr(recorder, parameter, value) + # Format the value for output + if isinstance(value, float): + value_formatted = f"{value:.2f}" + else: + value_formatted = value + timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3] + if extended_logging: + print(f" [{timestamp}] {bcolors.OKGREEN}Set recorder.{parameter} to: {bcolors.OKBLUE}{value_formatted}{bcolors.ENDC}") + # Optionally send a response back to the client + await websocket.send(json.dumps({"status": "success", "message": f"Parameter {parameter} set to {value}"})) + else: + if not parameter in allowed_parameters: + print(f"{bcolors.WARNING}Parameter {parameter} is not allowed (set_parameter){bcolors.ENDC}") + await websocket.send(json.dumps({"status": "error", "message": f"Parameter {parameter} is not allowed (set_parameter)"})) + else: + print(f"{bcolors.WARNING}Parameter {parameter} does not exist (set_parameter){bcolors.ENDC}") + await websocket.send(json.dumps({"status": "error", "message": f"Parameter {parameter} does not exist (set_parameter)"})) + + elif command == "get_parameter": + parameter = command_data.get("parameter") + request_id = command_data.get("request_id") # Get the request_id from the command data + if parameter in allowed_parameters and hasattr(recorder, parameter): + value = getattr(recorder, parameter) + if isinstance(value, float): + value_formatted = f"{value:.2f}" + else: + value_formatted = f"{value}" + + value_truncated = value_formatted[:39] + "…" if len(value_formatted) > 40 else value_formatted + + timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3] + if extended_logging: + print(f" [{timestamp}] {bcolors.OKGREEN}Get recorder.{parameter}: {bcolors.OKBLUE}{value_truncated}{bcolors.ENDC}") + response = {"status": "success", "parameter": parameter, "value": value} + if request_id is not None: + response["request_id"] = request_id + await websocket.send(json.dumps(response)) + else: + if not parameter in allowed_parameters: + print(f"{bcolors.WARNING}Parameter {parameter} is not allowed (get_parameter){bcolors.ENDC}") + await websocket.send(json.dumps({"status": "error", "message": f"Parameter {parameter} is not allowed (get_parameter)"})) + else: + print(f"{bcolors.WARNING}Parameter {parameter} does not exist (get_parameter){bcolors.ENDC}") + await websocket.send(json.dumps({"status": "error", "message": f"Parameter {parameter} does not exist (get_parameter)"})) + elif command == "call_method": + method_name = command_data.get("method") + if method_name in allowed_methods: + method = getattr(recorder, method_name, None) + if method and callable(method): + args = command_data.get("args", []) + kwargs = command_data.get("kwargs", {}) + method(*args, **kwargs) + timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3] + print(f" [{timestamp}] {bcolors.OKGREEN}Called method recorder.{bcolors.OKBLUE}{method_name}{bcolors.ENDC}") + await websocket.send(json.dumps({"status": "success", "message": f"Method {method_name} called"})) + else: + print(f"{bcolors.WARNING}Recorder does not have method {method_name}{bcolors.ENDC}") + await websocket.send(json.dumps({"status": "error", "message": f"Recorder does not have method {method_name}"})) + else: + print(f"{bcolors.WARNING}Method {method_name} is not allowed{bcolors.ENDC}") + await websocket.send(json.dumps({"status": "error", "message": f"Method {method_name} is not allowed"})) + else: + print(f"{bcolors.WARNING}Unknown command: {command}{bcolors.ENDC}") + await websocket.send(json.dumps({"status": "error", "message": f"Unknown command {command}"})) + except json.JSONDecodeError: + print(f"{bcolors.WARNING}Received invalid JSON command{bcolors.ENDC}") + await websocket.send(json.dumps({"status": "error", "message": "Invalid JSON command"})) + else: + print(f"{bcolors.WARNING}Received unknown message type on control connection{bcolors.ENDC}") + except websockets.exceptions.ConnectionClosed as e: + print(f"{bcolors.WARNING}Control client disconnected: {e}{bcolors.ENDC}") + finally: + control_connections.remove(websocket) + +async def data_handler(websocket): + global writechunks, wav_file + print(f"{bcolors.OKGREEN}Data client connected{bcolors.ENDC}") + data_connections.add(websocket) + try: + while True: + message = await websocket.recv() + if isinstance(message, bytes): + if extended_logging: + debug_print(f"Received audio chunk (size: {len(message)} bytes)") + elif log_incoming_chunks: + print(".", end='', flush=True) + # Handle binary message (audio data) + metadata_length = int.from_bytes(message[:4], byteorder='little') + metadata_json = message[4:4+metadata_length].decode('utf-8') + metadata = json.loads(metadata_json) + sample_rate = metadata['sampleRate'] + + if 'server_sent_to_stt' in metadata: + stt_received_ns = time.time_ns() + metadata["stt_received"] = stt_received_ns + metadata["stt_received_formatted"] = format_timestamp_ns(stt_received_ns) + print(f"Server received audio chunk of length {len(message)} bytes, metadata: {metadata}") + + if extended_logging: + debug_print(f"Processing audio chunk with sample rate {sample_rate}") + chunk = message[4+metadata_length:] + + if writechunks: + if not wav_file: + wav_file = wave.open(writechunks, 'wb') + wav_file.setnchannels(CHANNELS) + wav_file.setsampwidth(pyaudio.get_sample_size(FORMAT)) + wav_file.setframerate(sample_rate) + + wav_file.writeframes(chunk) + + if sample_rate != 16000: + resampled_chunk = decode_and_resample(chunk, sample_rate, 16000) + if extended_logging: + debug_print(f"Resampled chunk size: {len(resampled_chunk)} bytes") + recorder.feed_audio(resampled_chunk) + else: + recorder.feed_audio(chunk) + else: + print(f"{bcolors.WARNING}Received non-binary message on data connection{bcolors.ENDC}") + except websockets.exceptions.ConnectionClosed as e: + print(f"{bcolors.WARNING}Data client disconnected: {e}{bcolors.ENDC}") + finally: + data_connections.remove(websocket) + # recorder.clear_audio_queue() # Ensure audio queue is cleared if client disconnects + +async def broadcast_audio_messages(): + while True: + message = await audio_queue.get() + for conn in list(data_connections): + try: + timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3] + + if extended_logging: + print(f" [{timestamp}] Sending message: {bcolors.OKBLUE}{message}{bcolors.ENDC}\n", flush=True, end="") + await conn.send(message) + except websockets.exceptions.ConnectionClosed: + data_connections.remove(conn) + +# Helper function to create event loop bound closures for callbacks +def make_callback(loop, callback): + def inner_callback(*args, **kwargs): + callback(*args, **kwargs, loop=loop) + return inner_callback + +async def main_async(): + global stop_recorder, recorder_config, global_args + args = parse_arguments() + global_args = args + + # Get the event loop here and pass it to the recorder thread + loop = asyncio.get_event_loop() + + recorder_config = { + 'model': args.model, + 'download_root': args.root, + 'realtime_model_type': args.rt_model, + 'language': args.lang, + 'batch_size': args.batch, + 'init_realtime_after_seconds': args.init_realtime_after_seconds, + 'realtime_batch_size': args.realtime_batch_size, + 'initial_prompt_realtime': args.initial_prompt_realtime, + 'input_device_index': args.input_device, + 'silero_sensitivity': args.silero_sensitivity, + 'silero_use_onnx': args.silero_use_onnx, + 'webrtc_sensitivity': args.webrtc_sensitivity, + 'post_speech_silence_duration': args.unknown_sentence_detection_pause, + 'min_length_of_recording': args.min_length_of_recording, + 'min_gap_between_recordings': args.min_gap_between_recordings, + 'enable_realtime_transcription': args.enable_realtime_transcription, + 'realtime_processing_pause': args.realtime_processing_pause, + 'silero_deactivity_detection': args.silero_deactivity_detection, + 'early_transcription_on_silence': args.early_transcription_on_silence, + 'beam_size': args.beam_size, + 'beam_size_realtime': args.beam_size_realtime, + 'initial_prompt': args.initial_prompt, + 'wake_words': args.wake_words, + 'wake_words_sensitivity': args.wake_words_sensitivity, + 'wake_word_timeout': args.wake_word_timeout, + 'wake_word_activation_delay': args.wake_word_activation_delay, + 'wakeword_backend': args.wakeword_backend, + 'openwakeword_model_paths': args.openwakeword_model_paths, + 'openwakeword_inference_framework': args.openwakeword_inference_framework, + 'wake_word_buffer_duration': args.wake_word_buffer_duration, + 'use_main_model_for_realtime': args.use_main_model_for_realtime, + 'spinner': False, + 'use_microphone': False, + + 'on_realtime_transcription_update': make_callback(loop, text_detected), + 'on_recording_start': make_callback(loop, on_recording_start), + 'on_recording_stop': make_callback(loop, on_recording_stop), + 'on_vad_detect_start': make_callback(loop, on_vad_detect_start), + 'on_vad_detect_stop': make_callback(loop, on_vad_detect_stop), + 'on_wakeword_detected': make_callback(loop, on_wakeword_detected), + 'on_wakeword_detection_start': make_callback(loop, on_wakeword_detection_start), + 'on_wakeword_detection_end': make_callback(loop, on_wakeword_detection_end), + 'on_transcription_start': make_callback(loop, on_transcription_start), + 'on_turn_detection_start': make_callback(loop, on_turn_detection_start), + 'on_turn_detection_stop': make_callback(loop, on_turn_detection_stop), + + # 'on_recorded_chunk': make_callback(loop, on_recorded_chunk), + 'no_log_file': True, # Disable logging to file + 'use_extended_logging': args.use_extended_logging, + 'level': loglevel, + 'compute_type': args.compute_type, + 'gpu_device_index': args.gpu_device_index, + 'device': args.device, + 'handle_buffer_overflow': args.handle_buffer_overflow, + 'suppress_tokens': args.suppress_tokens, + 'allowed_latency_limit': args.allowed_latency_limit, + 'faster_whisper_vad_filter': args.faster_whisper_vad_filter, + } + + try: + # Attempt to start control and data servers + control_server = await websockets.serve(control_handler, "localhost", args.control) + data_server = await websockets.serve(data_handler, "localhost", args.data) + print(f"{bcolors.OKGREEN}Control server started on {bcolors.OKBLUE}ws://localhost:{args.control}{bcolors.ENDC}") + print(f"{bcolors.OKGREEN}Data server started on {bcolors.OKBLUE}ws://localhost:{args.data}{bcolors.ENDC}") + + # Start the broadcast and recorder threads + broadcast_task = asyncio.create_task(broadcast_audio_messages()) + + recorder_thread = threading.Thread(target=_recorder_thread, args=(loop,)) + recorder_thread.start() + recorder_ready.wait() + + print(f"{bcolors.OKGREEN}Server started. Press Ctrl+C to stop the server.{bcolors.ENDC}") + + # Run server tasks + await asyncio.gather(control_server.wait_closed(), data_server.wait_closed(), broadcast_task) + except OSError as e: + print(f"{bcolors.FAIL}Error: Could not start server on specified ports. It’s possible another instance of the server is already running, or the ports are being used by another application.{bcolors.ENDC}") + except KeyboardInterrupt: + print(f"{bcolors.WARNING}Server interrupted by user, shutting down...{bcolors.ENDC}") + finally: + # Shutdown procedures for recorder and server threads + await shutdown_procedure() + print(f"{bcolors.OKGREEN}Server shutdown complete.{bcolors.ENDC}") + +async def shutdown_procedure(): + global stop_recorder, recorder_thread + if recorder: + stop_recorder = True + recorder.abort() + # recorder.stop() + recorder.shutdown() + print(f"{bcolors.OKGREEN}Recorder shut down{bcolors.ENDC}") + + if recorder_thread: + recorder_thread.join() + print(f"{bcolors.OKGREEN}Recorder thread finished{bcolors.ENDC}") + + tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()] + for task in tasks: + task.cancel() + await asyncio.gather(*tasks, return_exceptions=True) + + print(f"{bcolors.OKGREEN}All tasks cancelled, closing event loop now.{bcolors.ENDC}") + +def main(): + try: + asyncio.run(main_async()) + except KeyboardInterrupt: + # Capture any final KeyboardInterrupt to prevent it from showing up in logs + print(f"{bcolors.WARNING}Server interrupted by user.{bcolors.ENDC}") + exit(0) + +if __name__ == '__main__': + main() diff --git a/stt_recorder/README.md b/stt_recorder/README.md new file mode 100644 index 00000000..f9edd992 --- /dev/null +++ b/stt_recorder/README.md @@ -0,0 +1,18 @@ +# SttRecorder + +To start your Phoenix server: + + * Run `mix setup` to install and setup dependencies + * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` + +Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. + +Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). + +## Learn more + + * Official website: https://www.phoenixframework.org/ + * Guides: https://hexdocs.pm/phoenix/overview.html + * Docs: https://hexdocs.pm/phoenix + * Forum: https://elixirforum.com/c/phoenix-forum + * Source: https://github.com/phoenixframework/phoenix diff --git a/stt_recorder/assets/css/app.css b/stt_recorder/assets/css/app.css new file mode 100644 index 00000000..d67ce14d --- /dev/null +++ b/stt_recorder/assets/css/app.css @@ -0,0 +1,86 @@ +@import "tailwindcss/base"; +@import "tailwindcss/components"; +@import "tailwindcss/utilities"; + +/* This file is for your main application CSS */ + body { + background-color: #f4f4f9; + color: #333; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + margin: 0; + } + #container { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + max-width: 700px; + padding: 20px; + box-sizing: border-box; + gap: 20px; /* Add more vertical space between items */ + height: 90%; /* Fixed height to prevent layout shift */ + } + #status { + color: #0056b3; + font-size: 20px; + text-align: center; + } + #transcriptionContainer { + height: 90px; /* Fixed height for approximately 3 lines of text */ + overflow-y: auto; + width: 100%; + padding: 10px; + box-sizing: border-box; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 5px; + } + #transcription { + font-size: 18px; + line-height: 1.6; + color: #333; + word-wrap: break-word; + } + #fullTextContainer { + height: 150px; /* Fixed height to prevent layout shift */ + overflow-y: auto; + width: 100%; + padding: 10px; + box-sizing: border-box; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 5px; + } + #fullText { + color: #4CAF50; + font-size: 18px; + font-weight: 600; + word-wrap: break-word; + } + .last-word { + color: #007bff; + font-weight: 600; + } + button { + padding: 12px 24px; + font-size: 16px; + cursor: pointer; + border: none; + border-radius: 5px; + margin: 5px; + transition: background-color 0.3s ease; + color: #fff; + background-color: #0056b3; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + } + button:hover { + background-color: #007bff; + } + button:disabled { + background-color: #cccccc; + cursor: not-allowed; + } \ No newline at end of file diff --git a/stt_recorder/assets/js/app.js b/stt_recorder/assets/js/app.js new file mode 100644 index 00000000..6f6037fe --- /dev/null +++ b/stt_recorder/assets/js/app.js @@ -0,0 +1,55 @@ +// If you want to use Phoenix channels, run `mix help phx.gen.channel` +// to get started and then uncomment the line below. +// import "./user_socket.js" + +// You can include dependencies in two ways. +// +// The simplest option is to put them in assets/vendor and +// import them using relative paths: +// +// import "../vendor/some-package.js" +// +// Alternatively, you can `npm install some-package --prefix assets` and import +// them using a path starting with the package name: +// +// import "some-package" +// + +// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. +import "phoenix_html" +// Establish Phoenix Socket and LiveView configuration. +import {Socket} from "phoenix" +import {LiveSocket} from "phoenix_live_view" +import topbar from "../vendor/topbar" +import SttRecorder from "./stt_recorder.js"; + +let liveSocket = new LiveSocket("/live", Socket, { + hooks: { SttRecorder }, + params: { _csrf_token: csrfToken } +}); + +liveSocket.connect(); + +window.liveSocket = liveSocket; + + +// let liveSocket = new LiveSocket("/live", Socket, { +// longPollFallbackMs: 2500, +// params: {_csrf_token: csrfToken}, +// hooks: Hooks +// }) + +// Show progress bar on live navigation and form submits +topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) +window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) +window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) + +// connect if there are any LiveViews on the page +liveSocket.connect() + +// expose liveSocket on window for web console debug logs and latency simulation: +// >> liveSocket.enableDebug() +// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session +// >> liveSocket.disableLatencySim() +window.liveSocket = liveSocket + diff --git a/stt_recorder/assets/js/stt_recorder.js b/stt_recorder/assets/js/stt_recorder.js new file mode 100644 index 00000000..4f3a3ba5 --- /dev/null +++ b/stt_recorder/assets/js/stt_recorder.js @@ -0,0 +1,119 @@ +// assets/js/stt_recorder.js + +let SttRecorder = { + mounted() { + const statusDiv = document.getElementById("status"); + const transcriptionDiv = document.getElementById("transcription"); + const fullTextDiv = document.getElementById("fullText"); + const startButton = document.getElementById("startButton"); + const stopButton = document.getElementById("stopButton"); + + const controlURL = "ws://127.0.0.1:8011"; + const dataURL = "ws://127.0.0.1:8012"; + let dataSocket; + let audioContext; + let mediaStream; + let mediaProcessor; + + // define startRecording and stopRecording here, or attach to this + window.startRecording = async function () { + try { + startButton.disabled = true; + stopButton.disabled = false; + statusDiv.textContent = "Recording..."; + transcriptionDiv.textContent = ""; + fullTextDiv.textContent = ""; + + audioContext = new AudioContext(); + mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true }); + const input = audioContext.createMediaStreamSource(mediaStream); + + mediaProcessor = audioContext.createScriptProcessor(1024, 1, 1); + mediaProcessor.onaudioprocess = (event) => { + const audioData = event.inputBuffer.getChannelData(0); + sendAudioChunk(audioData, audioContext.sampleRate); + }; + + input.connect(mediaProcessor); + mediaProcessor.connect(audioContext.destination); + + connectToDataSocket(); + } catch (error) { + console.error("Error accessing microphone:", error); + statusDiv.textContent = "Error accessing microphone."; + stopRecording(); + } + }; + + window.stopRecording = function () { + if (mediaProcessor && audioContext) { + mediaProcessor.disconnect(); + audioContext.close(); + } + + if (mediaStream) { + mediaStream.getTracks().forEach((track) => track.stop()); + } + + if (dataSocket) { + dataSocket.close(); + } + + startButton.disabled = false; + stopButton.disabled = true; + statusDiv.textContent = "Stopped recording."; + }; + + function connectToDataSocket() { + dataSocket = new WebSocket(dataURL); + + dataSocket.onopen = () => { + statusDiv.textContent = "Connected to STT server."; + }; + + dataSocket.onmessage = (event) => { + try { + const message = JSON.parse(event.data); + if (message.type === "realtime") { + let words = message.text.split(" "); + let lastWord = words.pop(); + transcriptionDiv.innerHTML = `${words.join(" ")} ${lastWord}`; + } else if (message.type === "fullSentence") { + fullTextDiv.innerHTML += message.text + " "; + transcriptionDiv.innerHTML = message.text; + } + } catch (e) { + console.error("Error parsing message:", e); + } + }; + + dataSocket.onerror = (error) => { + console.error("WebSocket error:", error); + statusDiv.textContent = "Error connecting to the STT server."; + }; + } + + function sendAudioChunk(audioData, sampleRate) { + if (dataSocket && dataSocket.readyState === WebSocket.OPEN) { + const float32Array = new Float32Array(audioData); + const pcm16Data = new Int16Array(float32Array.length); + for (let i = 0; i < float32Array.length; i++) { + pcm16Data[i] = Math.max(-1, Math.min(1, float32Array[i])) * 0x7fff; + } + + const metadata = JSON.stringify({ sampleRate }); + const metadataLength = new Uint32Array([metadata.length]); + const metadataBuffer = new TextEncoder().encode(metadata); + + const message = new Uint8Array(metadataLength.byteLength + metadataBuffer.byteLength + pcm16Data.byteLength); + message.set(new Uint8Array(metadataLength.buffer), 0); + message.set(metadataBuffer, metadataLength.byteLength); + message.set(new Uint8Array(pcm16Data.buffer), metadataLength.byteLength + metadataBuffer.byteLength); + + dataSocket.send(message); + } + } + }, +}; + +export default SttRecorder; diff --git a/stt_recorder/assets/tailwind.config.js b/stt_recorder/assets/tailwind.config.js new file mode 100644 index 00000000..b054b85a --- /dev/null +++ b/stt_recorder/assets/tailwind.config.js @@ -0,0 +1,74 @@ +// See the Tailwind configuration guide for advanced usage +// https://tailwindcss.com/docs/configuration + +const plugin = require("tailwindcss/plugin") +const fs = require("fs") +const path = require("path") + +module.exports = { + content: [ + "./js/**/*.js", + "../lib/stt_recorder_web.ex", + "../lib/stt_recorder_web/**/*.*ex" + ], + theme: { + extend: { + colors: { + brand: "#FD4F00", + } + }, + }, + plugins: [ + require("@tailwindcss/forms"), + // Allows prefixing tailwind classes with LiveView classes to add rules + // only when LiveView classes are applied, for example: + // + //

+ // + plugin(({addVariant}) => addVariant("phx-click-loading", [".phx-click-loading&", ".phx-click-loading &"])), + plugin(({addVariant}) => addVariant("phx-submit-loading", [".phx-submit-loading&", ".phx-submit-loading &"])), + plugin(({addVariant}) => addVariant("phx-change-loading", [".phx-change-loading&", ".phx-change-loading &"])), + + // Embeds Heroicons (https://heroicons.com) into your app.css bundle + // See your `CoreComponents.icon/1` for more information. + // + plugin(function({matchComponents, theme}) { + let iconsDir = path.join(__dirname, "../deps/heroicons/optimized") + let values = {} + let icons = [ + ["", "/24/outline"], + ["-solid", "/24/solid"], + ["-mini", "/20/solid"], + ["-micro", "/16/solid"] + ] + icons.forEach(([suffix, dir]) => { + fs.readdirSync(path.join(iconsDir, dir)).forEach(file => { + let name = path.basename(file, ".svg") + suffix + values[name] = {name, fullPath: path.join(iconsDir, dir, file)} + }) + }) + matchComponents({ + "hero": ({name, fullPath}) => { + let content = fs.readFileSync(fullPath).toString().replace(/\r?\n|\r/g, "") + let size = theme("spacing.6") + if (name.endsWith("-mini")) { + size = theme("spacing.5") + } else if (name.endsWith("-micro")) { + size = theme("spacing.4") + } + return { + [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`, + "-webkit-mask": `var(--hero-${name})`, + "mask": `var(--hero-${name})`, + "mask-repeat": "no-repeat", + "background-color": "currentColor", + "vertical-align": "middle", + "display": "inline-block", + "width": size, + "height": size + } + } + }, {values}) + }) + ] +} diff --git a/stt_recorder/assets/vendor/topbar.js b/stt_recorder/assets/vendor/topbar.js new file mode 100644 index 00000000..41957274 --- /dev/null +++ b/stt_recorder/assets/vendor/topbar.js @@ -0,0 +1,165 @@ +/** + * @license MIT + * topbar 2.0.0, 2023-02-04 + * https://buunguyen.github.io/topbar + * Copyright (c) 2021 Buu Nguyen + */ +(function (window, document) { + "use strict"; + + // https://gist.github.com/paulirish/1579671 + (function () { + var lastTime = 0; + var vendors = ["ms", "moz", "webkit", "o"]; + for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = + window[vendors[x] + "RequestAnimationFrame"]; + window.cancelAnimationFrame = + window[vendors[x] + "CancelAnimationFrame"] || + window[vendors[x] + "CancelRequestAnimationFrame"]; + } + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function (callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function () { + callback(currTime + timeToCall); + }, timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function (id) { + clearTimeout(id); + }; + })(); + + var canvas, + currentProgress, + showing, + progressTimerId = null, + fadeTimerId = null, + delayTimerId = null, + addEvent = function (elem, type, handler) { + if (elem.addEventListener) elem.addEventListener(type, handler, false); + else if (elem.attachEvent) elem.attachEvent("on" + type, handler); + else elem["on" + type] = handler; + }, + options = { + autoRun: true, + barThickness: 3, + barColors: { + 0: "rgba(26, 188, 156, .9)", + ".25": "rgba(52, 152, 219, .9)", + ".50": "rgba(241, 196, 15, .9)", + ".75": "rgba(230, 126, 34, .9)", + "1.0": "rgba(211, 84, 0, .9)", + }, + shadowBlur: 10, + shadowColor: "rgba(0, 0, 0, .6)", + className: null, + }, + repaint = function () { + canvas.width = window.innerWidth; + canvas.height = options.barThickness * 5; // need space for shadow + + var ctx = canvas.getContext("2d"); + ctx.shadowBlur = options.shadowBlur; + ctx.shadowColor = options.shadowColor; + + var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0); + for (var stop in options.barColors) + lineGradient.addColorStop(stop, options.barColors[stop]); + ctx.lineWidth = options.barThickness; + ctx.beginPath(); + ctx.moveTo(0, options.barThickness / 2); + ctx.lineTo( + Math.ceil(currentProgress * canvas.width), + options.barThickness / 2 + ); + ctx.strokeStyle = lineGradient; + ctx.stroke(); + }, + createCanvas = function () { + canvas = document.createElement("canvas"); + var style = canvas.style; + style.position = "fixed"; + style.top = style.left = style.right = style.margin = style.padding = 0; + style.zIndex = 100001; + style.display = "none"; + if (options.className) canvas.classList.add(options.className); + document.body.appendChild(canvas); + addEvent(window, "resize", repaint); + }, + topbar = { + config: function (opts) { + for (var key in opts) + if (options.hasOwnProperty(key)) options[key] = opts[key]; + }, + show: function (delay) { + if (showing) return; + if (delay) { + if (delayTimerId) return; + delayTimerId = setTimeout(() => topbar.show(), delay); + } else { + showing = true; + if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId); + if (!canvas) createCanvas(); + canvas.style.opacity = 1; + canvas.style.display = "block"; + topbar.progress(0); + if (options.autoRun) { + (function loop() { + progressTimerId = window.requestAnimationFrame(loop); + topbar.progress( + "+" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2) + ); + })(); + } + } + }, + progress: function (to) { + if (typeof to === "undefined") return currentProgress; + if (typeof to === "string") { + to = + (to.indexOf("+") >= 0 || to.indexOf("-") >= 0 + ? currentProgress + : 0) + parseFloat(to); + } + currentProgress = to > 1 ? 1 : to; + repaint(); + return currentProgress; + }, + hide: function () { + clearTimeout(delayTimerId); + delayTimerId = null; + if (!showing) return; + showing = false; + if (progressTimerId != null) { + window.cancelAnimationFrame(progressTimerId); + progressTimerId = null; + } + (function loop() { + if (topbar.progress("+.1") >= 1) { + canvas.style.opacity -= 0.05; + if (canvas.style.opacity <= 0.05) { + canvas.style.display = "none"; + fadeTimerId = null; + return; + } + } + fadeTimerId = window.requestAnimationFrame(loop); + })(); + }, + }; + + if (typeof module === "object" && typeof module.exports === "object") { + module.exports = topbar; + } else if (typeof define === "function" && define.amd) { + define(function () { + return topbar; + }); + } else { + this.topbar = topbar; + } +}.call(this, window, document)); diff --git a/stt_recorder/config/config.exs b/stt_recorder/config/config.exs new file mode 100644 index 00000000..9f4bc9b6 --- /dev/null +++ b/stt_recorder/config/config.exs @@ -0,0 +1,65 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Config module. +# +# This configuration file is loaded before any dependency and +# is restricted to this project. + +# General application configuration +import Config + +config :stt_recorder, + generators: [timestamp_type: :utc_datetime] + +# Configures the endpoint +config :stt_recorder, SttRecorderWeb.Endpoint, + url: [host: "localhost"], + adapter: Bandit.PhoenixAdapter, + render_errors: [ + formats: [html: SttRecorderWeb.ErrorHTML, json: SttRecorderWeb.ErrorJSON], + layout: false + ], + pubsub_server: SttRecorder.PubSub, + live_view: [signing_salt: "4qUMg2FH"] + +# Configures the mailer +# +# By default it uses the "Local" adapter which stores the emails +# locally. You can see the emails in your browser, at "/dev/mailbox". +# +# For production it's recommended to configure a different adapter +# at the `config/runtime.exs`. +config :stt_recorder, SttRecorder.Mailer, adapter: Swoosh.Adapters.Local + +# Configure esbuild (the version is required) +config :esbuild, + version: "0.17.11", + stt_recorder: [ + args: + ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*), + cd: Path.expand("../assets", __DIR__), + env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)} + ] + +# Configure tailwind (the version is required) +config :tailwind, + version: "3.4.3", + stt_recorder: [ + args: ~w( + --config=tailwind.config.js + --input=css/app.css + --output=../priv/static/assets/app.css + ), + cd: Path.expand("../assets", __DIR__) + ] + +# Configures Elixir's Logger +config :logger, :console, + format: "$time $metadata[$level] $message\n", + metadata: [:request_id] + +# Use Jason for JSON parsing in Phoenix +config :phoenix, :json_library, Jason + +# Import environment specific config. This must remain at the bottom +# of this file so it overrides the configuration defined above. +import_config "#{config_env()}.exs" diff --git a/stt_recorder/config/dev.exs b/stt_recorder/config/dev.exs new file mode 100644 index 00000000..efbe2f89 --- /dev/null +++ b/stt_recorder/config/dev.exs @@ -0,0 +1,75 @@ +import Config + +# For development, we disable any cache and enable +# debugging and code reloading. +# +# The watchers configuration can be used to run external +# watchers to your application. For example, we can use it +# to bundle .js and .css sources. +config :stt_recorder, SttRecorderWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 4000], + check_origin: false, + code_reloader: true, + debug_errors: true, + secret_key_base: "Cndrc+8xBjEgDUgDJfPsBZv0RcxwRezDvNUxV06+etB09JXsY7JgHSeYdtKCtgXR", + watchers: [ + esbuild: {Esbuild, :install_and_run, [:stt_recorder, ~w(--sourcemap=inline --watch)]}, + tailwind: {Tailwind, :install_and_run, [:stt_recorder, ~w(--watch)]} + ] + +# ## SSL Support +# +# In order to use HTTPS in development, a self-signed +# certificate can be generated by running the following +# Mix task: +# +# mix phx.gen.cert +# +# Run `mix help phx.gen.cert` for more information. +# +# The `http:` config above can be replaced with: +# +# https: [ +# port: 4001, +# cipher_suite: :strong, +# keyfile: "priv/cert/selfsigned_key.pem", +# certfile: "priv/cert/selfsigned.pem" +# ], +# +# If desired, both `http:` and `https:` keys can be +# configured to run both http and https servers on +# different ports. + +# Watch static and templates for browser reloading. +config :stt_recorder, SttRecorderWeb.Endpoint, + live_reload: [ + patterns: [ + ~r"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$", + ~r"priv/gettext/.*(po)$", + ~r"lib/stt_recorder_web/(controllers|live|components)/.*(ex|heex)$" + ] + ] + +# Enable dev routes for dashboard and mailbox +config :stt_recorder, dev_routes: true + +# Do not include metadata nor timestamps in development logs +config :logger, :console, format: "[$level] $message\n" + +# Set a higher stacktrace during development. Avoid configuring such +# in production as building large stacktraces may be expensive. +config :phoenix, :stacktrace_depth, 20 + +# Initialize plugs at runtime for faster development compilation +config :phoenix, :plug_init_mode, :runtime + +config :phoenix_live_view, + # Include HEEx debug annotations as HTML comments in rendered markup + debug_heex_annotations: true, + # Enable helpful, but potentially expensive runtime checks + enable_expensive_runtime_checks: true + +# Disable swoosh api client as it is only required for production adapters. +config :swoosh, :api_client, false diff --git a/stt_recorder/config/prod.exs b/stt_recorder/config/prod.exs new file mode 100644 index 00000000..3def4b03 --- /dev/null +++ b/stt_recorder/config/prod.exs @@ -0,0 +1,21 @@ +import Config + +# Note we also include the path to a cache manifest +# containing the digested version of static files. This +# manifest is generated by the `mix assets.deploy` task, +# which you should run after static files are built and +# before starting your production server. +config :stt_recorder, SttRecorderWeb.Endpoint, + cache_static_manifest: "priv/static/cache_manifest.json" + +# Configures Swoosh API Client +config :swoosh, api_client: Swoosh.ApiClient.Finch, finch_name: SttRecorder.Finch + +# Disable Swoosh Local Memory Storage +config :swoosh, local: false + +# Do not print debug messages in production +config :logger, level: :info + +# Runtime production configuration, including reading +# of environment variables, is done on config/runtime.exs. diff --git a/stt_recorder/config/runtime.exs b/stt_recorder/config/runtime.exs new file mode 100644 index 00000000..7d4f5f22 --- /dev/null +++ b/stt_recorder/config/runtime.exs @@ -0,0 +1,24 @@ +import Config + +if System.get_env("PHX_SERVER") do + config :stt_recorder, SttRecorderWeb.Endpoint, server: true +end + +if config_env() == :prod do + config :stt_recorder, SttRecorderWeb.Endpoint, + check_origin: false + + secret_key_base = System.get_env("SECRET_KEY_BASE") || "VGUXen8QezXuamrCHG6pTLtjJQVompq/BQK8ihF6jP1PG77G1Y8Ho6aFHKZQG07Z" + host = System.get_env("PHX_HOST") || "localhost" + port = String.to_integer(System.get_env("PORT") || "4000") + + config :stt_recorder, :dns_cluster_query, System.get_env("DNS_CLUSTER_QUERY") + + config :stt_recorder, SttRecorderWeb.Endpoint, + url: [host: host, port: port, scheme: "http"], + http: [ + ip: {0, 0, 0, 0}, + port: port + ], + secret_key_base: secret_key_base +end diff --git a/stt_recorder/config/test.exs b/stt_recorder/config/test.exs new file mode 100644 index 00000000..b6088bcf --- /dev/null +++ b/stt_recorder/config/test.exs @@ -0,0 +1,24 @@ +import Config + +# We don't run a server during test. If one is required, +# you can enable the server option below. +config :stt_recorder, SttRecorderWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 4002], + secret_key_base: "STk/67ZfxBsaykDyR6DCl1ZRZY2V/7DLR4gzKWi+mmmZNa1JkHLAlasIUV/SWO8B", + server: false + +# In test we don't send emails +config :stt_recorder, SttRecorder.Mailer, adapter: Swoosh.Adapters.Test + +# Disable swoosh api client as it is only required for production adapters +config :swoosh, :api_client, false + +# Print only warnings and errors during test +config :logger, level: :warning + +# Initialize plugs at runtime for faster test compilation +config :phoenix, :plug_init_mode, :runtime + +# Enable helpful, but potentially expensive runtime checks +config :phoenix_live_view, + enable_expensive_runtime_checks: true diff --git a/stt_recorder/lib/stt_recorder.ex b/stt_recorder/lib/stt_recorder.ex new file mode 100644 index 00000000..e73b4d03 --- /dev/null +++ b/stt_recorder/lib/stt_recorder.ex @@ -0,0 +1,9 @@ +defmodule SttRecorder do + @moduledoc """ + SttRecorder keeps the contexts that define your domain + and business logic. + + Contexts are also responsible for managing your data, regardless + if it comes from the database, an external API or others. + """ +end diff --git a/stt_recorder/lib/stt_recorder/application.ex b/stt_recorder/lib/stt_recorder/application.ex new file mode 100644 index 00000000..6ea50ed4 --- /dev/null +++ b/stt_recorder/lib/stt_recorder/application.ex @@ -0,0 +1,35 @@ +defmodule SttRecorder.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + SttRecorderWeb.Telemetry, + {DNSCluster, query: Application.get_env(:stt_recorder, :dns_cluster_query) || :ignore}, + {Phoenix.PubSub, name: SttRecorder.PubSub}, + # Start the Finch HTTP client for sending emails + {Finch, name: SttRecorder.Finch}, + # Start a worker by calling: SttRecorder.Worker.start_link(arg) + # {SttRecorder.Worker, arg}, + # Start to serve requests, typically the last entry + SttRecorderWeb.Endpoint + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: SttRecorder.Supervisor] + Supervisor.start_link(children, opts) + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + @impl true + def config_change(changed, _new, removed) do + SttRecorderWeb.Endpoint.config_change(changed, removed) + :ok + end +end diff --git a/stt_recorder/lib/stt_recorder/mailer.ex b/stt_recorder/lib/stt_recorder/mailer.ex new file mode 100644 index 00000000..2307a2fd --- /dev/null +++ b/stt_recorder/lib/stt_recorder/mailer.ex @@ -0,0 +1,3 @@ +defmodule SttRecorder.Mailer do + use Swoosh.Mailer, otp_app: :stt_recorder +end diff --git a/stt_recorder/lib/stt_recorder_web.ex b/stt_recorder/lib/stt_recorder_web.ex new file mode 100644 index 00000000..473782b4 --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web.ex @@ -0,0 +1,116 @@ +defmodule SttRecorderWeb do + @moduledoc """ + The entrypoint for defining your web interface, such + as controllers, components, channels, and so on. + + This can be used in your application as: + + use SttRecorderWeb, :controller + use SttRecorderWeb, :html + + The definitions below will be executed for every controller, + component, etc, so keep them short and clean, focused + on imports, uses and aliases. + + Do NOT define functions inside the quoted expressions + below. Instead, define additional modules and import + those modules here. + """ + + def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + + def router do + quote do + use Phoenix.Router, helpers: false + + # Import common connection and controller functions to use in pipelines + import Plug.Conn + import Phoenix.Controller + import Phoenix.LiveView.Router + end + end + + def channel do + quote do + use Phoenix.Channel + end + end + + def controller do + quote do + use Phoenix.Controller, + formats: [:html, :json], + layouts: [html: SttRecorderWeb.Layouts] + + use Gettext, backend: SttRecorderWeb.Gettext + + import Plug.Conn + + unquote(verified_routes()) + end + end + + def live_view do + quote do + use Phoenix.LiveView, + layout: {SttRecorderWeb.Layouts, :app} + + unquote(html_helpers()) + end + end + + def live_component do + quote do + use Phoenix.LiveComponent + + unquote(html_helpers()) + end + end + + def html do + quote do + use Phoenix.Component + + # Import convenience functions from controllers + import Phoenix.Controller, + only: [get_csrf_token: 0, view_module: 1, view_template: 1] + + # Include general helpers for rendering HTML + unquote(html_helpers()) + end + end + + defp html_helpers do + quote do + # Translation + use Gettext, backend: SttRecorderWeb.Gettext + + # HTML escaping functionality + import Phoenix.HTML + # Core UI components + import SttRecorderWeb.CoreComponents + + # Shortcut for generating JS commands + alias Phoenix.LiveView.JS + + # Routes generation with the ~p sigil + unquote(verified_routes()) + end + end + + def verified_routes do + quote do + use Phoenix.VerifiedRoutes, + endpoint: SttRecorderWeb.Endpoint, + router: SttRecorderWeb.Router, + statics: SttRecorderWeb.static_paths() + end + end + + @doc """ + When used, dispatch to the appropriate controller/live_view/etc. + """ + defmacro __using__(which) when is_atom(which) do + apply(__MODULE__, which, []) + end +end diff --git a/stt_recorder/lib/stt_recorder_web/components/core_components.ex b/stt_recorder/lib/stt_recorder_web/components/core_components.ex new file mode 100644 index 00000000..df968283 --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/components/core_components.ex @@ -0,0 +1,676 @@ +defmodule SttRecorderWeb.CoreComponents do + @moduledoc """ + Provides core UI components. + + At first glance, this module may seem daunting, but its goal is to provide + core building blocks for your application, such as modals, tables, and + forms. The components consist mostly of markup and are well-documented + with doc strings and declarative assigns. You may customize and style + them in any way you want, based on your application growth and needs. + + The default components use Tailwind CSS, a utility-first CSS framework. + See the [Tailwind CSS documentation](https://tailwindcss.com) to learn + how to customize them or feel free to swap in another framework altogether. + + Icons are provided by [heroicons](https://heroicons.com). See `icon/1` for usage. + """ + use Phoenix.Component + use Gettext, backend: SttRecorderWeb.Gettext + + alias Phoenix.LiveView.JS + + @doc """ + Renders a modal. + + ## Examples + + <.modal id="confirm-modal"> + This is a modal. + + + JS commands may be passed to the `:on_cancel` to configure + the closing/cancel event, for example: + + <.modal id="confirm" on_cancel={JS.navigate(~p"/posts")}> + This is another modal. + + + """ + attr :id, :string, required: true + attr :show, :boolean, default: false + attr :on_cancel, JS, default: %JS{} + slot :inner_block, required: true + + def modal(assigns) do + ~H""" + + """ + end + + def input(%{type: "select"} = assigns) do + ~H""" +
+ <.label for={@id}>{@label} + + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + def input(%{type: "textarea"} = assigns) do + ~H""" +
+ <.label for={@id}>{@label} + + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + # All other inputs text, datetime-local, url, password, etc. are handled here... + def input(assigns) do + ~H""" +
+ <.label for={@id}>{@label} + + <.error :for={msg <- @errors}>{msg} +
+ """ + end + + @doc """ + Renders a label. + """ + attr :for, :string, default: nil + slot :inner_block, required: true + + def label(assigns) do + ~H""" + + """ + end + + @doc """ + Generates a generic error message. + """ + slot :inner_block, required: true + + def error(assigns) do + ~H""" +

+ <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" /> + {render_slot(@inner_block)} +

+ """ + end + + @doc """ + Renders a header with title. + """ + attr :class, :string, default: nil + + slot :inner_block, required: true + slot :subtitle + slot :actions + + def header(assigns) do + ~H""" +
+
+

+ {render_slot(@inner_block)} +

+

+ {render_slot(@subtitle)} +

+
+
{render_slot(@actions)}
+
+ """ + end + + @doc ~S""" + Renders a table with generic styling. + + ## Examples + + <.table id="users" rows={@users}> + <:col :let={user} label="id">{user.id} + <:col :let={user} label="username">{user.username} + + """ + attr :id, :string, required: true + attr :rows, :list, required: true + attr :row_id, :any, default: nil, doc: "the function for generating the row id" + attr :row_click, :any, default: nil, doc: "the function for handling phx-click on each row" + + attr :row_item, :any, + default: &Function.identity/1, + doc: "the function for mapping each row before calling the :col and :action slots" + + slot :col, required: true do + attr :label, :string + end + + slot :action, doc: "the slot for showing user actions in the last table column" + + def table(assigns) do + assigns = + with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do + assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end) + end + + ~H""" +
+ + + + + + + + + + + + + +
{col[:label]} + {gettext("Actions")} +
+
+ + + {render_slot(col, @row_item.(row))} + +
+
+
+ + + {render_slot(action, @row_item.(row))} + +
+
+
+ """ + end + + @doc """ + Renders a data list. + + ## Examples + + <.list> + <:item title="Title">{@post.title} + <:item title="Views">{@post.views} + + """ + slot :item, required: true do + attr :title, :string, required: true + end + + def list(assigns) do + ~H""" +
+
+
+
{item.title}
+
{render_slot(item)}
+
+
+
+ """ + end + + @doc """ + Renders a back navigation link. + + ## Examples + + <.back navigate={~p"/posts"}>Back to posts + """ + attr :navigate, :any, required: true + slot :inner_block, required: true + + def back(assigns) do + ~H""" +
+ <.link + navigate={@navigate} + class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700" + > + <.icon name="hero-arrow-left-solid" class="h-3 w-3" /> + {render_slot(@inner_block)} + +
+ """ + end + + @doc """ + Renders a [Heroicon](https://heroicons.com). + + Heroicons come in three styles – outline, solid, and mini. + By default, the outline style is used, but solid and mini may + be applied by using the `-solid` and `-mini` suffix. + + You can customize the size and colors of the icons by setting + width, height, and background color classes. + + Icons are extracted from the `deps/heroicons` directory and bundled within + your compiled app.css by the plugin in your `assets/tailwind.config.js`. + + ## Examples + + <.icon name="hero-x-mark-solid" /> + <.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" /> + """ + attr :name, :string, required: true + attr :class, :string, default: nil + + def icon(%{name: "hero-" <> _} = assigns) do + ~H""" + + """ + end + + ## JS Commands + + def show(js \\ %JS{}, selector) do + JS.show(js, + to: selector, + time: 300, + transition: + {"transition-all transform ease-out duration-300", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95", + "opacity-100 translate-y-0 sm:scale-100"} + ) + end + + def hide(js \\ %JS{}, selector) do + JS.hide(js, + to: selector, + time: 200, + transition: + {"transition-all transform ease-in duration-200", + "opacity-100 translate-y-0 sm:scale-100", + "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"} + ) + end + + def show_modal(js \\ %JS{}, id) when is_binary(id) do + js + |> JS.show(to: "##{id}") + |> JS.show( + to: "##{id}-bg", + time: 300, + transition: {"transition-all transform ease-out duration-300", "opacity-0", "opacity-100"} + ) + |> show("##{id}-container") + |> JS.add_class("overflow-hidden", to: "body") + |> JS.focus_first(to: "##{id}-content") + end + + def hide_modal(js \\ %JS{}, id) do + js + |> JS.hide( + to: "##{id}-bg", + transition: {"transition-all transform ease-in duration-200", "opacity-100", "opacity-0"} + ) + |> hide("##{id}-container") + |> JS.hide(to: "##{id}", transition: {"block", "block", "hidden"}) + |> JS.remove_class("overflow-hidden", to: "body") + |> JS.pop_focus() + end + + @doc """ + Translates an error message using gettext. + """ + def translate_error({msg, opts}) do + # When using gettext, we typically pass the strings we want + # to translate as a static argument: + # + # # Translate the number of files with plural rules + # dngettext("errors", "1 file", "%{count} files", count) + # + # However the error messages in our forms and APIs are generated + # dynamically, so we need to translate them by calling Gettext + # with our gettext backend as first argument. Translations are + # available in the errors.po file (as we use the "errors" domain). + if count = opts[:count] do + Gettext.dngettext(SttRecorderWeb.Gettext, "errors", msg, msg, count, opts) + else + Gettext.dgettext(SttRecorderWeb.Gettext, "errors", msg, opts) + end + end + + @doc """ + Translates the errors for a field from a keyword list of errors. + """ + def translate_errors(errors, field) when is_list(errors) do + for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts}) + end +end diff --git a/stt_recorder/lib/stt_recorder_web/components/layouts.ex b/stt_recorder/lib/stt_recorder_web/components/layouts.ex new file mode 100644 index 00000000..b305c589 --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/components/layouts.ex @@ -0,0 +1,14 @@ +defmodule SttRecorderWeb.Layouts do + @moduledoc """ + This module holds different layouts used by your application. + + See the `layouts` directory for all templates available. + The "root" layout is a skeleton rendered as part of the + application router. The "app" layout is set as the default + layout on both `use SttRecorderWeb, :controller` and + `use SttRecorderWeb, :live_view`. + """ + use SttRecorderWeb, :html + + embed_templates "layouts/*" +end diff --git a/stt_recorder/lib/stt_recorder_web/components/layouts/app.html.heex b/stt_recorder/lib/stt_recorder_web/components/layouts/app.html.heex new file mode 100644 index 00000000..617ebdf3 --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/components/layouts/app.html.heex @@ -0,0 +1,6 @@ + +
+
+ {@inner_content} +
+
diff --git a/stt_recorder/lib/stt_recorder_web/components/layouts/root.html.heex b/stt_recorder/lib/stt_recorder_web/components/layouts/root.html.heex new file mode 100644 index 00000000..8072740a --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/components/layouts/root.html.heex @@ -0,0 +1,18 @@ + + + + + + + <.live_title default="SttRecorder" suffix=" · Phoenix Framework"> + {assigns[:page_title]} + + + + + + + {@inner_content} + + diff --git a/stt_recorder/lib/stt_recorder_web/controllers/error_html.ex b/stt_recorder/lib/stt_recorder_web/controllers/error_html.ex new file mode 100644 index 00000000..9bb1cc2a --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/controllers/error_html.ex @@ -0,0 +1,24 @@ +defmodule SttRecorderWeb.ErrorHTML do + @moduledoc """ + This module is invoked by your endpoint in case of errors on HTML requests. + + See config/config.exs. + """ + use SttRecorderWeb, :html + + # If you want to customize your error pages, + # uncomment the embed_templates/1 call below + # and add pages to the error directory: + # + # * lib/stt_recorder_web/controllers/error_html/404.html.heex + # * lib/stt_recorder_web/controllers/error_html/500.html.heex + # + # embed_templates "error_html/*" + + # The default is to render a plain text page based on + # the template name. For example, "404.html" becomes + # "Not Found". + def render(template, _assigns) do + Phoenix.Controller.status_message_from_template(template) + end +end diff --git a/stt_recorder/lib/stt_recorder_web/controllers/error_json.ex b/stt_recorder/lib/stt_recorder_web/controllers/error_json.ex new file mode 100644 index 00000000..ba79179f --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/controllers/error_json.ex @@ -0,0 +1,21 @@ +defmodule SttRecorderWeb.ErrorJSON do + @moduledoc """ + This module is invoked by your endpoint in case of errors on JSON requests. + + See config/config.exs. + """ + + # If you want to customize a particular status code, + # you may add your own clauses, such as: + # + # def render("500.json", _assigns) do + # %{errors: %{detail: "Internal Server Error"}} + # end + + # By default, Phoenix returns the status message from + # the template name. For example, "404.json" becomes + # "Not Found". + def render(template, _assigns) do + %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} + end +end diff --git a/stt_recorder/lib/stt_recorder_web/controllers/page_controller.ex b/stt_recorder/lib/stt_recorder_web/controllers/page_controller.ex new file mode 100644 index 00000000..e1b596ad --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/controllers/page_controller.ex @@ -0,0 +1,9 @@ +defmodule SttRecorderWeb.PageController do + use SttRecorderWeb, :controller + + def home(conn, _params) do + # The home page is often custom made, + # so skip the default app layout. + render(conn, :home, layout: false) + end +end diff --git a/stt_recorder/lib/stt_recorder_web/controllers/page_html.ex b/stt_recorder/lib/stt_recorder_web/controllers/page_html.ex new file mode 100644 index 00000000..a4112847 --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/controllers/page_html.ex @@ -0,0 +1,10 @@ +defmodule SttRecorderWeb.PageHTML do + @moduledoc """ + This module contains pages rendered by PageController. + + See the `page_html` directory for all templates available. + """ + use SttRecorderWeb, :html + + embed_templates "page_html/*" +end diff --git a/stt_recorder/lib/stt_recorder_web/controllers/page_html/home.html.heex b/stt_recorder/lib/stt_recorder_web/controllers/page_html/home.html.heex new file mode 100644 index 00000000..d72b03c2 --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/controllers/page_html/home.html.heex @@ -0,0 +1,222 @@ +<.flash_group flash={@flash} /> + +
diff --git a/stt_recorder/lib/stt_recorder_web/endpoint.ex b/stt_recorder/lib/stt_recorder_web/endpoint.ex new file mode 100644 index 00000000..9a289bef --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/endpoint.ex @@ -0,0 +1,52 @@ +defmodule SttRecorderWeb.Endpoint do + use Phoenix.Endpoint, otp_app: :stt_recorder + + # The session will be stored in the cookie and signed, + # this means its contents can be read but not tampered with. + # Set :encryption_salt if you would also like to encrypt it. + @session_options [ + store: :cookie, + key: "_stt_recorder_key", + signing_salt: "Xz3KHfj3", + same_site: "Lax" + ] + + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [session: @session_options]], + longpoll: [connect_info: [session: @session_options]] + + # Serve at "/" the static files from "priv/static" directory. + # + # You should set gzip to true if you are running phx.digest + # when deploying your static files in production. + plug Plug.Static, + at: "/", + from: :stt_recorder, + gzip: false, + only: SttRecorderWeb.static_paths() + + # Code reloading can be explicitly enabled under the + # :code_reloader configuration of your endpoint. + if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader + end + + plug Phoenix.LiveDashboard.RequestLogger, + param_key: "request_logger", + cookie_key: "request_logger" + + plug Plug.RequestId + plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] + + plug Plug.Parsers, + parsers: [:urlencoded, :multipart, :json], + pass: ["*/*"], + json_decoder: Phoenix.json_library() + + plug Plug.MethodOverride + plug Plug.Head + plug Plug.Session, @session_options + plug SttRecorderWeb.Router +end diff --git a/stt_recorder/lib/stt_recorder_web/gettext.ex b/stt_recorder/lib/stt_recorder_web/gettext.ex new file mode 100644 index 00000000..f691fda6 --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/gettext.ex @@ -0,0 +1,25 @@ +defmodule SttRecorderWeb.Gettext do + @moduledoc """ + A module providing Internationalization with a gettext-based API. + + By using [Gettext](https://hexdocs.pm/gettext), your module compiles translations + that you can use in your application. To use this Gettext backend module, + call `use Gettext` and pass it as an option: + + use Gettext, backend: SttRecorderWeb.Gettext + + # Simple translation + gettext("Here is the string to translate") + + # Plural translation + ngettext("Here is the string to translate", + "Here are the strings to translate", + 3) + + # Domain-based translation + dgettext("errors", "Here is the error message to translate") + + See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. + """ + use Gettext.Backend, otp_app: :stt_recorder +end diff --git a/stt_recorder/lib/stt_recorder_web/live/stt/test_recorder.ex b/stt_recorder/lib/stt_recorder_web/live/stt/test_recorder.ex new file mode 100644 index 00000000..195268b1 --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/live/stt/test_recorder.ex @@ -0,0 +1,160 @@ +defmodule SttRecorderWeb.Stt.TestRecorder do + use SttRecorderWeb, :live_view + + def mount(_params, _session, socket) do + {:ok, socket} + end + + def render(assigns) do + ~H""" +
+
Press "Start Recording"...
+ + +
+
+
+
+
+
+ +
+ """ + end +end diff --git a/stt_recorder/lib/stt_recorder_web/router.ex b/stt_recorder/lib/stt_recorder_web/router.ex new file mode 100644 index 00000000..ff72c50e --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/router.ex @@ -0,0 +1,47 @@ +defmodule SttRecorderWeb.Router do + use SttRecorderWeb, :router + + pipeline :browser do + plug :accepts, ["html"] + plug :fetch_session + plug :fetch_live_flash + plug :put_root_layout, html: {SttRecorderWeb.Layouts, :root} + plug :protect_from_forgery + plug :put_secure_browser_headers + end + + pipeline :api do + plug :accepts, ["json"] + end + + scope "/", SttRecorderWeb do + pipe_through :browser + + get "/", PageController, :home + live "/sttrecorder", Stt.SttLive + live "/test", Stt.TestRecorder + + end + + # Other scopes may use custom stacks. + # scope "/api", SttRecorderWeb do + # pipe_through :api + # end + + # Enable LiveDashboard and Swoosh mailbox preview in development + if Application.compile_env(:stt_recorder, :dev_routes) do + # If you want to use the LiveDashboard in production, you should put + # it behind authentication and allow only admins to access it. + # If your application does not have an admins-only section yet, + # you can use Plug.BasicAuth to set up some basic authentication + # as long as you are also using SSL (which you should anyway). + import Phoenix.LiveDashboard.Router + + scope "/dev" do + pipe_through :browser + + live_dashboard "/dashboard", metrics: SttRecorderWeb.Telemetry + forward "/mailbox", Plug.Swoosh.MailboxPreview + end + end +end diff --git a/stt_recorder/lib/stt_recorder_web/telemetry.ex b/stt_recorder/lib/stt_recorder_web/telemetry.ex new file mode 100644 index 00000000..31acde58 --- /dev/null +++ b/stt_recorder/lib/stt_recorder_web/telemetry.ex @@ -0,0 +1,70 @@ +defmodule SttRecorderWeb.Telemetry do + use Supervisor + import Telemetry.Metrics + + def start_link(arg) do + Supervisor.start_link(__MODULE__, arg, name: __MODULE__) + end + + @impl true + def init(_arg) do + children = [ + # Telemetry poller will execute the given period measurements + # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics + {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} + # Add reporters as children of your supervision tree. + # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} + ] + + Supervisor.init(children, strategy: :one_for_one) + end + + def metrics do + [ + # Phoenix Metrics + summary("phoenix.endpoint.start.system_time", + unit: {:native, :millisecond} + ), + summary("phoenix.endpoint.stop.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.start.system_time", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.exception.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.router_dispatch.stop.duration", + tags: [:route], + unit: {:native, :millisecond} + ), + summary("phoenix.socket_connected.duration", + unit: {:native, :millisecond} + ), + sum("phoenix.socket_drain.count"), + summary("phoenix.channel_joined.duration", + unit: {:native, :millisecond} + ), + summary("phoenix.channel_handled_in.duration", + tags: [:event], + unit: {:native, :millisecond} + ), + + # VM Metrics + summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.total_run_queue_lengths.total"), + summary("vm.total_run_queue_lengths.cpu"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp periodic_measurements do + [ + # A module, function and arguments to be invoked periodically. + # This function must call :telemetry.execute/3 and a metric must be added above. + # {SttRecorderWeb, :count_users, []} + ] + end +end diff --git a/stt_recorder/mix.exs b/stt_recorder/mix.exs new file mode 100644 index 00000000..d4b0df13 --- /dev/null +++ b/stt_recorder/mix.exs @@ -0,0 +1,79 @@ +defmodule SttRecorder.MixProject do + use Mix.Project + + def project do + [ + app: :stt_recorder, + version: "0.1.0", + elixir: "~> 1.14", + elixirc_paths: elixirc_paths(Mix.env()), + start_permanent: Mix.env() == :prod, + aliases: aliases(), + deps: deps() + ] + end + + # Configuration for the OTP application. + # + # Type `mix help compile.app` for more information. + def application do + [ + mod: {SttRecorder.Application, []}, + extra_applications: [:logger, :runtime_tools] + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Specifies your project dependencies. + # + # Type `mix help deps` for examples and options. + defp deps do + [ + {:phoenix, "~> 1.7.21"}, + {:phoenix_html, "~> 4.1"}, + {:phoenix_live_reload, "~> 1.2", only: :dev}, + {:phoenix_live_view, "~> 1.0"}, + {:floki, ">= 0.30.0", only: :test}, + {:phoenix_live_dashboard, "~> 0.8.3"}, + {:esbuild, "~> 0.8", runtime: Mix.env() == :dev}, + {:tailwind, "~> 0.2.0", runtime: Mix.env() == :dev}, + {:heroicons, + github: "tailwindlabs/heroicons", + tag: "v2.1.1", + sparse: "optimized", + app: false, + compile: false, + depth: 1}, + {:swoosh, "~> 1.5"}, + {:finch, "~> 0.13"}, + {:telemetry_metrics, "~> 1.0"}, + {:telemetry_poller, "~> 1.0"}, + {:gettext, "~> 0.26"}, + {:jason, "~> 1.2"}, + {:dns_cluster, "~> 0.1.1"}, + {:bandit, "~> 1.5"} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to install project dependencies and perform other setup tasks, run: + # + # $ mix setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + setup: ["deps.get", "assets.setup", "assets.build"], + "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"], + "assets.build": ["tailwind stt_recorder", "esbuild stt_recorder"], + "assets.deploy": [ + "tailwind stt_recorder --minify", + "esbuild stt_recorder --minify", + "phx.digest" + ] + ] + end +end diff --git a/stt_recorder/mix.lock b/stt_recorder/mix.lock new file mode 100644 index 00000000..42e5afc2 --- /dev/null +++ b/stt_recorder/mix.lock @@ -0,0 +1,35 @@ +%{ + "bandit": {:hex, :bandit, "1.7.0", "d1564f30553c97d3e25f9623144bb8df11f3787a26733f00b21699a128105c0c", [:mix], [{:hpax, "~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.18", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "3e2f7a98c7a11f48d9d8c037f7177cd39778e74d55c7af06fe6227c742a8168a"}, + "castore": {:hex, :castore, "1.0.14", "4582dd7d630b48cf5e1ca8d3d42494db51e406b7ba704e81fbd401866366896a", [:mix], [], "hexpm", "7bc1b65249d31701393edaaac18ec8398d8974d52c647b7904d01b964137b9f4"}, + "dns_cluster": {:hex, :dns_cluster, "0.1.3", "0bc20a2c88ed6cc494f2964075c359f8c2d00e1bf25518a6a6c7fd277c9b0c66", [:mix], [], "hexpm", "46cb7c4a1b3e52c7ad4cbe33ca5079fbde4840dedeafca2baf77996c2da1bc33"}, + "esbuild": {:hex, :esbuild, "0.10.0", "b0aa3388a1c23e727c5a3e7427c932d89ee791746b0081bbe56103e9ef3d291f", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "468489cda427b974a7cc9f03ace55368a83e1a7be12fba7e30969af78e5f8c70"}, + "expo": {:hex, :expo, "1.1.0", "f7b9ed7fb5745ebe1eeedf3d6f29226c5dd52897ac67c0f8af62a07e661e5c75", [:mix], [], "hexpm", "fbadf93f4700fb44c331362177bdca9eeb8097e8b0ef525c9cc501cb9917c960"}, + "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, + "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, + "floki": {:hex, :floki, "0.37.1", "d7aaee758c8a5b4a7495799a4260754fec5530d95b9c383c03b27359dea117cf", [:mix], [], "hexpm", "673d040cb594d31318d514590246b6dd587ed341d3b67e17c1c0eb8ce7ca6f04"}, + "gettext": {:hex, :gettext, "0.26.2", "5978aa7b21fada6deabf1f6341ddba50bc69c999e812211903b169799208f2a8", [:mix], [{:expo, "~> 0.5.1 or ~> 1.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "aa978504bcf76511efdc22d580ba08e2279caab1066b76bb9aa81c4a1e0a32a5"}, + "heroicons": {:git, "https://github.com/tailwindlabs/heroicons.git", "88ab3a0d790e6a47404cba02800a6b25d2afae50", [tag: "v2.1.1", sparse: "optimized", depth: 1]}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "phoenix": {:hex, :phoenix, "1.7.21", "14ca4f1071a5f65121217d6b57ac5712d1857e40a0833aff7a691b7870fc9a3b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "336dce4f86cba56fed312a7d280bf2282c720abb6074bdb1b61ec8095bdd0bc9"}, + "phoenix_html": {:hex, :phoenix_html, "4.2.1", "35279e2a39140068fc03f8874408d58eef734e488fc142153f055c5454fd1c08", [:mix], [], "hexpm", "cff108100ae2715dd959ae8f2a8cef8e20b593f8dfd031c9cba92702cf23e053"}, + "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.7", "405880012cb4b706f26dd1c6349125bfc903fb9e44d1ea668adaf4e04d4884b7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "3a8625cab39ec261d48a13b7468dc619c0ede099601b084e343968309bd4d7d7"}, + "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.6.0", "2791fac0e2776b640192308cc90c0dbcf67843ad51387ed4ecae2038263d708d", [:mix], [{:file_system, "~> 0.2.10 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "b3a1fa036d7eb2f956774eda7a7638cf5123f8f2175aca6d6420a7f95e598e1c"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.14", "621f075577e286ff1e67d6de085ddf6f364f934d229c1c5564be1ef4c77908b9", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b6dcb3f236044cd9d1c0d0996331bef72716b1991bbd8e0725a617c0d95a9483"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, + "plug": {:hex, :plug, "1.18.0", "d78df36c41f7e798f2edf1f33e1727eae438e9dd5d809a9997c463a108244042", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "819f9e176d51e44dc38132e132fe0accaf6767eab7f0303431e404da8476cfa2"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, + "swoosh": {:hex, :swoosh, "1.19.1", "77e839b27fc7af0704788e5854934c77d4dea7b437270c924a717513d598b8a4", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "eab57462d41a3330e82cb93a9d7640f5c79a85951f3457db25c1eb28fda193a6"}, + "tailwind": {:hex, :tailwind, "0.2.4", "5706ec47182d4e7045901302bf3a333e80f3d1af65c442ba9a9eed152fb26c2e", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "c6e4a82b8727bab593700c998a4d98cf3d8025678bfde059aed71d0000c3e463"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "telemetry_metrics": {:hex, :telemetry_metrics, "1.1.0", "5bd5f3b5637e0abea0426b947e3ce5dd304f8b3bc6617039e2b5a008adc02f8f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e7b79e8ddfde70adb6db8a6623d1778ec66401f366e9a8f5dd0955c56bc8ce67"}, + "telemetry_poller": {:hex, :telemetry_poller, "1.2.0", "ba82e333215aed9dd2096f93bd1d13ae89d249f82760fcada0850ba33bac154b", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7216e21a6c326eb9aa44328028c34e9fd348fb53667ca837be59d0aa2a0156e8"}, + "thousand_island": {:hex, :thousand_island, "1.3.14", "ad45ebed2577b5437582bcc79c5eccd1e2a8c326abf6a3464ab6c06e2055a34a", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d0d24a929d31cdd1d7903a4fe7f2409afeedff092d277be604966cd6aa4307ef"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"}, +} diff --git a/stt_recorder/priv/gettext/en/LC_MESSAGES/errors.po b/stt_recorder/priv/gettext/en/LC_MESSAGES/errors.po new file mode 100644 index 00000000..cdec3a11 --- /dev/null +++ b/stt_recorder/priv/gettext/en/LC_MESSAGES/errors.po @@ -0,0 +1,11 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: en\n" diff --git a/stt_recorder/priv/gettext/errors.pot b/stt_recorder/priv/gettext/errors.pot new file mode 100644 index 00000000..d6f47fa8 --- /dev/null +++ b/stt_recorder/priv/gettext/errors.pot @@ -0,0 +1,10 @@ +## This is a PO Template file. +## +## `msgid`s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run `mix gettext.extract` to bring this file up to +## date. Leave `msgstr`s empty as changing them here has no +## effect: edit them in PO (`.po`) files instead. + diff --git a/stt_recorder/priv/static/favicon-91f37b602a111216f1eef3aa337ad763.ico b/stt_recorder/priv/static/favicon-91f37b602a111216f1eef3aa337ad763.ico new file mode 100644 index 0000000000000000000000000000000000000000..7f372bfc21cdd8cb47585339d5fa4d9dd424402f GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=@t!V@Ar*{oFEH`~d50E!_s``s q?{G*w(7?#d#v@^nKnY_HKaYb01EZMZjMqTJ89ZJ6T-G@yGywoKK_h|y literal 0 HcmV?d00001 diff --git a/stt_recorder/priv/static/favicon.ico b/stt_recorder/priv/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7f372bfc21cdd8cb47585339d5fa4d9dd424402f GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=@t!V@Ar*{oFEH`~d50E!_s``s q?{G*w(7?#d#v@^nKnY_HKaYb01EZMZjMqTJ89ZJ6T-G@yGywoKK_h|y literal 0 HcmV?d00001 diff --git a/stt_recorder/priv/static/images/logo-06a11be1f2cdde2c851763d00bdd2e80.svg b/stt_recorder/priv/static/images/logo-06a11be1f2cdde2c851763d00bdd2e80.svg new file mode 100644 index 00000000..9f26baba --- /dev/null +++ b/stt_recorder/priv/static/images/logo-06a11be1f2cdde2c851763d00bdd2e80.svg @@ -0,0 +1,6 @@ + diff --git a/stt_recorder/priv/static/images/logo-06a11be1f2cdde2c851763d00bdd2e80.svg.gz b/stt_recorder/priv/static/images/logo-06a11be1f2cdde2c851763d00bdd2e80.svg.gz new file mode 100644 index 0000000000000000000000000000000000000000..2929d3b11bf718c21bc3cd53a039e0839b8125bb GIT binary patch literal 1613 zcmV-T2D14diwFP!000003Qd;JZXP!h#P5EJf%ZHUn{2Ylg=HYgBDZ}3-Gt9hY(TaH zMTzqG`>P%w7O+P%{P?H4x~iJ*|NQ&+Ve!p+E z)_Z2$9e;oM^!D@je;)4YQID|0*WK~km*?k)yW3wcFQ2}>{__3#`^(+&^z!BD{QTwP z$4}oL?p|O1`gHf<-EqACs)0|XW3Ydr?1KBvl{EW zZB~TObFhvj(>NunW_Qz$QxIPXEIULL;TePA5rvq- z+ZAh<+PY^@lA6cV(k%@_E#l`e7nW5?^{SnEpIPt6uN|@~`v6d=OVC5GPdzvS5|RS- zEv-ot3*}m(D5>%)si17BSo_AfsL_(^#aN_?aQ4y+V@(rvz^FrSp1YGgRD{|KIH>IPFwT+05laM|?E+ z2X9llN&v#tg|=%1wi7ot+5l~KnUqwA1jK?WvuQFwSaQiI6c$BxMxk@0H%0$xN?LyW z_#^9O6`oYf3LUXkwJhS4XUdRbsHBt&iI|_gQt@$9afNpXl&S}^E2+-8Q~o36!d839 z`YM|tuO-eKQQ|M-Vt3$l5sF(ZL9qB%a&V{!O{A{Wm;xv-(G4=MlJ2yJfU=&R&QlK2 zu>yxY>?d@)B@aZ9t~yNW;=Fj*@dMUCdS62dJCRmR=NO|ue#J@QsGUMSvuTTep8#7xIVAacUgMG zOe>*M;i8YumFO%_RiCnu4xomOd6Q)kfVu+Ti8%}u3^HJf)YJ|H17ka>w*;|C6dP5A z%NvKOV5_Q#t%+&gv$yAaRDo8nhSD*?0h=J*poZEGJ4|add25Dy|_Sc;H-Bf4LPYFel=WIH(3aq^w>!P=90O_?v<03eu~ z`kxOq5naL1pX^=A-_mVesEDF87*8|pCaPF&TJz4ehprucM&8U|>*#S&LtK~Bxh_dU z!m8z6Ydz5h`ByLhf>C)JsuKuBC2Ufcm$Y#@9U*N(PEjs(f@t&qUwdA6n$V>rO<2Po zVxPBcI^t{goV|1_0%c&?g1WA!T_$3_o|l_ayvSz9RY3hC6*jVMC?<&{t!}8NTvwlv zqmAx)-PE$#@