nocek 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (710) hide show
  1. nocek/__init__.py +3 -0
  2. nocek/__main__.py +5 -0
  3. nocek/cli.py +165 -0
  4. nocek/core/__init__.py +5 -0
  5. nocek/core/_data.py +13 -0
  6. nocek/core/audio.py +44 -0
  7. nocek/core/batdetect.py +320 -0
  8. nocek/core/battybird.py +383 -0
  9. nocek/core/consensus.py +190 -0
  10. nocek/core/data/batdetect2_classes.json +31 -0
  11. nocek/core/data/battybird_labels.json +34 -0
  12. nocek/core/data/comparison_groups.json +9 -0
  13. nocek/core/data/key_bands.json +24 -0
  14. nocek/core/data/regions.json +30 -0
  15. nocek/core/data/taxa.json +29 -0
  16. nocek/core/detection.py +336 -0
  17. nocek/core/key.py +159 -0
  18. nocek/core/nights.py +15 -0
  19. nocek/core/opinion.py +78 -0
  20. nocek/core/pipeline.py +194 -0
  21. nocek/core/region.py +116 -0
  22. nocek/core/spectrogram.py +70 -0
  23. nocek/core/taxonomy.py +77 -0
  24. nocek/icons/nocek.icns +0 -0
  25. nocek/icons/nocek.ico +0 -0
  26. nocek/icons/nocek.png +0 -0
  27. nocek/py.typed +0 -0
  28. nocek/recorders/__init__.py +5 -0
  29. nocek/recorders/base.py +108 -0
  30. nocek/recorders/checks.py +84 -0
  31. nocek/recorders/pippyg.py +98 -0
  32. nocek/recorders/registry.py +8 -0
  33. nocek/recorders/sun.py +89 -0
  34. nocek/shortcut.py +192 -0
  35. nocek/storage/__init__.py +1 -0
  36. nocek/storage/database.py +72 -0
  37. nocek/storage/importer.py +270 -0
  38. nocek/storage/library.py +72 -0
  39. nocek/storage/migrations/0001_initial.sql +92 -0
  40. nocek/storage/migrations/0002_site_numbers.sql +28 -0
  41. nocek/storage/repository.py +494 -0
  42. nocek/storage/serialize.py +304 -0
  43. nocek/storage/settings.py +123 -0
  44. nocek/storage/summaries.py +322 -0
  45. nocek/web/__init__.py +1 -0
  46. nocek/web/api.py +190 -0
  47. nocek/web/app.py +91 -0
  48. nocek/web/dialog.py +101 -0
  49. nocek/web/errors.py +25 -0
  50. nocek/web/export_api.py +152 -0
  51. nocek/web/folders.py +97 -0
  52. nocek/web/i18n.py +91 -0
  53. nocek/web/jobs.py +427 -0
  54. nocek/web/jobs_api.py +72 -0
  55. nocek/web/library_api.py +465 -0
  56. nocek/web/locales/de.json +426 -0
  57. nocek/web/locales/en.json +426 -0
  58. nocek/web/locales/pl.json +450 -0
  59. nocek/web/media.py +119 -0
  60. nocek/web/media_api.py +105 -0
  61. nocek/web/security.py +51 -0
  62. nocek/web/state.py +60 -0
  63. nocek/web/static/favicon.png +0 -0
  64. nocek/web/static/index.html +23 -0
  65. nocek/web/static/js/api.js +26 -0
  66. nocek/web/static/js/app.js +34 -0
  67. nocek/web/static/js/bat-colours.js +38 -0
  68. nocek/web/static/js/calendar.js +151 -0
  69. nocek/web/static/js/dialog.js +20 -0
  70. nocek/web/static/js/dom.js +35 -0
  71. nocek/web/static/js/folder-picker.js +71 -0
  72. nocek/web/static/js/header.js +80 -0
  73. nocek/web/static/js/i18n.js +69 -0
  74. nocek/web/static/js/jobs.js +122 -0
  75. nocek/web/static/js/magma.js +10 -0
  76. nocek/web/static/js/names.js +44 -0
  77. nocek/web/static/js/nightbar.js +330 -0
  78. nocek/web/static/js/notice.js +27 -0
  79. nocek/web/static/js/quit.js +43 -0
  80. nocek/web/static/js/router.js +33 -0
  81. nocek/web/static/js/spectrogram.js +175 -0
  82. nocek/web/static/js/theme-early.js +12 -0
  83. nocek/web/static/js/theme.js +28 -0
  84. nocek/web/static/js/views/devices.js +90 -0
  85. nocek/web/static/js/views/home.js +318 -0
  86. nocek/web/static/js/views/import.js +325 -0
  87. nocek/web/static/js/views/recording.js +277 -0
  88. nocek/web/static/js/views/settings.js +137 -0
  89. nocek/web/static/js/views/sites.js +177 -0
  90. nocek/web/static/js/views/welcome.js +88 -0
  91. nocek/web/static/style.css +464 -0
  92. nocek/web/static/vendor/README.txt +2 -0
  93. nocek/web/static/vendor/lucide/LICENSE +43 -0
  94. nocek/web/static/vendor/lucide/audio-lines.svg +20 -0
  95. nocek/web/static/vendor/lucide/check.svg +15 -0
  96. nocek/web/static/vendor/lucide/chevron-down.svg +15 -0
  97. nocek/web/static/vendor/lucide/chevron-left.svg +15 -0
  98. nocek/web/static/vendor/lucide/chevron-right.svg +15 -0
  99. nocek/web/static/vendor/lucide/circle-alert.svg +17 -0
  100. nocek/web/static/vendor/lucide/download.svg +17 -0
  101. nocek/web/static/vendor/lucide/ellipsis.svg +17 -0
  102. nocek/web/static/vendor/lucide/folder-open.svg +15 -0
  103. nocek/web/static/vendor/lucide/info.svg +17 -0
  104. nocek/web/static/vendor/lucide/languages.svg +20 -0
  105. nocek/web/static/vendor/lucide/map-pin.svg +16 -0
  106. nocek/web/static/vendor/lucide/monitor.svg +17 -0
  107. nocek/web/static/vendor/lucide/moon.svg +15 -0
  108. nocek/web/static/vendor/lucide/pause.svg +16 -0
  109. nocek/web/static/vendor/lucide/play.svg +15 -0
  110. nocek/web/static/vendor/lucide/power.svg +16 -0
  111. nocek/web/static/vendor/lucide/radio.svg +19 -0
  112. nocek/web/static/vendor/lucide/settings.svg +16 -0
  113. nocek/web/static/vendor/lucide/sun-moon.svg +19 -0
  114. nocek/web/static/vendor/lucide/sun.svg +23 -0
  115. nocek/web/static/vendor/lucide/x.svg +16 -0
  116. nocek/web/static/vendor/lucide/zoom-in.svg +18 -0
  117. nocek/web/static/vendor/lucide/zoom-out.svg +17 -0
  118. nocek/web/static/vendor/webawesome/LICENSE.md +7 -0
  119. nocek/web/static/vendor/webawesome/chunks/chunk.26QE47KB.js +12 -0
  120. nocek/web/static/vendor/webawesome/chunks/chunk.2BXLTQVW.js +41 -0
  121. nocek/web/static/vendor/webawesome/chunks/chunk.2E2VZMOR.js +121 -0
  122. nocek/web/static/vendor/webawesome/chunks/chunk.2FVCYQY4.js +30 -0
  123. nocek/web/static/vendor/webawesome/chunks/chunk.2LXKNNNE.js +13 -0
  124. nocek/web/static/vendor/webawesome/chunks/chunk.2NOGQWD2.js +64 -0
  125. nocek/web/static/vendor/webawesome/chunks/chunk.2NV6QW6C.js +127 -0
  126. nocek/web/static/vendor/webawesome/chunks/chunk.2OUDDW5N.js +34 -0
  127. nocek/web/static/vendor/webawesome/chunks/chunk.2QMPIFI4.js +211 -0
  128. nocek/web/static/vendor/webawesome/chunks/chunk.2SO23SX3.js +72 -0
  129. nocek/web/static/vendor/webawesome/chunks/chunk.2WDGLBJO.js +29 -0
  130. nocek/web/static/vendor/webawesome/chunks/chunk.2YEETXBR.js +112 -0
  131. nocek/web/static/vendor/webawesome/chunks/chunk.3774FK3E.js +29 -0
  132. nocek/web/static/vendor/webawesome/chunks/chunk.3BILHKLX.js +301 -0
  133. nocek/web/static/vendor/webawesome/chunks/chunk.3H2FQERJ.js +25 -0
  134. nocek/web/static/vendor/webawesome/chunks/chunk.3MALJW2P.js +29 -0
  135. nocek/web/static/vendor/webawesome/chunks/chunk.3MSWQ3RG.js +20 -0
  136. nocek/web/static/vendor/webawesome/chunks/chunk.3NKIHICW.js +12 -0
  137. nocek/web/static/vendor/webawesome/chunks/chunk.43IMJ6LM.js +29 -0
  138. nocek/web/static/vendor/webawesome/chunks/chunk.457WG26C.js +440 -0
  139. nocek/web/static/vendor/webawesome/chunks/chunk.46NHROTP.js +2239 -0
  140. nocek/web/static/vendor/webawesome/chunks/chunk.4AI6OCVC.js +261 -0
  141. nocek/web/static/vendor/webawesome/chunks/chunk.4EC7IRRU.js +353 -0
  142. nocek/web/static/vendor/webawesome/chunks/chunk.4IALDZ4Y.js +13 -0
  143. nocek/web/static/vendor/webawesome/chunks/chunk.4LIBWWJL.js +18 -0
  144. nocek/web/static/vendor/webawesome/chunks/chunk.4MQTK254.js +12 -0
  145. nocek/web/static/vendor/webawesome/chunks/chunk.4OSDOJJ4.js +312 -0
  146. nocek/web/static/vendor/webawesome/chunks/chunk.4UDEZ2KB.js +27 -0
  147. nocek/web/static/vendor/webawesome/chunks/chunk.4XYGFHPR.js +99 -0
  148. nocek/web/static/vendor/webawesome/chunks/chunk.4ZAKP7NY.js +12 -0
  149. nocek/web/static/vendor/webawesome/chunks/chunk.55AH5UAX.js +76 -0
  150. nocek/web/static/vendor/webawesome/chunks/chunk.5FPZSTUW.js +27 -0
  151. nocek/web/static/vendor/webawesome/chunks/chunk.5H6HB774.js +30 -0
  152. nocek/web/static/vendor/webawesome/chunks/chunk.5JBQCQFB.js +92 -0
  153. nocek/web/static/vendor/webawesome/chunks/chunk.5KKHTQSP.js +15 -0
  154. nocek/web/static/vendor/webawesome/chunks/chunk.5PFAYAIK.js +15 -0
  155. nocek/web/static/vendor/webawesome/chunks/chunk.5VKLVAP2.js +330 -0
  156. nocek/web/static/vendor/webawesome/chunks/chunk.5ZSSLK3M.js +68 -0
  157. nocek/web/static/vendor/webawesome/chunks/chunk.6CIAVEBC.js +65 -0
  158. nocek/web/static/vendor/webawesome/chunks/chunk.6FQNTW2R.js +32 -0
  159. nocek/web/static/vendor/webawesome/chunks/chunk.6J5ZEH6X.js +30 -0
  160. nocek/web/static/vendor/webawesome/chunks/chunk.6SNQOYNK.js +61 -0
  161. nocek/web/static/vendor/webawesome/chunks/chunk.6TNHHCAM.js +37 -0
  162. nocek/web/static/vendor/webawesome/chunks/chunk.6VIHFQLB.js +15 -0
  163. nocek/web/static/vendor/webawesome/chunks/chunk.6VPDWW2I.js +274 -0
  164. nocek/web/static/vendor/webawesome/chunks/chunk.6ZVWWDQT.js +30 -0
  165. nocek/web/static/vendor/webawesome/chunks/chunk.7HGYKBWW.js +94 -0
  166. nocek/web/static/vendor/webawesome/chunks/chunk.7QISORGK.js +29 -0
  167. nocek/web/static/vendor/webawesome/chunks/chunk.7RXXO7LR.js +27 -0
  168. nocek/web/static/vendor/webawesome/chunks/chunk.7ZCFTSGG.js +32 -0
  169. nocek/web/static/vendor/webawesome/chunks/chunk.A5622CLY.js +209 -0
  170. nocek/web/static/vendor/webawesome/chunks/chunk.A6PE4UC7.js +27 -0
  171. nocek/web/static/vendor/webawesome/chunks/chunk.AAHO5GAS.js +32 -0
  172. nocek/web/static/vendor/webawesome/chunks/chunk.AB2K5S2N.js +15 -0
  173. nocek/web/static/vendor/webawesome/chunks/chunk.AG44H7MD.js +12 -0
  174. nocek/web/static/vendor/webawesome/chunks/chunk.AJQ3S5VI.js +29 -0
  175. nocek/web/static/vendor/webawesome/chunks/chunk.AOZFN6E7.js +147 -0
  176. nocek/web/static/vendor/webawesome/chunks/chunk.AP3JK77D.js +184 -0
  177. nocek/web/static/vendor/webawesome/chunks/chunk.AQZVKFHG.js +33 -0
  178. nocek/web/static/vendor/webawesome/chunks/chunk.ASU4ZOK4.js +235 -0
  179. nocek/web/static/vendor/webawesome/chunks/chunk.B3QSBNFD.js +89 -0
  180. nocek/web/static/vendor/webawesome/chunks/chunk.B6O732C7.js +31 -0
  181. nocek/web/static/vendor/webawesome/chunks/chunk.BAKBSVVU.js +27 -0
  182. nocek/web/static/vendor/webawesome/chunks/chunk.BAY6RJD3.js +30 -0
  183. nocek/web/static/vendor/webawesome/chunks/chunk.BGEID5TE.js +70 -0
  184. nocek/web/static/vendor/webawesome/chunks/chunk.BKE5EYM3.js +274 -0
  185. nocek/web/static/vendor/webawesome/chunks/chunk.BLGT2UQZ.js +27 -0
  186. nocek/web/static/vendor/webawesome/chunks/chunk.BMO76VKZ.js +12 -0
  187. nocek/web/static/vendor/webawesome/chunks/chunk.BOYYUMGV.js +256 -0
  188. nocek/web/static/vendor/webawesome/chunks/chunk.BQNDCXAL.js +51 -0
  189. nocek/web/static/vendor/webawesome/chunks/chunk.BSKW43C6.js +247 -0
  190. nocek/web/static/vendor/webawesome/chunks/chunk.BUCKADPY.js +41 -0
  191. nocek/web/static/vendor/webawesome/chunks/chunk.BZMNT3VX.js +13 -0
  192. nocek/web/static/vendor/webawesome/chunks/chunk.C435GZCM.js +118 -0
  193. nocek/web/static/vendor/webawesome/chunks/chunk.CCRRQN4H.js +92 -0
  194. nocek/web/static/vendor/webawesome/chunks/chunk.CEY3XO3U.js +30 -0
  195. nocek/web/static/vendor/webawesome/chunks/chunk.CV35G2DF.js +305 -0
  196. nocek/web/static/vendor/webawesome/chunks/chunk.CXF4H6Y3.js +117 -0
  197. nocek/web/static/vendor/webawesome/chunks/chunk.CYF6RIMN.js +145 -0
  198. nocek/web/static/vendor/webawesome/chunks/chunk.D2XOEELP.js +27 -0
  199. nocek/web/static/vendor/webawesome/chunks/chunk.D5FEZLQU.js +42 -0
  200. nocek/web/static/vendor/webawesome/chunks/chunk.D5HA4OFX.js +189 -0
  201. nocek/web/static/vendor/webawesome/chunks/chunk.D5YFE5NT.js +1 -0
  202. nocek/web/static/vendor/webawesome/chunks/chunk.D6GXWYLR.js +93 -0
  203. nocek/web/static/vendor/webawesome/chunks/chunk.D7J2HJDE.js +12 -0
  204. nocek/web/static/vendor/webawesome/chunks/chunk.D7T5MUIJ.js +395 -0
  205. nocek/web/static/vendor/webawesome/chunks/chunk.DEXB7B6F.js +547 -0
  206. nocek/web/static/vendor/webawesome/chunks/chunk.DITA2S5A.js +80 -0
  207. nocek/web/static/vendor/webawesome/chunks/chunk.DL7HG4WM.js +23 -0
  208. nocek/web/static/vendor/webawesome/chunks/chunk.DMDEL2LT.js +136 -0
  209. nocek/web/static/vendor/webawesome/chunks/chunk.DOVAIU4B.js +264 -0
  210. nocek/web/static/vendor/webawesome/chunks/chunk.DP5YIFD7.js +13 -0
  211. nocek/web/static/vendor/webawesome/chunks/chunk.E2VSSWZB.js +30 -0
  212. nocek/web/static/vendor/webawesome/chunks/chunk.ECEQUBWG.js +29 -0
  213. nocek/web/static/vendor/webawesome/chunks/chunk.EF25YJJE.js +13 -0
  214. nocek/web/static/vendor/webawesome/chunks/chunk.EFKLEU6F.js +1922 -0
  215. nocek/web/static/vendor/webawesome/chunks/chunk.EFUXUR2V.js +34 -0
  216. nocek/web/static/vendor/webawesome/chunks/chunk.EIWUDXXU.js +1333 -0
  217. nocek/web/static/vendor/webawesome/chunks/chunk.EP5NMLNE.js +101 -0
  218. nocek/web/static/vendor/webawesome/chunks/chunk.EUQGFY4Z.js +338 -0
  219. nocek/web/static/vendor/webawesome/chunks/chunk.EV5QZWZG.js +155 -0
  220. nocek/web/static/vendor/webawesome/chunks/chunk.F25QOBDY.js +18 -0
  221. nocek/web/static/vendor/webawesome/chunks/chunk.F2PHWUN2.js +550 -0
  222. nocek/web/static/vendor/webawesome/chunks/chunk.F4JHOXPG.js +122 -0
  223. nocek/web/static/vendor/webawesome/chunks/chunk.FG2PKHMT.js +153 -0
  224. nocek/web/static/vendor/webawesome/chunks/chunk.FMGPE4WI.js +17 -0
  225. nocek/web/static/vendor/webawesome/chunks/chunk.FNFKITIN.js +25 -0
  226. nocek/web/static/vendor/webawesome/chunks/chunk.FV2UJLMK.js +15 -0
  227. nocek/web/static/vendor/webawesome/chunks/chunk.FYKN76UA.js +12 -0
  228. nocek/web/static/vendor/webawesome/chunks/chunk.G6XOOSBC.js +65 -0
  229. nocek/web/static/vendor/webawesome/chunks/chunk.GDKEEBXF.js +94 -0
  230. nocek/web/static/vendor/webawesome/chunks/chunk.GDWSLLNY.js +85 -0
  231. nocek/web/static/vendor/webawesome/chunks/chunk.GJBQN6LP.js +31 -0
  232. nocek/web/static/vendor/webawesome/chunks/chunk.GNSEW4OW.js +33 -0
  233. nocek/web/static/vendor/webawesome/chunks/chunk.GWSUX3V5.js +43 -0
  234. nocek/web/static/vendor/webawesome/chunks/chunk.GYYOD525.js +89 -0
  235. nocek/web/static/vendor/webawesome/chunks/chunk.H23DVATU.js +36 -0
  236. nocek/web/static/vendor/webawesome/chunks/chunk.H7TA73OO.js +13 -0
  237. nocek/web/static/vendor/webawesome/chunks/chunk.HD5OAVHA.js +34 -0
  238. nocek/web/static/vendor/webawesome/chunks/chunk.HE4U2ARF.js +228 -0
  239. nocek/web/static/vendor/webawesome/chunks/chunk.HGBRCPUS.js +55 -0
  240. nocek/web/static/vendor/webawesome/chunks/chunk.HJ65SLMP.js +51 -0
  241. nocek/web/static/vendor/webawesome/chunks/chunk.HJQBZY2G.js +372 -0
  242. nocek/web/static/vendor/webawesome/chunks/chunk.HKMBVADD.js +201 -0
  243. nocek/web/static/vendor/webawesome/chunks/chunk.HLGGDMDY.js +27 -0
  244. nocek/web/static/vendor/webawesome/chunks/chunk.HNCFWHCC.js +138 -0
  245. nocek/web/static/vendor/webawesome/chunks/chunk.HOKYDFUG.js +124 -0
  246. nocek/web/static/vendor/webawesome/chunks/chunk.HPULLNVR.js +12 -0
  247. nocek/web/static/vendor/webawesome/chunks/chunk.HU5FIEKB.js +173 -0
  248. nocek/web/static/vendor/webawesome/chunks/chunk.HYVJ2NPG.js +224 -0
  249. nocek/web/static/vendor/webawesome/chunks/chunk.I2Y4E7B6.js +94 -0
  250. nocek/web/static/vendor/webawesome/chunks/chunk.I7YQOYV2.js +30 -0
  251. nocek/web/static/vendor/webawesome/chunks/chunk.ICDCAGCX.js +24 -0
  252. nocek/web/static/vendor/webawesome/chunks/chunk.IHXCBN37.js +95 -0
  253. nocek/web/static/vendor/webawesome/chunks/chunk.IIHGIRPB.js +12 -0
  254. nocek/web/static/vendor/webawesome/chunks/chunk.IJOBO25X.js +32 -0
  255. nocek/web/static/vendor/webawesome/chunks/chunk.IOZQCLLD.js +26 -0
  256. nocek/web/static/vendor/webawesome/chunks/chunk.IPQSINJJ.js +32 -0
  257. nocek/web/static/vendor/webawesome/chunks/chunk.IX2GQ2J6.js +342 -0
  258. nocek/web/static/vendor/webawesome/chunks/chunk.IYKD6SDL.js +315 -0
  259. nocek/web/static/vendor/webawesome/chunks/chunk.J2WRH4YL.js +144 -0
  260. nocek/web/static/vendor/webawesome/chunks/chunk.J4UHUULC.js +184 -0
  261. nocek/web/static/vendor/webawesome/chunks/chunk.J7EXAHCE.js +167 -0
  262. nocek/web/static/vendor/webawesome/chunks/chunk.JATRSH7G.js +27 -0
  263. nocek/web/static/vendor/webawesome/chunks/chunk.JB5Y2AN3.js +34 -0
  264. nocek/web/static/vendor/webawesome/chunks/chunk.JBSQSXSP.js +108 -0
  265. nocek/web/static/vendor/webawesome/chunks/chunk.JHZRD2LV.js +60 -0
  266. nocek/web/static/vendor/webawesome/chunks/chunk.JJKKSYRO.js +37 -0
  267. nocek/web/static/vendor/webawesome/chunks/chunk.JOAOXII7.js +29 -0
  268. nocek/web/static/vendor/webawesome/chunks/chunk.JTOY5KP3.js +12 -0
  269. nocek/web/static/vendor/webawesome/chunks/chunk.JWREXNHC.js +30 -0
  270. nocek/web/static/vendor/webawesome/chunks/chunk.K5EDTD7G.js +48 -0
  271. nocek/web/static/vendor/webawesome/chunks/chunk.K5Q2EBKV.js +77 -0
  272. nocek/web/static/vendor/webawesome/chunks/chunk.KBLB7M2I.js +956 -0
  273. nocek/web/static/vendor/webawesome/chunks/chunk.KBS6YHTA.js +1 -0
  274. nocek/web/static/vendor/webawesome/chunks/chunk.KGCSF52Q.js +29 -0
  275. nocek/web/static/vendor/webawesome/chunks/chunk.KH7MR2OK.js +98 -0
  276. nocek/web/static/vendor/webawesome/chunks/chunk.KJ34WZZX.js +27 -0
  277. nocek/web/static/vendor/webawesome/chunks/chunk.KJKGFBJH.js +452 -0
  278. nocek/web/static/vendor/webawesome/chunks/chunk.KKI7M5DP.js +158 -0
  279. nocek/web/static/vendor/webawesome/chunks/chunk.KNPPWD7S.js +32 -0
  280. nocek/web/static/vendor/webawesome/chunks/chunk.KO4RVYVT.js +58 -0
  281. nocek/web/static/vendor/webawesome/chunks/chunk.KSVLZZAC.js +84 -0
  282. nocek/web/static/vendor/webawesome/chunks/chunk.KWDPKKFO.js +46 -0
  283. nocek/web/static/vendor/webawesome/chunks/chunk.L2PH6XPF.js +47 -0
  284. nocek/web/static/vendor/webawesome/chunks/chunk.L6CIKOFQ.js +55 -0
  285. nocek/web/static/vendor/webawesome/chunks/chunk.L7UUCOPZ.js +318 -0
  286. nocek/web/static/vendor/webawesome/chunks/chunk.LAIHMICP.js +241 -0
  287. nocek/web/static/vendor/webawesome/chunks/chunk.LBLI4KS5.js +353 -0
  288. nocek/web/static/vendor/webawesome/chunks/chunk.LCFSCRUJ.js +13 -0
  289. nocek/web/static/vendor/webawesome/chunks/chunk.LD7WPEY6.js +27 -0
  290. nocek/web/static/vendor/webawesome/chunks/chunk.LMUGFDHU.js +27 -0
  291. nocek/web/static/vendor/webawesome/chunks/chunk.LN7M2NWC.js +980 -0
  292. nocek/web/static/vendor/webawesome/chunks/chunk.LNFTFTSZ.js +77 -0
  293. nocek/web/static/vendor/webawesome/chunks/chunk.LOCSL3WJ.js +656 -0
  294. nocek/web/static/vendor/webawesome/chunks/chunk.LPHX3BNP.js +92 -0
  295. nocek/web/static/vendor/webawesome/chunks/chunk.LRYJ2M5H.js +24 -0
  296. nocek/web/static/vendor/webawesome/chunks/chunk.M3V3HAFX.js +339 -0
  297. nocek/web/static/vendor/webawesome/chunks/chunk.M6MK4JNZ.js +189 -0
  298. nocek/web/static/vendor/webawesome/chunks/chunk.M7O5VY4E.js +12 -0
  299. nocek/web/static/vendor/webawesome/chunks/chunk.MBU7SJFC.js +13 -0
  300. nocek/web/static/vendor/webawesome/chunks/chunk.MEATLWHD.js +1 -0
  301. nocek/web/static/vendor/webawesome/chunks/chunk.MF234WAF.js +13 -0
  302. nocek/web/static/vendor/webawesome/chunks/chunk.MGSBRQWA.js +27 -0
  303. nocek/web/static/vendor/webawesome/chunks/chunk.MHOTNIZ4.js +27 -0
  304. nocek/web/static/vendor/webawesome/chunks/chunk.MHY5F46I.js +417 -0
  305. nocek/web/static/vendor/webawesome/chunks/chunk.MO2KS3JW.js +239 -0
  306. nocek/web/static/vendor/webawesome/chunks/chunk.MOCFCD22.js +311 -0
  307. nocek/web/static/vendor/webawesome/chunks/chunk.MPFW6BBX.js +33 -0
  308. nocek/web/static/vendor/webawesome/chunks/chunk.MQODJ75V.js +13 -0
  309. nocek/web/static/vendor/webawesome/chunks/chunk.MT4KGKUD.js +29 -0
  310. nocek/web/static/vendor/webawesome/chunks/chunk.MYS3OD37.js +13 -0
  311. nocek/web/static/vendor/webawesome/chunks/chunk.N2TXQSKF.js +1 -0
  312. nocek/web/static/vendor/webawesome/chunks/chunk.NDZDW54F.js +287 -0
  313. nocek/web/static/vendor/webawesome/chunks/chunk.NH3YPZ5E.js +29 -0
  314. nocek/web/static/vendor/webawesome/chunks/chunk.NJV43B2Z.js +29 -0
  315. nocek/web/static/vendor/webawesome/chunks/chunk.NR2LFKJE.js +44 -0
  316. nocek/web/static/vendor/webawesome/chunks/chunk.NS4E73TC.js +15 -0
  317. nocek/web/static/vendor/webawesome/chunks/chunk.NSSMHWNA.js +13 -0
  318. nocek/web/static/vendor/webawesome/chunks/chunk.NT6HDXPW.js +29 -0
  319. nocek/web/static/vendor/webawesome/chunks/chunk.NWZSEH5M.js +122 -0
  320. nocek/web/static/vendor/webawesome/chunks/chunk.NY2PQ35L.js +13 -0
  321. nocek/web/static/vendor/webawesome/chunks/chunk.NY7ZFHLS.js +27 -0
  322. nocek/web/static/vendor/webawesome/chunks/chunk.NZNAY3HQ.js +303 -0
  323. nocek/web/static/vendor/webawesome/chunks/chunk.O3FF2LGE.js +29 -0
  324. nocek/web/static/vendor/webawesome/chunks/chunk.O6IZ4I7T.js +34 -0
  325. nocek/web/static/vendor/webawesome/chunks/chunk.ONMWPE2G.js +75 -0
  326. nocek/web/static/vendor/webawesome/chunks/chunk.OUXOGF7E.js +480 -0
  327. nocek/web/static/vendor/webawesome/chunks/chunk.OVHJANOP.js +29 -0
  328. nocek/web/static/vendor/webawesome/chunks/chunk.OZ5VT3R5.js +31 -0
  329. nocek/web/static/vendor/webawesome/chunks/chunk.P3CFHBKJ.js +27 -0
  330. nocek/web/static/vendor/webawesome/chunks/chunk.PIQFM4OJ.js +31 -0
  331. nocek/web/static/vendor/webawesome/chunks/chunk.PP5RFEZX.js +109 -0
  332. nocek/web/static/vendor/webawesome/chunks/chunk.PPALL6KG.js +169 -0
  333. nocek/web/static/vendor/webawesome/chunks/chunk.PSADQYP2.js +66 -0
  334. nocek/web/static/vendor/webawesome/chunks/chunk.PUN7URRY.js +290 -0
  335. nocek/web/static/vendor/webawesome/chunks/chunk.PX3HMKF7.js +12 -0
  336. nocek/web/static/vendor/webawesome/chunks/chunk.PZ23AOZF.js +166 -0
  337. nocek/web/static/vendor/webawesome/chunks/chunk.PZAN6FPN.js +32 -0
  338. nocek/web/static/vendor/webawesome/chunks/chunk.Q23D34AW.js +98 -0
  339. nocek/web/static/vendor/webawesome/chunks/chunk.Q4QAYIGQ.js +27 -0
  340. nocek/web/static/vendor/webawesome/chunks/chunk.Q6XMGFWJ.js +12 -0
  341. nocek/web/static/vendor/webawesome/chunks/chunk.QD7TM6E5.js +73 -0
  342. nocek/web/static/vendor/webawesome/chunks/chunk.QG644JIK.js +98 -0
  343. nocek/web/static/vendor/webawesome/chunks/chunk.QGF6GJ2E.js +227 -0
  344. nocek/web/static/vendor/webawesome/chunks/chunk.QKPMXIMG.js +27 -0
  345. nocek/web/static/vendor/webawesome/chunks/chunk.QNZSHXSH.js +27 -0
  346. nocek/web/static/vendor/webawesome/chunks/chunk.QVJVDGCX.js +364 -0
  347. nocek/web/static/vendor/webawesome/chunks/chunk.QXPWLPKE.js +29 -0
  348. nocek/web/static/vendor/webawesome/chunks/chunk.R62LX7HF.js +27 -0
  349. nocek/web/static/vendor/webawesome/chunks/chunk.R7QX4M6R.js +47 -0
  350. nocek/web/static/vendor/webawesome/chunks/chunk.REBCVBVJ.js +272 -0
  351. nocek/web/static/vendor/webawesome/chunks/chunk.REHZ4JRM.js +29 -0
  352. nocek/web/static/vendor/webawesome/chunks/chunk.RGZE6BBW.js +27 -0
  353. nocek/web/static/vendor/webawesome/chunks/chunk.RKPV5RWB.js +85 -0
  354. nocek/web/static/vendor/webawesome/chunks/chunk.RMZ7BVDM.js +10 -0
  355. nocek/web/static/vendor/webawesome/chunks/chunk.RO57OJZD.js +30 -0
  356. nocek/web/static/vendor/webawesome/chunks/chunk.ROEDV6HF.js +27 -0
  357. nocek/web/static/vendor/webawesome/chunks/chunk.RPQJAXXR.js +21 -0
  358. nocek/web/static/vendor/webawesome/chunks/chunk.RQUTRD4U.js +13 -0
  359. nocek/web/static/vendor/webawesome/chunks/chunk.RRKG3AQG.js +34 -0
  360. nocek/web/static/vendor/webawesome/chunks/chunk.RV5MEM4L.js +132 -0
  361. nocek/web/static/vendor/webawesome/chunks/chunk.RWNXKUCF.js +66 -0
  362. nocek/web/static/vendor/webawesome/chunks/chunk.RXATNCKH.js +363 -0
  363. nocek/web/static/vendor/webawesome/chunks/chunk.S3XWKRZZ.js +27 -0
  364. nocek/web/static/vendor/webawesome/chunks/chunk.SD34Q4G4.js +29 -0
  365. nocek/web/static/vendor/webawesome/chunks/chunk.SG72JPY2.js +81 -0
  366. nocek/web/static/vendor/webawesome/chunks/chunk.SJUGO6JB.js +105 -0
  367. nocek/web/static/vendor/webawesome/chunks/chunk.SKLR37OM.js +13 -0
  368. nocek/web/static/vendor/webawesome/chunks/chunk.SLTXS4GR.js +248 -0
  369. nocek/web/static/vendor/webawesome/chunks/chunk.SUZGY3IK.js +13 -0
  370. nocek/web/static/vendor/webawesome/chunks/chunk.SUZTEJNL.js +88 -0
  371. nocek/web/static/vendor/webawesome/chunks/chunk.SVFNJFHB.js +270 -0
  372. nocek/web/static/vendor/webawesome/chunks/chunk.SXR5N7XU.js +1376 -0
  373. nocek/web/static/vendor/webawesome/chunks/chunk.T6GGTYHD.js +13 -0
  374. nocek/web/static/vendor/webawesome/chunks/chunk.T7CCLJ4F.js +27 -0
  375. nocek/web/static/vendor/webawesome/chunks/chunk.TDR7XG37.js +30 -0
  376. nocek/web/static/vendor/webawesome/chunks/chunk.TLFIX76K.js +340 -0
  377. nocek/web/static/vendor/webawesome/chunks/chunk.TSARWN6S.js +275 -0
  378. nocek/web/static/vendor/webawesome/chunks/chunk.U36KZLSQ.js +12 -0
  379. nocek/web/static/vendor/webawesome/chunks/chunk.UDRJMWVZ.js +55 -0
  380. nocek/web/static/vendor/webawesome/chunks/chunk.UIKY7WPI.js +72 -0
  381. nocek/web/static/vendor/webawesome/chunks/chunk.UK2M7WPO.js +29 -0
  382. nocek/web/static/vendor/webawesome/chunks/chunk.UMPSPH77.js +45 -0
  383. nocek/web/static/vendor/webawesome/chunks/chunk.UOYQVA3T.js +27 -0
  384. nocek/web/static/vendor/webawesome/chunks/chunk.UUEMMZG6.js +178 -0
  385. nocek/web/static/vendor/webawesome/chunks/chunk.UV4Y72XP.js +37 -0
  386. nocek/web/static/vendor/webawesome/chunks/chunk.UVIXNJVD.js +428 -0
  387. nocek/web/static/vendor/webawesome/chunks/chunk.V3B4FDYO.js +715 -0
  388. nocek/web/static/vendor/webawesome/chunks/chunk.VC3BPUZJ.js +12 -0
  389. nocek/web/static/vendor/webawesome/chunks/chunk.VO4OKF3D.js +13 -0
  390. nocek/web/static/vendor/webawesome/chunks/chunk.VRQU2Y2Z.js +198 -0
  391. nocek/web/static/vendor/webawesome/chunks/chunk.VSYG3NGW.js +27 -0
  392. nocek/web/static/vendor/webawesome/chunks/chunk.VVLIN6UY.js +259 -0
  393. nocek/web/static/vendor/webawesome/chunks/chunk.W2C46OQX.js +353 -0
  394. nocek/web/static/vendor/webawesome/chunks/chunk.W3JJ6XED.js +127 -0
  395. nocek/web/static/vendor/webawesome/chunks/chunk.W55GO32K.js +932 -0
  396. nocek/web/static/vendor/webawesome/chunks/chunk.WC7A3EOY.js +741 -0
  397. nocek/web/static/vendor/webawesome/chunks/chunk.WCGXFNG4.js +95 -0
  398. nocek/web/static/vendor/webawesome/chunks/chunk.WDIIGUNP.js +12 -0
  399. nocek/web/static/vendor/webawesome/chunks/chunk.WGFDW2LC.js +25 -0
  400. nocek/web/static/vendor/webawesome/chunks/chunk.WKX3BKNK.js +18 -0
  401. nocek/web/static/vendor/webawesome/chunks/chunk.WLKYECLX.js +858 -0
  402. nocek/web/static/vendor/webawesome/chunks/chunk.WYNTFJHW.js +116 -0
  403. nocek/web/static/vendor/webawesome/chunks/chunk.X542U3B2.js +337 -0
  404. nocek/web/static/vendor/webawesome/chunks/chunk.X55YNZ3B.js +20 -0
  405. nocek/web/static/vendor/webawesome/chunks/chunk.X7T4SHKO.js +27 -0
  406. nocek/web/static/vendor/webawesome/chunks/chunk.XELEYQBQ.js +249 -0
  407. nocek/web/static/vendor/webawesome/chunks/chunk.XJ4PEGQL.js +135 -0
  408. nocek/web/static/vendor/webawesome/chunks/chunk.XJOHOSCS.js +345 -0
  409. nocek/web/static/vendor/webawesome/chunks/chunk.XLATYKDG.js +13 -0
  410. nocek/web/static/vendor/webawesome/chunks/chunk.XMNDC2OW.js +452 -0
  411. nocek/web/static/vendor/webawesome/chunks/chunk.XSPQ4QMK.js +29 -0
  412. nocek/web/static/vendor/webawesome/chunks/chunk.XU63UNPU.js +2183 -0
  413. nocek/web/static/vendor/webawesome/chunks/chunk.XW6BKGGI.js +13 -0
  414. nocek/web/static/vendor/webawesome/chunks/chunk.XZ235HUK.js +113 -0
  415. nocek/web/static/vendor/webawesome/chunks/chunk.XZPLJ4VW.js +13 -0
  416. nocek/web/static/vendor/webawesome/chunks/chunk.YBFCQDTA.js +13 -0
  417. nocek/web/static/vendor/webawesome/chunks/chunk.YD2JFSBQ.js +382 -0
  418. nocek/web/static/vendor/webawesome/chunks/chunk.YDQCS2HK.js +12 -0
  419. nocek/web/static/vendor/webawesome/chunks/chunk.YJNHQL5U.js +27 -0
  420. nocek/web/static/vendor/webawesome/chunks/chunk.YLIESRP2.js +377 -0
  421. nocek/web/static/vendor/webawesome/chunks/chunk.YMGBKPRR.js +32 -0
  422. nocek/web/static/vendor/webawesome/chunks/chunk.YN7QRSZ7.js +170 -0
  423. nocek/web/static/vendor/webawesome/chunks/chunk.YNIJJMXK.js +112 -0
  424. nocek/web/static/vendor/webawesome/chunks/chunk.YPRC5XZF.js +101 -0
  425. nocek/web/static/vendor/webawesome/chunks/chunk.YQNBAO2Z.js +45 -0
  426. nocek/web/static/vendor/webawesome/chunks/chunk.YXOWVBUA.js +19 -0
  427. nocek/web/static/vendor/webawesome/chunks/chunk.Z4KHDJB4.js +173 -0
  428. nocek/web/static/vendor/webawesome/chunks/chunk.Z6LULOQL.js +97 -0
  429. nocek/web/static/vendor/webawesome/chunks/chunk.Z73LSB5Z.js +27 -0
  430. nocek/web/static/vendor/webawesome/chunks/chunk.ZA5G6FAT.js +27 -0
  431. nocek/web/static/vendor/webawesome/chunks/chunk.ZFMOLE6Z.js +27 -0
  432. nocek/web/static/vendor/webawesome/chunks/chunk.ZFSRFTCP.js +83 -0
  433. nocek/web/static/vendor/webawesome/chunks/chunk.ZSEFTQAO.js +12 -0
  434. nocek/web/static/vendor/webawesome/chunks/chunk.ZSNU3QL4.js +78 -0
  435. nocek/web/static/vendor/webawesome/chunks/chunk.ZT4OZS6F.js +12 -0
  436. nocek/web/static/vendor/webawesome/chunks/chunk.ZWQCGLB5.js +12 -0
  437. nocek/web/static/vendor/webawesome/chunks/chunk.ZY7BMFLO.js +13 -0
  438. nocek/web/static/vendor/webawesome/chunks/chunk.ZZPJSMJ4.js +13 -0
  439. nocek/web/static/vendor/webawesome/components/accordion/accordion.js +39 -0
  440. nocek/web/static/vendor/webawesome/components/accordion/accordion.styles.js +10 -0
  441. nocek/web/static/vendor/webawesome/components/accordion-item/accordion-item.js +33 -0
  442. nocek/web/static/vendor/webawesome/components/accordion-item/accordion-item.styles.js +10 -0
  443. nocek/web/static/vendor/webawesome/components/animated-image/animated-image.js +28 -0
  444. nocek/web/static/vendor/webawesome/components/animated-image/animated-image.styles.js +10 -0
  445. nocek/web/static/vendor/webawesome/components/animation/animation.js +17 -0
  446. nocek/web/static/vendor/webawesome/components/animation/animation.styles.js +10 -0
  447. nocek/web/static/vendor/webawesome/components/animation/animations.js +12 -0
  448. nocek/web/static/vendor/webawesome/components/avatar/avatar.js +23 -0
  449. nocek/web/static/vendor/webawesome/components/avatar/avatar.styles.js +10 -0
  450. nocek/web/static/vendor/webawesome/components/badge/badge.js +13 -0
  451. nocek/web/static/vendor/webawesome/components/badge/badge.styles.js +10 -0
  452. nocek/web/static/vendor/webawesome/components/breadcrumb/breadcrumb.js +26 -0
  453. nocek/web/static/vendor/webawesome/components/breadcrumb/breadcrumb.styles.js +10 -0
  454. nocek/web/static/vendor/webawesome/components/breadcrumb-item/breadcrumb-item.js +14 -0
  455. nocek/web/static/vendor/webawesome/components/breadcrumb-item/breadcrumb-item.styles.js +10 -0
  456. nocek/web/static/vendor/webawesome/components/button/button.js +38 -0
  457. nocek/web/static/vendor/webawesome/components/button/button.styles.js +10 -0
  458. nocek/web/static/vendor/webawesome/components/button-group/button-group.js +12 -0
  459. nocek/web/static/vendor/webawesome/components/button-group/button-group.styles.js +10 -0
  460. nocek/web/static/vendor/webawesome/components/callout/callout.js +16 -0
  461. nocek/web/static/vendor/webawesome/components/callout/callout.styles.js +10 -0
  462. nocek/web/static/vendor/webawesome/components/card/card.js +16 -0
  463. nocek/web/static/vendor/webawesome/components/card/card.styles.js +10 -0
  464. nocek/web/static/vendor/webawesome/components/carousel/autoplay-controller.js +8 -0
  465. nocek/web/static/vendor/webawesome/components/carousel/carousel.js +34 -0
  466. nocek/web/static/vendor/webawesome/components/carousel/carousel.styles.js +10 -0
  467. nocek/web/static/vendor/webawesome/components/carousel/scroll-controller.js +110 -0
  468. nocek/web/static/vendor/webawesome/components/carousel-item/carousel-item.js +12 -0
  469. nocek/web/static/vendor/webawesome/components/carousel-item/carousel-item.styles.js +10 -0
  470. nocek/web/static/vendor/webawesome/components/checkbox/checkbox.js +34 -0
  471. nocek/web/static/vendor/webawesome/components/checkbox/checkbox.styles.js +10 -0
  472. nocek/web/static/vendor/webawesome/components/checkbox-group/checkbox-group.js +36 -0
  473. nocek/web/static/vendor/webawesome/components/checkbox-group/checkbox-group.styles.js +10 -0
  474. nocek/web/static/vendor/webawesome/components/color-picker/color-picker.js +59 -0
  475. nocek/web/static/vendor/webawesome/components/color-picker/color-picker.styles.js +10 -0
  476. nocek/web/static/vendor/webawesome/components/comparison/comparison.js +30 -0
  477. nocek/web/static/vendor/webawesome/components/comparison/comparison.styles.js +10 -0
  478. nocek/web/static/vendor/webawesome/components/copy-button/copy-button.js +44 -0
  479. nocek/web/static/vendor/webawesome/components/copy-button/copy-button.styles.js +10 -0
  480. nocek/web/static/vendor/webawesome/components/details/details.js +36 -0
  481. nocek/web/static/vendor/webawesome/components/details/details.styles.js +10 -0
  482. nocek/web/static/vendor/webawesome/components/dialog/dialog.js +49 -0
  483. nocek/web/static/vendor/webawesome/components/dialog/dialog.styles.js +10 -0
  484. nocek/web/static/vendor/webawesome/components/divider/divider.js +14 -0
  485. nocek/web/static/vendor/webawesome/components/divider/divider.styles.js +10 -0
  486. nocek/web/static/vendor/webawesome/components/drawer/drawer.js +49 -0
  487. nocek/web/static/vendor/webawesome/components/drawer/drawer.styles.js +10 -0
  488. nocek/web/static/vendor/webawesome/components/dropdown/dropdown.js +46 -0
  489. nocek/web/static/vendor/webawesome/components/dropdown/dropdown.styles.js +10 -0
  490. nocek/web/static/vendor/webawesome/components/dropdown-item/dropdown-item.js +27 -0
  491. nocek/web/static/vendor/webawesome/components/dropdown-item/dropdown-item.styles.js +10 -0
  492. nocek/web/static/vendor/webawesome/components/format-bytes/format-bytes.js +14 -0
  493. nocek/web/static/vendor/webawesome/components/format-date/format-date.js +14 -0
  494. nocek/web/static/vendor/webawesome/components/format-number/format-number.js +14 -0
  495. nocek/web/static/vendor/webawesome/components/icon/icon.js +21 -0
  496. nocek/web/static/vendor/webawesome/components/icon/icon.styles.js +10 -0
  497. nocek/web/static/vendor/webawesome/components/icon/library.default.js +11 -0
  498. nocek/web/static/vendor/webawesome/components/icon/library.js +23 -0
  499. nocek/web/static/vendor/webawesome/components/icon/library.system.js +10 -0
  500. nocek/web/static/vendor/webawesome/components/include/include.js +16 -0
  501. nocek/web/static/vendor/webawesome/components/include/include.styles.js +10 -0
  502. nocek/web/static/vendor/webawesome/components/include/request.js +8 -0
  503. nocek/web/static/vendor/webawesome/components/input/input.js +39 -0
  504. nocek/web/static/vendor/webawesome/components/input/input.styles.js +10 -0
  505. nocek/web/static/vendor/webawesome/components/intersection-observer/intersection-observer.js +16 -0
  506. nocek/web/static/vendor/webawesome/components/intersection-observer/intersection-observer.styles.js +10 -0
  507. nocek/web/static/vendor/webawesome/components/known-date/internal/field-order.js +8 -0
  508. nocek/web/static/vendor/webawesome/components/known-date/internal/partial-date-validator.js +8 -0
  509. nocek/web/static/vendor/webawesome/components/known-date/internal/parts.js +16 -0
  510. nocek/web/static/vendor/webawesome/components/known-date/known-date.js +33 -0
  511. nocek/web/static/vendor/webawesome/components/known-date/known-date.styles.js +10 -0
  512. nocek/web/static/vendor/webawesome/components/markdown/markdown.js +12 -0
  513. nocek/web/static/vendor/webawesome/components/markdown/markdown.styles.js +10 -0
  514. nocek/web/static/vendor/webawesome/components/mutation-observer/mutation-observer.js +14 -0
  515. nocek/web/static/vendor/webawesome/components/mutation-observer/mutation-observer.styles.js +10 -0
  516. nocek/web/static/vendor/webawesome/components/number-input/number-input.js +38 -0
  517. nocek/web/static/vendor/webawesome/components/number-input/number-input.styles.js +10 -0
  518. nocek/web/static/vendor/webawesome/components/option/option.js +26 -0
  519. nocek/web/static/vendor/webawesome/components/option/option.styles.js +10 -0
  520. nocek/web/static/vendor/webawesome/components/otp-input/otp-input.js +28 -0
  521. nocek/web/static/vendor/webawesome/components/otp-input/otp-input.styles.js +10 -0
  522. nocek/web/static/vendor/webawesome/components/page/page.js +56 -0
  523. nocek/web/static/vendor/webawesome/components/page/page.mobile.styles.js +8 -0
  524. nocek/web/static/vendor/webawesome/components/page/page.styles.js +10 -0
  525. nocek/web/static/vendor/webawesome/components/pagination/pagination.js +34 -0
  526. nocek/web/static/vendor/webawesome/components/pagination/pagination.styles.js +10 -0
  527. nocek/web/static/vendor/webawesome/components/popover/popover.js +29 -0
  528. nocek/web/static/vendor/webawesome/components/popover/popover.styles.js +10 -0
  529. nocek/web/static/vendor/webawesome/components/popup/popup.js +18 -0
  530. nocek/web/static/vendor/webawesome/components/popup/popup.styles.js +10 -0
  531. nocek/web/static/vendor/webawesome/components/progress-bar/progress-bar.js +17 -0
  532. nocek/web/static/vendor/webawesome/components/progress-bar/progress-bar.styles.js +10 -0
  533. nocek/web/static/vendor/webawesome/components/progress-ring/progress-ring.js +17 -0
  534. nocek/web/static/vendor/webawesome/components/progress-ring/progress-ring.styles.js +10 -0
  535. nocek/web/static/vendor/webawesome/components/qr-code/qr-code.js +14 -0
  536. nocek/web/static/vendor/webawesome/components/qr-code/qr-code.styles.js +10 -0
  537. nocek/web/static/vendor/webawesome/components/radio/radio.js +28 -0
  538. nocek/web/static/vendor/webawesome/components/radio/radio.styles.js +10 -0
  539. nocek/web/static/vendor/webawesome/components/radio-group/radio-group.js +35 -0
  540. nocek/web/static/vendor/webawesome/components/radio-group/radio-group.styles.js +10 -0
  541. nocek/web/static/vendor/webawesome/components/random-content/random-content.js +17 -0
  542. nocek/web/static/vendor/webawesome/components/random-content/random-content.styles.js +10 -0
  543. nocek/web/static/vendor/webawesome/components/rating/rating.js +37 -0
  544. nocek/web/static/vendor/webawesome/components/rating/rating.styles.js +10 -0
  545. nocek/web/static/vendor/webawesome/components/relative-time/relative-time.js +14 -0
  546. nocek/web/static/vendor/webawesome/components/resize-observer/resize-observer.js +14 -0
  547. nocek/web/static/vendor/webawesome/components/resize-observer/resize-observer.styles.js +10 -0
  548. nocek/web/static/vendor/webawesome/components/scroller/scroller.js +15 -0
  549. nocek/web/static/vendor/webawesome/components/scroller/scroller.styles.js +10 -0
  550. nocek/web/static/vendor/webawesome/components/select/select.js +60 -0
  551. nocek/web/static/vendor/webawesome/components/select/select.styles.js +10 -0
  552. nocek/web/static/vendor/webawesome/components/skeleton/skeleton.js +12 -0
  553. nocek/web/static/vendor/webawesome/components/skeleton/skeleton.styles.js +10 -0
  554. nocek/web/static/vendor/webawesome/components/slider/slider.js +41 -0
  555. nocek/web/static/vendor/webawesome/components/slider/slider.styles.js +10 -0
  556. nocek/web/static/vendor/webawesome/components/spinner/spinner.js +15 -0
  557. nocek/web/static/vendor/webawesome/components/spinner/spinner.styles.js +10 -0
  558. nocek/web/static/vendor/webawesome/components/split-panel/split-panel.js +20 -0
  559. nocek/web/static/vendor/webawesome/components/split-panel/split-panel.styles.js +10 -0
  560. nocek/web/static/vendor/webawesome/components/step/step.js +33 -0
  561. nocek/web/static/vendor/webawesome/components/step/step.styles.js +10 -0
  562. nocek/web/static/vendor/webawesome/components/stepper/stepper.js +39 -0
  563. nocek/web/static/vendor/webawesome/components/stepper/stepper.styles.js +10 -0
  564. nocek/web/static/vendor/webawesome/components/switch/switch.js +28 -0
  565. nocek/web/static/vendor/webawesome/components/switch/switch.styles.js +10 -0
  566. nocek/web/static/vendor/webawesome/components/tab/tab.js +15 -0
  567. nocek/web/static/vendor/webawesome/components/tab/tab.styles.js +10 -0
  568. nocek/web/static/vendor/webawesome/components/tab-group/tab-group.js +47 -0
  569. nocek/web/static/vendor/webawesome/components/tab-group/tab-group.styles.js +10 -0
  570. nocek/web/static/vendor/webawesome/components/tab-panel/tab-panel.js +15 -0
  571. nocek/web/static/vendor/webawesome/components/tab-panel/tab-panel.styles.js +10 -0
  572. nocek/web/static/vendor/webawesome/components/tag/tag.js +41 -0
  573. nocek/web/static/vendor/webawesome/components/tag/tag.styles.js +10 -0
  574. nocek/web/static/vendor/webawesome/components/tag-input/tag-input.js +50 -0
  575. nocek/web/static/vendor/webawesome/components/tag-input/tag-input.styles.js +10 -0
  576. nocek/web/static/vendor/webawesome/components/textarea/textarea.js +29 -0
  577. nocek/web/static/vendor/webawesome/components/textarea/textarea.styles.js +10 -0
  578. nocek/web/static/vendor/webawesome/components/time-input/internal/time-segments.js +38 -0
  579. nocek/web/static/vendor/webawesome/components/time-input/time-input.js +39 -0
  580. nocek/web/static/vendor/webawesome/components/time-input/time-input.styles.js +10 -0
  581. nocek/web/static/vendor/webawesome/components/toast/toast.js +43 -0
  582. nocek/web/static/vendor/webawesome/components/toast/toast.styles.js +10 -0
  583. nocek/web/static/vendor/webawesome/components/toast-item/toast-item.js +40 -0
  584. nocek/web/static/vendor/webawesome/components/toast-item/toast-item.styles.js +10 -0
  585. nocek/web/static/vendor/webawesome/components/tooltip/tooltip.js +29 -0
  586. nocek/web/static/vendor/webawesome/components/tooltip/tooltip.styles.js +10 -0
  587. nocek/web/static/vendor/webawesome/components/tree/tree.js +53 -0
  588. nocek/web/static/vendor/webawesome/components/tree/tree.styles.js +10 -0
  589. nocek/web/static/vendor/webawesome/components/tree-item/tree-item.js +51 -0
  590. nocek/web/static/vendor/webawesome/components/tree-item/tree-item.styles.js +10 -0
  591. nocek/web/static/vendor/webawesome/components/zoomable-frame/zoomable-frame.js +28 -0
  592. nocek/web/static/vendor/webawesome/components/zoomable-frame/zoomable-frame.styles.js +10 -0
  593. nocek/web/static/vendor/webawesome/events/accordion-after-collapse.js +8 -0
  594. nocek/web/static/vendor/webawesome/events/accordion-after-expand.js +8 -0
  595. nocek/web/static/vendor/webawesome/events/accordion-collapse.js +8 -0
  596. nocek/web/static/vendor/webawesome/events/accordion-expand.js +8 -0
  597. nocek/web/static/vendor/webawesome/events/accordion-item-collapsed.js +8 -0
  598. nocek/web/static/vendor/webawesome/events/accordion-item-expanded.js +8 -0
  599. nocek/web/static/vendor/webawesome/events/accordion-item-trigger.js +8 -0
  600. nocek/web/static/vendor/webawesome/events/after-collapse.js +8 -0
  601. nocek/web/static/vendor/webawesome/events/after-expand.js +8 -0
  602. nocek/web/static/vendor/webawesome/events/after-hide.js +8 -0
  603. nocek/web/static/vendor/webawesome/events/after-show.js +8 -0
  604. nocek/web/static/vendor/webawesome/events/before-page-change.js +8 -0
  605. nocek/web/static/vendor/webawesome/events/before-step-change.js +8 -0
  606. nocek/web/static/vendor/webawesome/events/cancel.js +8 -0
  607. nocek/web/static/vendor/webawesome/events/cell-click.js +13 -0
  608. nocek/web/static/vendor/webawesome/events/cell-context-menu.js +13 -0
  609. nocek/web/static/vendor/webawesome/events/clear.js +8 -0
  610. nocek/web/static/vendor/webawesome/events/collapse.js +8 -0
  611. nocek/web/static/vendor/webawesome/events/column-move.js +13 -0
  612. nocek/web/static/vendor/webawesome/events/column-pin.js +13 -0
  613. nocek/web/static/vendor/webawesome/events/column-resize.js +13 -0
  614. nocek/web/static/vendor/webawesome/events/column-visibility-change.js +13 -0
  615. nocek/web/static/vendor/webawesome/events/complete.js +8 -0
  616. nocek/web/static/vendor/webawesome/events/copy.js +8 -0
  617. nocek/web/static/vendor/webawesome/events/create.js +8 -0
  618. nocek/web/static/vendor/webawesome/events/data-error.js +13 -0
  619. nocek/web/static/vendor/webawesome/events/data-request.js +13 -0
  620. nocek/web/static/vendor/webawesome/events/error.js +8 -0
  621. nocek/web/static/vendor/webawesome/events/events.js +2 -0
  622. nocek/web/static/vendor/webawesome/events/expand.js +8 -0
  623. nocek/web/static/vendor/webawesome/events/filter-change.js +13 -0
  624. nocek/web/static/vendor/webawesome/events/finish.js +8 -0
  625. nocek/web/static/vendor/webawesome/events/focus-day.js +13 -0
  626. nocek/web/static/vendor/webawesome/events/hide.js +8 -0
  627. nocek/web/static/vendor/webawesome/events/hover.js +8 -0
  628. nocek/web/static/vendor/webawesome/events/include-error.js +8 -0
  629. nocek/web/static/vendor/webawesome/events/intersect.js +8 -0
  630. nocek/web/static/vendor/webawesome/events/invalid.js +8 -0
  631. nocek/web/static/vendor/webawesome/events/lazy-change.js +8 -0
  632. nocek/web/static/vendor/webawesome/events/lazy-load.js +8 -0
  633. nocek/web/static/vendor/webawesome/events/load.js +8 -0
  634. nocek/web/static/vendor/webawesome/events/mutation.js +8 -0
  635. nocek/web/static/vendor/webawesome/events/options-error.js +13 -0
  636. nocek/web/static/vendor/webawesome/events/options-request.js +13 -0
  637. nocek/web/static/vendor/webawesome/events/page-change.js +8 -0
  638. nocek/web/static/vendor/webawesome/events/random-content-change.js +8 -0
  639. nocek/web/static/vendor/webawesome/events/remove.js +8 -0
  640. nocek/web/static/vendor/webawesome/events/reposition.js +8 -0
  641. nocek/web/static/vendor/webawesome/events/resize.js +8 -0
  642. nocek/web/static/vendor/webawesome/events/row-collapse.js +13 -0
  643. nocek/web/static/vendor/webawesome/events/row-expand.js +13 -0
  644. nocek/web/static/vendor/webawesome/events/row-select.js +13 -0
  645. nocek/web/static/vendor/webawesome/events/select.js +8 -0
  646. nocek/web/static/vendor/webawesome/events/selection-change.js +8 -0
  647. nocek/web/static/vendor/webawesome/events/show.js +8 -0
  648. nocek/web/static/vendor/webawesome/events/slide-change.js +8 -0
  649. nocek/web/static/vendor/webawesome/events/sort-change.js +13 -0
  650. nocek/web/static/vendor/webawesome/events/start.js +8 -0
  651. nocek/web/static/vendor/webawesome/events/step-change.js +8 -0
  652. nocek/web/static/vendor/webawesome/events/tab-hide.js +8 -0
  653. nocek/web/static/vendor/webawesome/events/tab-show.js +8 -0
  654. nocek/web/static/vendor/webawesome/events/video-change.js +13 -0
  655. nocek/web/static/vendor/webawesome/events/view-change.js +13 -0
  656. nocek/web/static/vendor/webawesome/styles/color/palettes/base.css +44 -0
  657. nocek/web/static/vendor/webawesome/styles/color/palettes/bright.css +145 -0
  658. nocek/web/static/vendor/webawesome/styles/color/palettes/default.css +146 -0
  659. nocek/web/static/vendor/webawesome/styles/color/palettes/shoelace.css +146 -0
  660. nocek/web/static/vendor/webawesome/styles/color/variants/brand.css +162 -0
  661. nocek/web/static/vendor/webawesome/styles/color/variants/danger.css +162 -0
  662. nocek/web/static/vendor/webawesome/styles/color/variants/neutral.css +162 -0
  663. nocek/web/static/vendor/webawesome/styles/color/variants/success.css +162 -0
  664. nocek/web/static/vendor/webawesome/styles/color/variants/warning.css +162 -0
  665. nocek/web/static/vendor/webawesome/styles/color/variants.css +5 -0
  666. nocek/web/static/vendor/webawesome/styles/component/form-control.styles.ts +43 -0
  667. nocek/web/static/vendor/webawesome/styles/component/host.styles.ts +18 -0
  668. nocek/web/static/vendor/webawesome/styles/component/segmented-field.styles.ts +97 -0
  669. nocek/web/static/vendor/webawesome/styles/component/size.styles.ts +26 -0
  670. nocek/web/static/vendor/webawesome/styles/component/variants.styles.ts +69 -0
  671. nocek/web/static/vendor/webawesome/styles/component/visually-hidden.styles.ts +21 -0
  672. nocek/web/static/vendor/webawesome/styles/layers.css +21 -0
  673. nocek/web/static/vendor/webawesome/styles/native.css +1442 -0
  674. nocek/web/static/vendor/webawesome/styles/themes/awesome.css +616 -0
  675. nocek/web/static/vendor/webawesome/styles/themes/default.css +373 -0
  676. nocek/web/static/vendor/webawesome/styles/themes/shoelace.css +650 -0
  677. nocek/web/static/vendor/webawesome/styles/utilities/align-items.css +44 -0
  678. nocek/web/static/vendor/webawesome/styles/utilities/border-radius.css +20 -0
  679. nocek/web/static/vendor/webawesome/styles/utilities/flex-wrap.css +16 -0
  680. nocek/web/static/vendor/webawesome/styles/utilities/fouce.css +17 -0
  681. nocek/web/static/vendor/webawesome/styles/utilities/gap.css +56 -0
  682. nocek/web/static/vendor/webawesome/styles/utilities/justify-content.css +32 -0
  683. nocek/web/static/vendor/webawesome/styles/utilities/layout.css +159 -0
  684. nocek/web/static/vendor/webawesome/styles/utilities/placeholder.css +9 -0
  685. nocek/web/static/vendor/webawesome/styles/utilities/prose.css +161 -0
  686. nocek/web/static/vendor/webawesome/styles/utilities/scroll-lock.css +20 -0
  687. nocek/web/static/vendor/webawesome/styles/utilities/size.css +21 -0
  688. nocek/web/static/vendor/webawesome/styles/utilities/text.css +262 -0
  689. nocek/web/static/vendor/webawesome/styles/utilities/variants.css +63 -0
  690. nocek/web/static/vendor/webawesome/styles/utilities/visually-hidden.css +24 -0
  691. nocek/web/static/vendor/webawesome/styles/utilities.css +17 -0
  692. nocek/web/static/vendor/webawesome/styles/webawesome.css +11 -0
  693. nocek/web/static/vendor/webawesome/translations/de.js +11 -0
  694. nocek/web/static/vendor/webawesome/translations/en.js +9 -0
  695. nocek/web/static/vendor/webawesome/translations/pl.js +241 -0
  696. nocek/web/static/vendor/webawesome/utilities/animation.js +11 -0
  697. nocek/web/static/vendor/webawesome/utilities/autoloader.js +15 -0
  698. nocek/web/static/vendor/webawesome/utilities/base-path.js +18 -0
  699. nocek/web/static/vendor/webawesome/utilities/clone.js +54 -0
  700. nocek/web/static/vendor/webawesome/utilities/defined.js +8 -0
  701. nocek/web/static/vendor/webawesome/utilities/form.js +8 -0
  702. nocek/web/static/vendor/webawesome/utilities/localize.js +13 -0
  703. nocek/web/static/vendor/webawesome/utilities/ssr-hydration.js +7 -0
  704. nocek/web/static/vendor/webawesome/webawesome.js +66 -0
  705. nocek/web/static/vendor/webawesome/webawesome.loader.js +67 -0
  706. nocek-0.1.0.dist-info/METADATA +233 -0
  707. nocek-0.1.0.dist-info/RECORD +710 -0
  708. nocek-0.1.0.dist-info/WHEEL +4 -0
  709. nocek-0.1.0.dist-info/entry_points.txt +2 -0
  710. nocek-0.1.0.dist-info/licenses/LICENSE +75 -0
nocek/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """Nocek: identify bats from ultrasonic recordings."""
2
+
3
+ __version__ = "0.1.0"
nocek/__main__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Allow ``python -m nocek``."""
2
+
3
+ from nocek.cli import main
4
+
5
+ raise SystemExit(main())
nocek/cli.py ADDED
@@ -0,0 +1,165 @@
1
+ """Command-line entry point.
2
+
3
+ The application is used through the browser; the command line only starts it.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import json
10
+ import logging
11
+ import re
12
+ import socket
13
+ import sys
14
+ import urllib.request
15
+ import webbrowser
16
+ from collections.abc import Sequence
17
+ from logging.handlers import RotatingFileHandler
18
+
19
+ from nocek import __version__
20
+ from nocek.storage import settings
21
+
22
+ DEFAULT_PORT = 6475 # B-A-T-S on a phone keypad
23
+ HOST = "127.0.0.1"
24
+
25
+ log = logging.getLogger("nocek")
26
+
27
+
28
+ def build_parser() -> argparse.ArgumentParser:
29
+ parser = argparse.ArgumentParser(
30
+ prog="nocek",
31
+ description="Identify bats from ultrasonic recordings.",
32
+ )
33
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
34
+ parser.add_argument(
35
+ "--port",
36
+ type=int,
37
+ default=DEFAULT_PORT,
38
+ help=f"port to listen on (default {DEFAULT_PORT}; the next free one if taken)",
39
+ )
40
+ parser.add_argument("--no-browser", action="store_true", help="do not open a browser")
41
+ commands = parser.add_subparsers(dest="command", metavar="command")
42
+ shortcut = commands.add_parser(
43
+ "shortcut", help="add Nocek to the system's menu, to start it without a terminal"
44
+ )
45
+ shortcut.add_argument("--remove", action="store_true", help="remove the shortcut instead")
46
+ return parser
47
+
48
+
49
+ def main(argv: Sequence[str] | None = None) -> int:
50
+ args = build_parser().parse_args(argv)
51
+ if args.command == "shortcut":
52
+ return make_shortcut(remove=args.remove)
53
+ setup_logging()
54
+ if running(args.port):
55
+ # Started again, from a shortcut say: show the one already running.
56
+ log.info("Nocek is already running at http://%s:%d", HOST, args.port)
57
+ if not args.no_browser:
58
+ webbrowser.open(f"http://{HOST}:{args.port}/?token={settings.token()}")
59
+ return 0
60
+ serve(free_port(args.port), open_browser=not args.no_browser)
61
+ return 0
62
+
63
+
64
+ def make_shortcut(remove: bool) -> int:
65
+ from nocek import shortcut
66
+
67
+ made = shortcut.for_this_system()
68
+ try:
69
+ if remove:
70
+ print(f"Removed {made.path}" if made.remove() else f"There was no {made.path}")
71
+ else:
72
+ made.create()
73
+ print(f"Nocek can now be started from {made.path}")
74
+ except (OSError, shortcut.ShortcutError) as error:
75
+ print(f"Could not {'remove' if remove else 'make'} the shortcut: {error}", file=sys.stderr)
76
+ return 1
77
+ return 0
78
+
79
+
80
+ def running(port: int) -> bool:
81
+ """Whether Nocek already answers on this port."""
82
+ try:
83
+ with urllib.request.urlopen(f"http://{HOST}:{port}/api/health", timeout=1) as response:
84
+ return "version" in json.loads(response.read())
85
+ except (OSError, ValueError):
86
+ return False
87
+
88
+
89
+ def serve(port: int, open_browser: bool) -> None:
90
+ import uvicorn
91
+
92
+ from nocek.web.app import create_app
93
+ from nocek.web.state import AppState
94
+
95
+ token = settings.token()
96
+ state = AppState(settings.config_dir() / "settings.json", token, port)
97
+ address = f"http://{HOST}:{port}/?token={token}"
98
+
99
+ class Server(uvicorn.Server):
100
+ async def startup(self, sockets: list[socket.socket] | None = None) -> None:
101
+ await super().startup(sockets)
102
+ log.info("Nocek is running at http://%s:%d", HOST, port)
103
+ if open_browser:
104
+ webbrowser.open(address)
105
+
106
+ def stop_server() -> None:
107
+ log.info("Quit from the interface")
108
+ server.should_exit = True
109
+
110
+ try:
111
+ app = create_app(state, on_quit=stop_server)
112
+ server = Server(uvicorn.Config(app, host=HOST, port=port, log_config=None))
113
+ server.run()
114
+ finally:
115
+ state.close()
116
+
117
+
118
+ def free_port(wanted: int, attempts: int = 20) -> int:
119
+ """The wanted port, or the first free one after it.
120
+
121
+ Tested the way the server will bind it: with SO_REUSEADDR, so a port
122
+ still holding connections from a server that just stopped counts as
123
+ free, as it is to the server. (On Windows that option would let a port
124
+ in use be taken over, so there it is left off.)
125
+ """
126
+ for port in range(wanted, wanted + attempts):
127
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
128
+ if sys.platform != "win32":
129
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
130
+ try:
131
+ s.bind((HOST, port))
132
+ except OSError:
133
+ continue
134
+ return port
135
+ raise SystemExit(f"no free port between {wanted} and {wanted + attempts - 1}")
136
+
137
+
138
+ class HideToken(logging.Filter):
139
+ """Keep the access token out of the log: the opening address carries it."""
140
+
141
+ PATTERN = re.compile(r"token=[^&\s\"]+")
142
+
143
+ def filter(self, record: logging.LogRecord) -> bool:
144
+ record.msg = self.PATTERN.sub("token=…", str(record.msg))
145
+ if record.args:
146
+ record.args = tuple(
147
+ self.PATTERN.sub("token=…", a) if isinstance(a, str) else a
148
+ for a in (record.args if isinstance(record.args, tuple) else (record.args,))
149
+ )
150
+ return True
151
+
152
+
153
+ def setup_logging() -> None:
154
+ folder = settings.log_dir()
155
+ folder.mkdir(parents=True, exist_ok=True)
156
+ file = RotatingFileHandler(folder / "nocek.log", maxBytes=1_000_000, backupCount=3)
157
+ file.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(name)s: %(message)s"))
158
+ handlers: list[logging.Handler] = [file]
159
+ if sys.stderr is not None: # started without a console, as pythonw does, there is none
160
+ console = logging.StreamHandler()
161
+ console.setFormatter(logging.Formatter("%(message)s"))
162
+ handlers.append(console)
163
+ for handler in handlers:
164
+ handler.addFilter(HideToken())
165
+ logging.basicConfig(level=logging.INFO, handlers=handlers, force=True)
nocek/core/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Signal analysis and species identification.
2
+
3
+ Nothing in this package knows about recorders, storage or the web interface:
4
+ it takes audio and returns measurements and opinions.
5
+ """
nocek/core/_data.py ADDED
@@ -0,0 +1,13 @@
1
+ """Data files shipped inside the package."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from importlib import resources
7
+ from typing import Any
8
+
9
+
10
+ def load_json(name: str) -> Any:
11
+ """A JSON file from ``nocek/core/data``."""
12
+ path = resources.files("nocek.core").joinpath("data").joinpath(name)
13
+ return json.loads(path.read_text("utf-8"))
nocek/core/audio.py ADDED
@@ -0,0 +1,44 @@
1
+ """Loading recordings into memory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ import numpy as np
9
+ import numpy.typing as npt
10
+ import soundfile as sf
11
+
12
+ Samples = npt.NDArray[np.float32]
13
+
14
+
15
+ class AudioError(Exception):
16
+ """A file that cannot be read as audio."""
17
+
18
+
19
+ # eq=False: comparing arrays field by field has no single truth value.
20
+ @dataclass(frozen=True, eq=False)
21
+ class Audio:
22
+ """Mono samples in the range [-1, 1] and the rate they were recorded at."""
23
+
24
+ samples: Samples
25
+ sample_rate: int
26
+
27
+ @property
28
+ def duration_s(self) -> float:
29
+ return len(self.samples) / self.sample_rate
30
+
31
+
32
+ def load(path: Path) -> Audio:
33
+ """Read an audio file, mixing several channels down to one.
34
+
35
+ Any format libsndfile understands is accepted, so recordings from other
36
+ devices load the same way as WAV files from a bat recorder.
37
+ """
38
+ try:
39
+ data, sample_rate = sf.read(path, dtype="float32", always_2d=True)
40
+ except (sf.LibsndfileError, OSError) as error:
41
+ raise AudioError(f"{path.name}: {error}") from error
42
+
43
+ samples = data[:, 0] if data.shape[1] == 1 else data.mean(axis=1, dtype=np.float32)
44
+ return Audio(samples=np.ascontiguousarray(samples), sample_rate=int(sample_rate))
@@ -0,0 +1,320 @@
1
+ """Identify species with BatDetect2, a neural network that finds and labels calls.
2
+
3
+ BatDetect2 (Mac Aodha et al., UCL) boxes every echolocation call in a
4
+ recording and gives each a class. It was trained on British bats, so two
5
+ things are added here. Each class stands for the set of taxa it can mean
6
+ locally (see data/batdetect2_classes.json), and a recording's calls are
7
+ split by frequency into sources, one per animal, each with its own opinion:
8
+ one file often holds more than one bat.
9
+
10
+ The model itself is loaded on first use and only through `Model`; everything
11
+ else works on plain call lists and needs no network to test.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from collections.abc import Mapping, Sequence
17
+ from dataclasses import dataclass
18
+ from functools import cache
19
+ from pathlib import Path
20
+ from types import MappingProxyType
21
+ from typing import Any
22
+
23
+ from nocek.core import taxonomy
24
+ from nocek.core._data import load_json
25
+ from nocek.core.opinion import Abstention, Candidate, Opinion
26
+ from nocek.core.taxonomy import Taxon
27
+
28
+ SOURCE = "batdetect2"
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class Params:
33
+ # Below about 0.3 noise floods in.
34
+ detection_threshold: float = 0.4
35
+ # Sources are found as peaks in a histogram of call centre frequencies.
36
+ cluster_floor_khz: float = 10.0
37
+ cluster_bin_khz: float = 5.0
38
+ # Peaks closer than this cannot be told from one bat's own spread.
39
+ cluster_min_separation_khz: float = 8.0
40
+ cluster_min_calls: int = 3
41
+ cluster_min_share: float = 0.10
42
+ # Calls voting this evenly between labels are one animal the model cannot
43
+ # place, not two animals.
44
+ min_agreement: float = 0.5
45
+ # A species that rarely occurs in a region must score at least this. On
46
+ # clean reference recordings the model's best call scores at worst 0.657,
47
+ # so a rare bat has to look as convincing as a known one.
48
+ rare_min_score: float = 0.65
49
+
50
+
51
+ DEFAULT_PARAMS = Params()
52
+
53
+
54
+ @dataclass(frozen=True)
55
+ class ClassInfo:
56
+ taxa: frozenset[Taxon]
57
+ low_khz: tuple[float, float]
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class Call:
62
+ """One call as the model boxed and labelled it."""
63
+
64
+ start_s: float
65
+ end_s: float
66
+ low_khz: float
67
+ high_khz: float
68
+ model_class: str
69
+ class_prob: float
70
+ det_prob: float
71
+
72
+ @property
73
+ def centre_khz(self) -> float:
74
+ return (self.low_khz + self.high_khz) / 2
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class Source:
79
+ """The calls of one animal, as far as frequency can tell, and their opinion."""
80
+
81
+ calls: tuple[Call, ...]
82
+ model_class: str
83
+ centre_khz: float
84
+ # Median low edge of the call boxes, close to the end frequency.
85
+ low_khz: float
86
+ # Distance between the frequency peaks merged into this source.
87
+ spread_khz: float
88
+ agreement: float
89
+ opinion: Opinion
90
+
91
+
92
+ @dataclass(frozen=True)
93
+ class Analysis:
94
+ sources: tuple[Source, ...]
95
+
96
+ @property
97
+ def n_calls(self) -> int:
98
+ return sum(len(s.calls) for s in self.sources)
99
+
100
+
101
+ class BatDetectError(Exception):
102
+ """The model failed on a recording."""
103
+
104
+
105
+ @cache
106
+ def classes() -> Mapping[str, ClassInfo]:
107
+ """What each model class stands for."""
108
+ return MappingProxyType(
109
+ {
110
+ name: ClassInfo(
111
+ taxa=frozenset(taxonomy.get(t) for t in info["taxa"]),
112
+ low_khz=(float(info["low_khz"][0]), float(info["low_khz"][1])),
113
+ )
114
+ for name, info in load_json("batdetect2_classes.json")["classes"].items()
115
+ }
116
+ )
117
+
118
+
119
+ def interpret(calls: Sequence[Call], params: Params = DEFAULT_PARAMS) -> Analysis:
120
+ """Split calls into sources and give each an opinion."""
121
+ unknown = {c.model_class for c in calls} - classes().keys()
122
+ if unknown:
123
+ raise ValueError(f"model classes missing from batdetect2_classes.json: {sorted(unknown)}")
124
+
125
+ sources = _merge([_summarise(cluster) for cluster in _cluster(calls, params)], params)
126
+ # Named sources first, then the ones with most calls.
127
+ sources.sort(key=lambda s: (s.opinion.abstention is not None, -len(s.calls)))
128
+ return Analysis(sources=tuple(sources))
129
+
130
+
131
+ class Model:
132
+ """BatDetect2, loaded on first use."""
133
+
134
+ def __init__(self, params: Params = DEFAULT_PARAMS, device: str | None = None) -> None:
135
+ self.params = params
136
+ self._device_name = device
137
+ self._loaded: tuple[Any, Any, Any] | None = None
138
+
139
+ @property
140
+ def device(self) -> str:
141
+ return str(self._load()[2])
142
+
143
+ def analyse(self, path: Path) -> Analysis:
144
+ from batdetect2.utils import detector_utils
145
+
146
+ model, config, device = self._load()
147
+ try:
148
+ run = detector_utils.process_file(str(path), model, config, device)
149
+ except Exception as error: # the model raises anything from torch to librosa
150
+ raise BatDetectError(f"{path.name}: {error}") from error
151
+ calls = [
152
+ Call(
153
+ start_s=float(a["start_time"]),
154
+ end_s=float(a["end_time"]),
155
+ low_khz=float(a["low_freq"]) / 1000,
156
+ high_khz=float(a["high_freq"]) / 1000,
157
+ model_class=str(a["class"]),
158
+ class_prob=float(a["class_prob"]),
159
+ det_prob=float(a["det_prob"]),
160
+ )
161
+ for a in run["pred_dict"]["annotation"]
162
+ if a.get("event", "Echolocation") == "Echolocation"
163
+ ]
164
+ return interpret(calls, self.params)
165
+
166
+ def _load(self) -> tuple[Any, Any, Any]:
167
+ if self._loaded is None:
168
+ import torch
169
+ from batdetect2.detector import parameters
170
+ from batdetect2.utils import detector_utils
171
+
172
+ device = torch.device(self._device_name or _best_device())
173
+ model, model_params = detector_utils.load_model(
174
+ parameters.DEFAULT_MODEL_PATH, device=device
175
+ )
176
+ config = {
177
+ **parameters.DEFAULT_PROCESSING_CONFIGURATIONS,
178
+ **model_params,
179
+ "detection_threshold": self.params.detection_threshold,
180
+ "max_duration": None,
181
+ }
182
+ self._loaded = (model, config, device)
183
+ return self._loaded
184
+
185
+
186
+ def _best_device() -> str:
187
+ import torch
188
+
189
+ if torch.cuda.is_available():
190
+ return "cuda"
191
+ # Apple GPUs: about four times faster than the CPU, with the same calls.
192
+ if torch.backends.mps.is_available():
193
+ return "mps"
194
+ return "cpu"
195
+
196
+
197
+ def _cluster(calls: Sequence[Call], params: Params) -> list[list[Call]]:
198
+ """Split calls into one group per animal, by centre frequency.
199
+
200
+ Labels alone do not separate animals: the model jitters between
201
+ neighbouring classes on a single bat. Frequency does, because jitter does
202
+ not move a call and a second animal does.
203
+ """
204
+ if not calls:
205
+ return []
206
+ n = len(calls)
207
+ counts: dict[int, int] = {}
208
+ for c in calls:
209
+ b = _bin(c.centre_khz, params)
210
+ counts[b] = counts.get(b, 0) + 1
211
+
212
+ peaks = [
213
+ (count, params.cluster_floor_khz + (b + 0.5) * params.cluster_bin_khz)
214
+ for b, count in counts.items()
215
+ if count >= params.cluster_min_calls
216
+ and count / n >= params.cluster_min_share
217
+ and count >= counts.get(b - 1, 0)
218
+ and count >= counts.get(b + 1, 0)
219
+ ]
220
+ centres: list[float] = []
221
+ for _, centre in sorted(peaks, reverse=True):
222
+ if all(abs(centre - k) >= params.cluster_min_separation_khz for k in centres):
223
+ centres.append(centre)
224
+ if not centres:
225
+ centres = [_upper_median([c.centre_khz for c in calls])]
226
+
227
+ groups: list[list[Call]] = [[] for _ in centres]
228
+ for c in calls:
229
+ nearest = min(range(len(centres)), key=lambda k: abs(c.centre_khz - centres[k]))
230
+ groups[nearest].append(c)
231
+ return [g for g in groups if g]
232
+
233
+
234
+ def _bin(khz: float, params: Params) -> int:
235
+ return int((khz - params.cluster_floor_khz) // params.cluster_bin_khz)
236
+
237
+
238
+ @dataclass
239
+ class _Summary:
240
+ calls: list[Call]
241
+ taxa: frozenset[Taxon]
242
+ model_class: str
243
+ centres_khz: list[float]
244
+ low_khz: float
245
+ max_prob: float
246
+ agreement: float
247
+
248
+
249
+ def _summarise(calls: list[Call]) -> _Summary:
250
+ """The label one cluster votes for, weighted by class probability."""
251
+ votes: dict[frozenset[Taxon], float] = {}
252
+ for c in calls:
253
+ taxa = classes()[c.model_class].taxa
254
+ votes[taxa] = votes.get(taxa, 0.0) + c.class_prob
255
+ best = max(votes, key=lambda t: votes[t])
256
+ in_best = [c for c in calls if classes()[c.model_class].taxa == best]
257
+ by_class: dict[str, float] = {}
258
+ for c in in_best:
259
+ by_class[c.model_class] = by_class.get(c.model_class, 0.0) + c.class_prob
260
+ return _Summary(
261
+ calls=calls,
262
+ taxa=best,
263
+ model_class=max(by_class, key=lambda k: by_class[k]),
264
+ centres_khz=[_upper_median([c.centre_khz for c in calls])],
265
+ low_khz=_upper_median([c.low_khz for c in calls]),
266
+ max_prob=max(c.class_prob for c in calls),
267
+ agreement=votes[best] / sum(votes.values()),
268
+ )
269
+
270
+
271
+ def _merge(summaries: list[_Summary], params: Params) -> list[Source]:
272
+ """Clusters with the same label are one source: two individuals, or one bat
273
+ boxed inconsistently, and the data cannot tell which."""
274
+ merged: dict[frozenset[Taxon], _Summary] = {}
275
+ for s in summaries:
276
+ m = merged.get(s.taxa)
277
+ if m is None:
278
+ merged[s.taxa] = s
279
+ continue
280
+ n_m, n_s = len(m.calls), len(s.calls)
281
+ m.agreement = (m.agreement * n_m + s.agreement * n_s) / (n_m + n_s)
282
+ m.low_khz = (m.low_khz * n_m + s.low_khz * n_s) / (n_m + n_s)
283
+ m.max_prob = max(m.max_prob, s.max_prob)
284
+ m.centres_khz += s.centres_khz
285
+ m.calls = m.calls + s.calls
286
+ return [_source(s, params) for s in merged.values()]
287
+
288
+
289
+ def _source(s: _Summary, params: Params) -> Source:
290
+ low, high = classes()[s.model_class].low_khz
291
+ declined = None
292
+ if not low <= s.low_khz <= high:
293
+ declined = Abstention.OUT_OF_BAND
294
+ elif s.agreement < params.min_agreement:
295
+ declined = Abstention.INCONCLUSIVE
296
+ candidate = Candidate(
297
+ taxa=s.taxa,
298
+ score=s.max_prob,
299
+ evidence={"agreement": s.agreement, "low_khz": s.low_khz},
300
+ )
301
+ return Source(
302
+ calls=tuple(sorted(s.calls, key=lambda c: c.start_s)),
303
+ model_class=s.model_class,
304
+ centre_khz=s.centres_khz[0],
305
+ low_khz=s.low_khz,
306
+ spread_khz=max(s.centres_khz) - min(s.centres_khz),
307
+ agreement=s.agreement,
308
+ opinion=Opinion(
309
+ source=SOURCE,
310
+ ranking=(candidate,),
311
+ min_score=0.0,
312
+ shortlist_ratio=1.0,
313
+ declined=declined,
314
+ ),
315
+ )
316
+
317
+
318
+ def _upper_median(values: list[float]) -> float:
319
+ """The middle value, or the upper of the two middle ones."""
320
+ return sorted(values)[len(values) // 2]