creates video voice memos from audio clips; with bluesky integration. trill.ptr.pet

feat: add audio transcription

ptr.pet f04d69d1 cb193787

verified
+443 -4
deno.lock
···
"npm:@atcute/lexicons@^1.2.3": "1.2.3",
"npm:@atcute/microcosm@1": "1.0.0",
"npm:@atcute/oauth-browser-client@^2.0.1": "2.0.1_@atcute+identity@1.1.2",
+
"npm:@huggingface/transformers@^3.8.0": "3.8.0",
"npm:@pandacss/dev@^1.5.1": "1.5.1_typescript@5.9.3",
"npm:@pandacss/preset-base@^1.5.1": "1.5.1",
"npm:@park-ui/panda-preset@~0.43.1": "0.43.1_@pandacss+dev@1.5.1__typescript@5.9.3_typescript@5.9.3",
···
"debug",
"gensync",
"json5",
-
"semver"
+
"semver@6.3.1"
]
},
"@babel/generator@7.28.5": {
···
"@babel/helper-validator-option",
"browserslist@4.28.0",
"lru-cache",
-
"semver"
+
"semver@6.3.1"
]
},
"@babel/helper-globals@7.28.0": {
···
"postcss-selector-parser"
]
},
+
"@emnapi/runtime@1.7.1": {
+
"integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==",
+
"dependencies": [
+
"tslib"
+
]
+
},
"@esbuild/aix-ppc64@0.25.12": {
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"os": ["aix"],
···
"@floating-ui/utils@0.2.10": {
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="
},
+
"@huggingface/jinja@0.5.1": {
+
"integrity": "sha512-yUZLld4lrM9iFxHCwFQ7D1HW2MWMwSbeB7WzWqFYDWK+rEb+WldkLdAJxUPOmgICMHZLzZGVcVjFh3w/YGubng=="
+
},
+
"@huggingface/transformers@3.8.0": {
+
"integrity": "sha512-bEvx9k/fnhjKtekc1pDYLGDhVwxW5mO3k4UkP/mJQtQI5dC34daCQ28O1B19lkyM0QYFwAj+RGl0qOjE9Upt/w==",
+
"dependencies": [
+
"@huggingface/jinja",
+
"onnxruntime-node",
+
"onnxruntime-web",
+
"sharp"
+
]
+
},
+
"@img/colour@1.0.0": {
+
"integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="
+
},
+
"@img/sharp-darwin-arm64@0.34.5": {
+
"integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+
"optionalDependencies": [
+
"@img/sharp-libvips-darwin-arm64"
+
],
+
"os": ["darwin"],
+
"cpu": ["arm64"]
+
},
+
"@img/sharp-darwin-x64@0.34.5": {
+
"integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+
"optionalDependencies": [
+
"@img/sharp-libvips-darwin-x64"
+
],
+
"os": ["darwin"],
+
"cpu": ["x64"]
+
},
+
"@img/sharp-libvips-darwin-arm64@1.2.4": {
+
"integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+
"os": ["darwin"],
+
"cpu": ["arm64"]
+
},
+
"@img/sharp-libvips-darwin-x64@1.2.4": {
+
"integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+
"os": ["darwin"],
+
"cpu": ["x64"]
+
},
+
"@img/sharp-libvips-linux-arm64@1.2.4": {
+
"integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@img/sharp-libvips-linux-arm@1.2.4": {
+
"integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+
"os": ["linux"],
+
"cpu": ["arm"]
+
},
+
"@img/sharp-libvips-linux-ppc64@1.2.4": {
+
"integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+
"os": ["linux"],
+
"cpu": ["ppc64"]
+
},
+
"@img/sharp-libvips-linux-riscv64@1.2.4": {
+
"integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+
"os": ["linux"],
+
"cpu": ["riscv64"]
+
},
+
"@img/sharp-libvips-linux-s390x@1.2.4": {
+
"integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+
"os": ["linux"],
+
"cpu": ["s390x"]
+
},
+
"@img/sharp-libvips-linux-x64@1.2.4": {
+
"integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@img/sharp-libvips-linuxmusl-arm64@1.2.4": {
+
"integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@img/sharp-libvips-linuxmusl-x64@1.2.4": {
+
"integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@img/sharp-linux-arm64@0.34.5": {
+
"integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+
"optionalDependencies": [
+
"@img/sharp-libvips-linux-arm64"
+
],
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@img/sharp-linux-arm@0.34.5": {
+
"integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+
"optionalDependencies": [
+
"@img/sharp-libvips-linux-arm"
+
],
+
"os": ["linux"],
+
"cpu": ["arm"]
+
},
+
"@img/sharp-linux-ppc64@0.34.5": {
+
"integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+
"optionalDependencies": [
+
"@img/sharp-libvips-linux-ppc64"
+
],
+
"os": ["linux"],
+
"cpu": ["ppc64"]
+
},
+
"@img/sharp-linux-riscv64@0.34.5": {
+
"integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+
"optionalDependencies": [
+
"@img/sharp-libvips-linux-riscv64"
+
],
+
"os": ["linux"],
+
"cpu": ["riscv64"]
+
},
+
"@img/sharp-linux-s390x@0.34.5": {
+
"integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+
"optionalDependencies": [
+
"@img/sharp-libvips-linux-s390x"
+
],
+
"os": ["linux"],
+
"cpu": ["s390x"]
+
},
+
"@img/sharp-linux-x64@0.34.5": {
+
"integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+
"optionalDependencies": [
+
"@img/sharp-libvips-linux-x64"
+
],
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@img/sharp-linuxmusl-arm64@0.34.5": {
+
"integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+
"optionalDependencies": [
+
"@img/sharp-libvips-linuxmusl-arm64"
+
],
+
"os": ["linux"],
+
"cpu": ["arm64"]
+
},
+
"@img/sharp-linuxmusl-x64@0.34.5": {
+
"integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+
"optionalDependencies": [
+
"@img/sharp-libvips-linuxmusl-x64"
+
],
+
"os": ["linux"],
+
"cpu": ["x64"]
+
},
+
"@img/sharp-wasm32@0.34.5": {
+
"integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+
"dependencies": [
+
"@emnapi/runtime"
+
],
+
"cpu": ["wasm32"]
+
},
+
"@img/sharp-win32-arm64@0.34.5": {
+
"integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+
"os": ["win32"],
+
"cpu": ["arm64"]
+
},
+
"@img/sharp-win32-ia32@0.34.5": {
+
"integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+
"os": ["win32"],
+
"cpu": ["ia32"]
+
},
+
"@img/sharp-win32-x64@0.34.5": {
+
"integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+
"os": ["win32"],
+
"cpu": ["x64"]
+
},
"@internationalized/date@3.10.0": {
"integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==",
"dependencies": [
···
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"dependencies": [
"@isaacs/balanced-match"
+
]
+
},
+
"@isaacs/fs-minipass@4.0.1": {
+
"integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
+
"dependencies": [
+
"minipass"
]
},
"@jridgewell/gen-mapping@0.3.13": {
···
"effect"
],
"scripts": true
+
},
+
"@protobufjs/aspromise@1.1.2": {
+
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
+
},
+
"@protobufjs/base64@1.1.2": {
+
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+
},
+
"@protobufjs/codegen@2.0.4": {
+
"integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+
},
+
"@protobufjs/eventemitter@1.1.0": {
+
"integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
+
},
+
"@protobufjs/fetch@1.1.0": {
+
"integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+
"dependencies": [
+
"@protobufjs/aspromise",
+
"@protobufjs/inquire"
+
]
+
},
+
"@protobufjs/float@1.0.2": {
+
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
+
},
+
"@protobufjs/inquire@1.1.0": {
+
"integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
+
},
+
"@protobufjs/path@1.1.2": {
+
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
+
},
+
"@protobufjs/pool@1.1.0": {
+
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
+
},
+
"@protobufjs/utf8@1.1.0": {
+
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
},
"@rollup/rollup-android-arm-eabi@4.52.5": {
"integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==",
···
"integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==",
"bin": true
},
+
"boolean@3.2.0": {
+
"integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
+
"deprecated": true
+
},
"braces@3.0.3": {
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dependencies": [
···
"readdirp"
},
+
"chownr@3.0.0": {
+
"integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="
+
},
"code-block-writer@13.0.3": {
"integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="
},
···
"ms"
},
+
"define-data-property@1.1.4": {
+
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+
"dependencies": [
+
"es-define-property",
+
"es-errors",
+
"gopd"
+
]
+
},
+
"define-properties@1.2.1": {
+
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+
"dependencies": [
+
"define-data-property",
+
"has-property-descriptors",
+
"object-keys"
+
]
+
},
"detect-libc@1.0.3": {
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"bin": true
+
},
+
"detect-libc@2.1.2": {
+
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="
+
},
+
"detect-node@2.1.0": {
+
"integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
},
"effect@3.10.15": {
"integrity": "sha512-LdczPAFbtij3xGr9i+8PyDtuWdlXjSY5UJ8PKrYrr0DClKfR/OW3j8sxtambWYljzJAYD865KFhv7LdbWdG7VQ==",
···
"entities@6.0.1": {
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="
},
+
"es-define-property@1.0.1": {
+
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="
+
},
+
"es-errors@1.3.0": {
+
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
+
},
+
"es6-error@4.1.1": {
+
"integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="
+
},
"esbuild@0.25.12": {
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"optionalDependencies": [
···
},
"escalade@3.2.0": {
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
+
},
+
"escape-string-regexp@4.0.0": {
+
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
},
"esm-env@1.2.2": {
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="
···
"to-regex-range"
},
+
"flatbuffers@25.9.23": {
+
"integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ=="
+
},
"focus-trap@7.5.4": {
"integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==",
"dependencies": [
···
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dependencies": [
"is-glob"
+
]
+
},
+
"global-agent@3.0.0": {
+
"integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
+
"dependencies": [
+
"boolean",
+
"es6-error",
+
"matcher",
+
"roarr",
+
"semver@7.7.3",
+
"serialize-error"
+
]
+
},
+
"globalthis@1.0.4": {
+
"integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+
"dependencies": [
+
"define-properties",
+
"gopd"
},
"globrex@0.1.2": {
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
},
+
"gopd@1.2.0": {
+
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="
+
},
"graceful-fs@4.2.11": {
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+
},
+
"guid-typescript@1.0.9": {
+
"integrity": "sha512-Y8T4vYhEfwJOTbouREvG+3XDsjr8E3kIr7uf+JZ0BYloFsttiHU0WfvANVsR7TxNUJa/WpCnw/Ino/p+DeBhBQ=="
+
},
+
"has-property-descriptors@1.0.2": {
+
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+
"dependencies": [
+
"es-define-property"
+
]
},
"hookable@5.5.3": {
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="
···
"json-schema-traverse@1.0.0": {
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
},
+
"json-stringify-safe@5.0.1": {
+
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
+
},
"json5@2.2.3": {
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"bin": true
···
"lightningcss@1.25.1": {
"integrity": "sha512-V0RMVZzK1+rCHpymRv4URK2lNhIRyO8g7U7zOFwVAhJuat74HtkjIQpQRKNCwFEYkRGpafOpmXXLoaoBcyVtBg==",
"dependencies": [
-
"detect-libc"
+
"detect-libc@1.0.3"
],
"optionalDependencies": [
"lightningcss-darwin-arm64",
···
"lodash.uniq@4.5.0": {
"integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="
},
+
"long@5.3.2": {
+
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="
+
},
"look-it-up@2.1.0": {
"integrity": "sha512-nMoGWW2HurtuJf6XAL56FWTDCWLOTSsanrgwOyaR5Y4e3zfG5N/0cU5xWZSEU3tBxhQugRbV1xL9jb+ug7yZww=="
},
"lru-cache@5.1.1": {
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dependencies": [
-
"yallist"
+
"yallist@3.1.1"
},
"lucide-solid@0.553.0_solid-js@1.9.10__seroval@1.3.2": {
···
"integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==",
"dependencies": [
"@jridgewell/sourcemap-codec"
+
]
+
},
+
"matcher@3.0.0": {
+
"integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
+
"dependencies": [
+
"escape-string-regexp"
},
"mediabunny@1.25.0": {
···
"@isaacs/brace-expansion"
},
+
"minipass@7.1.2": {
+
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="
+
},
+
"minizlib@3.1.0": {
+
"integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==",
+
"dependencies": [
+
"minipass"
+
]
+
},
"ms@2.1.3": {
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
···
"node-releases@2.0.27": {
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="
},
+
"object-keys@1.1.1": {
+
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
+
},
"object-path@0.11.8": {
"integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA=="
},
+
"onnxruntime-common@1.21.0": {
+
"integrity": "sha512-Q632iLLrtCAVOTO65dh2+mNbQir/QNTVBG3h/QdZBpns7mZ0RYbLRBgGABPbpU9351AgYy7SJf1WaeVwMrBFPQ=="
+
},
+
"onnxruntime-common@1.22.0-dev.20250409-89f8206ba4": {
+
"integrity": "sha512-vDJMkfCfb0b1A836rgHj+ORuZf4B4+cc2bASQtpeoJLueuFc5DuYwjIZUBrSvx/fO5IrLjLz+oTrB3pcGlhovQ=="
+
},
+
"onnxruntime-node@1.21.0": {
+
"integrity": "sha512-NeaCX6WW2L8cRCSqy3bInlo5ojjQqu2fD3D+9W5qb5irwxhEyWKXeH2vZ8W9r6VxaMPUan+4/7NDwZMtouZxEw==",
+
"dependencies": [
+
"global-agent",
+
"onnxruntime-common@1.21.0",
+
"tar"
+
],
+
"os": ["win32", "darwin", "linux"],
+
"scripts": true
+
},
+
"onnxruntime-web@1.22.0-dev.20250409-89f8206ba4": {
+
"integrity": "sha512-0uS76OPgH0hWCPrFKlL8kYVV7ckM7t/36HfbgoFw6Nd0CZVVbQC4PkrR8mBX8LtNUFZO25IQBqV2Hx2ho3FlbQ==",
+
"dependencies": [
+
"flatbuffers",
+
"guid-typescript",
+
"long",
+
"onnxruntime-common@1.22.0-dev.20250409-89f8206ba4",
+
"platform",
+
"protobufjs"
+
]
+
},
"outdent@0.8.0": {
"integrity": "sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A=="
},
···
"exsolve",
"pathe"
+
},
+
"platform@1.3.6": {
+
"integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg=="
},
"pluralize@8.0.0": {
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="
···
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"bin": true
},
+
"protobufjs@7.5.4": {
+
"integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==",
+
"dependencies": [
+
"@protobufjs/aspromise",
+
"@protobufjs/base64",
+
"@protobufjs/codegen",
+
"@protobufjs/eventemitter",
+
"@protobufjs/fetch",
+
"@protobufjs/float",
+
"@protobufjs/inquire",
+
"@protobufjs/path",
+
"@protobufjs/pool",
+
"@protobufjs/utf8",
+
"@types/node",
+
"long"
+
],
+
"scripts": true
+
},
"proxy-compare@3.0.0": {
"integrity": "sha512-y44MCkgtZUCT9tZGuE278fB7PWVf7fRYy0vbRXAts2o5F0EfC4fIQrvQQGBJo1WJbFcVLXzApOscyJuZqHQc1w=="
},
···
"reusify@1.1.0": {
"integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="
},
+
"roarr@2.15.4": {
+
"integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
+
"dependencies": [
+
"boolean",
+
"detect-node",
+
"globalthis",
+
"json-stringify-safe",
+
"semver-compare",
+
"sprintf-js"
+
]
+
},
"rollup@4.52.5": {
"integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==",
"dependencies": [
···
"queue-microtask"
},
+
"semver-compare@1.0.0": {
+
"integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="
+
},
"semver@6.3.1": {
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": true
+
},
+
"semver@7.7.3": {
+
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+
"bin": true
+
},
+
"serialize-error@7.0.1": {
+
"integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
+
"dependencies": [
+
"type-fest"
+
]
},
"seroval-plugins@1.3.3_seroval@1.3.2": {
"integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==",
···
"seroval@1.3.2": {
"integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ=="
},
+
"sharp@0.34.5": {
+
"integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+
"dependencies": [
+
"@img/colour",
+
"detect-libc@2.1.2",
+
"semver@7.7.3"
+
],
+
"optionalDependencies": [
+
"@img/sharp-darwin-arm64",
+
"@img/sharp-darwin-x64",
+
"@img/sharp-libvips-darwin-arm64",
+
"@img/sharp-libvips-darwin-x64",
+
"@img/sharp-libvips-linux-arm",
+
"@img/sharp-libvips-linux-arm64",
+
"@img/sharp-libvips-linux-ppc64",
+
"@img/sharp-libvips-linux-riscv64",
+
"@img/sharp-libvips-linux-s390x",
+
"@img/sharp-libvips-linux-x64",
+
"@img/sharp-libvips-linuxmusl-arm64",
+
"@img/sharp-libvips-linuxmusl-x64",
+
"@img/sharp-linux-arm",
+
"@img/sharp-linux-arm64",
+
"@img/sharp-linux-ppc64",
+
"@img/sharp-linux-riscv64",
+
"@img/sharp-linux-s390x",
+
"@img/sharp-linux-x64",
+
"@img/sharp-linuxmusl-arm64",
+
"@img/sharp-linuxmusl-x64",
+
"@img/sharp-wasm32",
+
"@img/sharp-win32-arm64",
+
"@img/sharp-win32-ia32",
+
"@img/sharp-win32-x64"
+
],
+
"scripts": true
+
},
"sisteransi@1.0.5": {
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="
},
···
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
+
"sprintf-js@1.1.3": {
+
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
+
},
"string-width@4.2.3": {
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": [
···
"strip-ansi"
},
+
"tar@7.5.1": {
+
"integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==",
+
"dependencies": [
+
"@isaacs/fs-minipass",
+
"chownr",
+
"minipass",
+
"minizlib",
+
"yallist@5.0.0"
+
]
+
},
"tinyglobby@0.2.15_picomatch@4.0.3": {
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dependencies": [
···
},
"tslib@2.8.1": {
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+
},
+
"type-fest@0.13.1": {
+
"integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="
},
"typescript@5.9.3": {
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
···
},
"yallist@3.1.1": {
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+
},
+
"yallist@5.0.0": {
+
"integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="
},
"workspace": {
···
"npm:@atcute/lexicons@^1.2.3",
"npm:@atcute/microcosm@1",
"npm:@atcute/oauth-browser-client@^2.0.1",
+
"npm:@huggingface/transformers@^3.8.0",
"npm:@pandacss/dev@^1.5.1",
"npm:@pandacss/preset-base@^1.5.1",
"npm:@park-ui/panda-preset@~0.43.1",
+1
package.json
···
"@atcute/lexicons": "^1.2.3",
"@atcute/microcosm": "^1.0.0",
"@atcute/oauth-browser-client": "^2.0.1",
+
"@huggingface/transformers": "^3.8.0",
"@solid-primitives/date": "^2.1.4",
"@solid-primitives/map": "^0.7.2",
"fast-average-color": "^9.5.0",
+50 -22
src/components/FileTask.tsx
···
import {
+
CaptionsIcon,
CircleAlertIcon,
DownloadIcon,
EllipsisVerticalIcon,
···
import { TaskState } from "~/lib/task";
import PostDialog from "./PostDialog";
-
import { Button } from "./ui/button";
+
import { Button, ButtonProps } from "./ui/button";
import { Menu } from "./ui/menu";
import { createSignal } from "solid-js";
+
import { toaster } from "./Toaster";
const downloadFile = (blob: Blob, fileName: string) => {
const url = URL.createObjectURL(blob);
···
</Popover.Positioner>
</Popover.Root>
);
-
const statusSuccess = (result: Blob) => {
+
const statusSuccess = (result: Blob, altText?: string) => {
+
const [menuOpen, setMenuOpen] = createSignal(false);
+
const MenuButton = (props: ButtonProps) => (
+
<Button
+
color={{ _hover: "colorPalette.emphasized" }}
+
variant="ghost"
+
display="flex"
+
justifyContent="space-between"
+
alignItems="center"
+
{...props}
+
onClick={(e) => {
+
if (typeof props.onClick === "function") props.onClick(e);
+
setMenuOpen(false);
+
}}
+
/>
+
);
return (
<>
<PostDialog
openSignal={[dialogOpen, setDialogOpen]}
account={selectedAccount}
result={result}
+
initialAltText={altText}
/>
<Menu.Root
+
open={menuOpen()}
+
onOpenChange={(e) => setMenuOpen(e.open)}
positioning={{ placement: "bottom-start", strategy: "fixed" }}
>
<Menu.Trigger
···
)}
/>
<Menu.Positioner>
-
<Menu.Content>
+
<Menu.Content py="0">
<Menu.ItemGroup>
-
<Button
-
color={{ _hover: "colorPalette.emphasized" }}
-
onClick={() =>
+
<MenuButton
+
onClick={() => {
downloadFile(
result,
process.file.name
···
.slice(0, -1)
.join(".")
.concat(".mp4"),
-
)
-
}
-
variant="ghost"
-
display="flex"
-
justifyContent="space-between"
-
alignItems="center"
+
);
+
toaster.create({
+
title: "downloaded result file",
+
type: "success",
+
duration: 1000,
+
});
+
}}
>
download <DownloadIcon />
-
</Button>
-
<Button
-
onClick={() => setDialogOpen(!dialogOpen())}
+
</MenuButton>
+
<MenuButton
+
disabled={altText === undefined}
+
onClick={() => {
+
navigator.clipboard.writeText(altText!);
+
toaster.create({
+
title: "copied transcribed text to clipboard",
+
type: "success",
+
duration: 1000,
+
});
+
}}
+
>
+
copy transcription <CaptionsIcon />
+
</MenuButton>
+
<MenuButton
disabled={selectedAccount === undefined}
-
color={{ _hover: "colorPalette.emphasized" }}
-
variant="ghost"
-
display="flex"
-
justifyContent="space-between"
-
alignItems="center"
+
onClick={() => setDialogOpen(!dialogOpen())}
>
post to bsky <SendIcon />
-
</Button>
+
</MenuButton>
</Menu.ItemGroup>
</Menu.Content>
</Menu.Positioner>
···
const status = () => {
switch (process.status) {
case "success":
-
return statusSuccess(process.result);
+
return statusSuccess(process.result, process.altText);
case "processing":
return statusProcessing();
default:
+39 -2
src/components/PostDialog.tsx
···
import { Component, createSignal, Signal } from "solid-js";
-
import { SendIcon, XIcon } from "lucide-solid";
+
import { CaptionsIcon, SendIcon, XIcon } from "lucide-solid";
import { Stack } from "styled-system/jsx";
import { IconButton } from "~/components/ui/icon-button";
import { Spinner } from "~/components/ui/spinner";
···
import { Dialog } from "~/components/ui/dialog";
import { Textarea } from "~/components/ui/textarea";
import { Account } from "~/lib/accounts";
+
import { Popover } from "./ui/popover";
const PostDialog = (props: {
result: Blob;
account: Account | undefined;
openSignal: Signal<boolean>;
+
initialAltText?: string;
}) => {
const [postContent, setPostContent] = createSignal<string>("");
+
const [altText, setAltText] = createSignal<string>(
+
props.initialAltText ?? "",
+
);
const [posting, setPosting] = createSignal(false);
const [open, setOpen] = props.openSignal;
···
)}
/>
)}
+
<Popover.Root>
+
<Popover.Trigger
+
asChild={(triggerProps) => (
+
<IconButton
+
{...triggerProps()}
+
variant={altText() ? "solid" : "ghost"}
+
size="sm"
+
>
+
<CaptionsIcon />
+
</IconButton>
+
)}
+
/>
+
<Popover.Positioner>
+
<Popover.Content width="sm">
+
<Popover.Arrow />
+
<Stack gap="2">
+
<Popover.Title>video alt text</Popover.Title>
+
<Textarea
+
value={altText()}
+
onInput={(e) => setAltText(e.currentTarget.value)}
+
placeholder="describe the video content..."
+
rows={4}
+
/>
+
</Stack>
+
</Popover.Content>
+
</Popover.Positioner>
+
</Popover.Root>
<IconButton
disabled={posting()}
onClick={() => {
setPosting(true);
-
sendPost(props.account?.did!, props.result, postContent())
+
sendPost(
+
props.account?.did!,
+
props.result,
+
postContent(),
+
altText(),
+
)
.then((result) => {
const parsedUri = parseCanonicalResourceUri(result.uri);
if (!parsedUri.ok) throw "failed to parse atproto uri";
+119 -61
src/components/Settings.tsx
···
-
import { createSignal, For, Signal } from "solid-js";
+
import { Component, createSignal, For, JSXElement, Signal } from "solid-js";
import {
CheckIcon,
···
backgroundColor as backgroundColorSetting,
frameRate as frameRateSetting,
useDominantColorAsBg as useDominantColorAsBgSetting,
+
autoTranscribe as autoTranscribeSetting,
+
whisperModel as whisperModelSetting,
Setting,
+
defaultWhisperModel,
} from "~/lib/settings";
import { handleResolver } from "~/lib/at";
import { toaster } from "~/components/Toaster";
···
import { type Color, type ListCollection, parseColor } from "@ark-ui/solid";
import { ColorPicker } from "~/components/ui/color-picker";
import { Input } from "~/components/ui/input";
+
import { preloadModel } from "~/lib/transcribe";
const SettingCheckbox = (props: {
setting: Setting<boolean>;
···
);
};
+
const Category = ({
+
title,
+
children,
+
}: {
+
title: string;
+
children: JSXElement;
+
}) => (
+
<Stack>
+
<FormLabel>{title}</FormLabel>
+
<Stack
+
gap="0"
+
border="1px solid var(--colors-border-default)"
+
borderBottomWidth="3px"
+
rounded="xs"
+
>
+
{children}
+
</Stack>
+
</Stack>
+
);
+
const Settings = () => {
const [handle, setHandle] = createSignal("");
const isHandleValid = () => isHandle(handle());
···
</For>
);
return (
-
<Stack>
-
<FormLabel>accounts</FormLabel>
+
<Category title="accounts">
<Stack
-
border="1px solid var(--colors-border-default)"
-
borderBottomWidth="3px"
-
rounded="xs"
+
borderBottom="1px solid var(--colors-border-default)"
+
p="2"
+
marginBottom="2"
+
direction="row"
+
gap="2"
+
w="full"
>
-
<Stack
-
borderBottom="1px solid var(--colors-border-default)"
-
p="2"
-
direction="row"
-
gap="2"
-
w="full"
-
>
-
<Field.Root w="full">
-
<Field.Input
-
placeholder="example.bsky.social"
-
value={handle()}
-
onInput={(e) => setHandle(e.currentTarget.value)}
-
/>
-
</Field.Root>
-
<IconButton onClick={startAccountFlow} disabled={!isHandleValid()}>
-
<PlusIcon />
-
</IconButton>
-
</Stack>
-
{items(accounts())}
+
<Field.Root w="full">
+
<Field.Input
+
placeholder="example.bsky.social"
+
value={handle()}
+
onInput={(e) => setHandle(e.currentTarget.value)}
+
/>
+
</Field.Root>
+
<IconButton onClick={startAccountFlow} disabled={!isHandleValid()}>
+
<PlusIcon />
+
</IconButton>
</Stack>
-
</Stack>
+
{items(accounts())}
+
</Category>
);
};
···
backgroundColorSetting.set(newColor.toString("rgb"));
};
+
const whisperModelCollection = createListCollection({
+
items: [
+
{ tag: "tiny", size: "40MB" },
+
{ tag: "base", size: "80MB" },
+
{ tag: "small", size: "250MB" },
+
].map((model) => ({
+
label: `${model.tag} (${model.size})`,
+
value: `onnx-community/whisper-${model.tag}`,
+
})),
+
});
+
const [whisperModel, _setWhisperModel] = createSignal(
+
(whisperModelSetting.get() ?? defaultWhisperModel).toString(),
+
);
+
const setWhisperModel = (value: string | ((prev: string) => string)) => {
+
const newModel = _setWhisperModel(value);
+
whisperModelSetting.set(newModel);
+
if (autoTranscribe()) setTimeout(() => preloadModel(), 200);
+
};
+
const [autoTranscribe, setAutoTranscribe] = createSignal(
+
autoTranscribeSetting.get() ?? false,
+
);
+
return (
<Drawer.Root>
<Drawer.Trigger
···
<Drawer.Body>
<Stack gap="4">
<Accounts />
-
<Stack>
-
<FormLabel>processing</FormLabel>
-
<Stack
-
gap="0"
-
border="1px solid var(--colors-border-default)"
-
borderBottomWidth="3px"
-
rounded="xs"
-
>
-
<Box borderBottom="1px solid var(--colors-border-subtle)">
-
<SettingCheckbox
-
label="show profile picture"
-
setting={showProfilePictureSetting}
-
signal={[showProfilePicture, setShowProfilePicture]}
-
/>
-
</Box>
+
<Category title="video processing">
+
<Box borderBottom="1px solid var(--colors-border-subtle)">
<SettingCheckbox
-
label="show visualizer"
-
setting={showVisualizerSetting}
-
signal={[showVisualizer, setShowVisualizer]}
+
label="show profile picture"
+
setting={showProfilePictureSetting}
+
signal={[showProfilePicture, setShowProfilePicture]}
/>
-
<Stack gap="0" borderY="1px solid var(--colors-border-muted)">
-
<SettingCheckbox
-
label="use dominant color as bg"
-
setting={useDominantColorAsBgSetting}
-
signal={[useDominantColorAsBg, setUseDominantColorAsBg]}
-
disabled={!showProfilePicture()}
-
/>
-
<SettingColorPicker
-
label="background color"
-
signal={[backgroundColor, setBackgroundColor]}
-
/>
-
</Stack>
-
<SettingSelect
-
label="frame rate"
-
signal={[frameRate, setFrameRate]}
-
collection={frameRateCollection}
+
</Box>
+
<SettingCheckbox
+
label="show visualizer"
+
setting={showVisualizerSetting}
+
signal={[showVisualizer, setShowVisualizer]}
+
/>
+
<Stack gap="0" borderY="1px solid var(--colors-border-muted)">
+
<SettingCheckbox
+
label="use dominant color as bg"
+
setting={useDominantColorAsBgSetting}
+
signal={[useDominantColorAsBg, setUseDominantColorAsBg]}
+
disabled={!showProfilePicture()}
+
/>
+
<SettingColorPicker
+
label="background color"
+
signal={[backgroundColor, setBackgroundColor]}
/>
</Stack>
-
</Stack>
+
<SettingSelect
+
label="frame rate"
+
signal={[frameRate, setFrameRate]}
+
collection={frameRateCollection}
+
/>
+
</Category>
+
<Category title="audio transcription">
+
<Box borderBottom="1px solid var(--colors-border-subtle)">
+
<SettingCheckbox
+
label="transcribe audio"
+
setting={autoTranscribeSetting}
+
signal={[
+
autoTranscribe,
+
(val) => {
+
const newVal = setAutoTranscribe(val);
+
if (newVal) preloadModel();
+
return val;
+
},
+
]}
+
/>
+
</Box>
+
<Box borderBottom="1px solid var(--colors-border-subtle)">
+
<SettingSelect
+
label="whisper model"
+
signal={[whisperModel, setWhisperModel]}
+
collection={whisperModelCollection}
+
/>
+
</Box>
+
<Text color="fg.subtle" p="2" fontSize="sm" fontWeight="normal">
+
note: the model will only be downloaded once.
+
</Text>
+
</Category>
</Stack>
</Drawer.Body>
<Drawer.Footer p="2" gap="3">
+4
src/index.tsx
···
import { accounts, setAccounts } from "./lib/accounts";
import { AtprotoDid } from "@atcute/lexicons/syntax";
import { toaster } from "./components/Toaster";
+
import { autoTranscribe } from "./lib/settings";
+
import { preloadModel } from "./lib/transcribe";
const root = document.getElementById("root");
···
type: "error",
});
});
+
+
if (autoTranscribe.get()) preloadModel();
render(() => <App />, root!);
+2
src/lib/at.ts
···
did: AtprotoDid,
blob: Blob,
postContent: string,
+
altText?: string,
) => {
const login = await getSessionClient(did);
const upload = await login.client.post("com.atproto.repo.uploadBlob", {
···
embed: {
$type: "app.bsky.embed.video",
video: upload.data.blob,
+
alt: altText,
},
createdAt: new Date().toISOString(),
};
+4
src/lib/settings.ts
···
export const useDominantColorAsBg = setting<boolean>("useDominantColorAsBg");
export const backgroundColor = setting<string>("backgroundColor");
export const frameRate = setting<number>("frameRate");
+
+
export const autoTranscribe = setting<boolean>("autoTranscribe");
+
export const whisperModel = setting<string>("whisperModel");
+
export const defaultWhisperModel = "onnx-community/whisper-tiny";
+16 -8
src/lib/task.ts
···
showProfilePicture,
showVisualizer,
useDominantColorAsBg,
+
autoTranscribe,
} from "./settings";
import { getSessionClient } from "./oauth";
import { is } from "@atcute/lexicons";
···
import { FastAverageColor } from "fast-average-color";
import { toaster } from "~/components/Toaster";
import { parseColor } from "@ark-ui/solid";
+
import { transcribe } from "./transcribe";
export type TaskState = { file: File } & (
| { status: "processing" }
| { status: "error"; error: string }
-
| { status: "success"; result: Blob }
+
| { status: "success"; result: Blob; altText?: string }
);
let _idCounter = 0;
···
});
}
}
-
const result = await render(file, {
-
pfpUrl,
-
visualizer: showVisualizer.get() ?? true,
-
frameRate: frameRate.get() ?? 30,
-
bgColor,
-
duration,
-
});
+
const [result, altText] = await Promise.all([
+
render(file, {
+
pfpUrl,
+
visualizer: showVisualizer.get() ?? true,
+
frameRate: frameRate.get() ?? 30,
+
bgColor,
+
duration,
+
}),
+
(autoTranscribe.get() ?? false)
+
? transcribe(file)
+
: Promise.resolve(undefined),
+
]);
tasks.set(id, {
file,
status: "success",
result,
+
altText,
});
} catch (error) {
console.error(error);
+101
src/lib/transcribe.ts
···
+
import {
+
AutomaticSpeechRecognitionPipeline,
+
pipeline,
+
} from "@huggingface/transformers";
+
import { toaster } from "~/components/Toaster";
+
import { defaultWhisperModel, whisperModel } from "./settings";
+
+
let transcriberPromise: Promise<AutomaticSpeechRecognitionPipeline> | null =
+
null;
+
let model: AutomaticSpeechRecognitionPipeline | null = null;
+
+
const loadModel = () => {
+
if (model) return Promise.resolve(model);
+
+
if (transcriberPromise) return transcriberPromise;
+
+
let toastId: string | undefined;
+
+
const modelName = whisperModel.get() ?? defaultWhisperModel;
+
+
transcriberPromise = pipeline("automatic-speech-recognition", modelName, {
+
progress_callback: (data: any) => {
+
// data contains: { status, file, name, loaded, total, progress }
+
if (data.status === "initiate") {
+
if (!toastId) {
+
toastId = toaster.create({
+
title: "downloading transcription model",
+
description: `fetching ${data.file}...`,
+
type: "info",
+
duration: 999999,
+
});
+
}
+
} else if (data.status === "progress" && toastId) {
+
const percent = data.progress ? Math.round(data.progress) : 0;
+
toaster.update(toastId, {
+
title: "downloading transcription model",
+
description: `fetching ${data.file} (at ${percent}%)...`,
+
type: "info",
+
duration: 999999,
+
});
+
}
+
},
+
})
+
.then((transcriber) => {
+
if (toastId) {
+
toaster.update(toastId, {
+
title: "transcription model loaded",
+
description: `${modelName.split("/")[1]} is ready`,
+
type: "success",
+
duration: 3000,
+
});
+
}
+
model = transcriber;
+
return transcriber;
+
})
+
.catch((err) => {
+
const toastOpts = {
+
title: "transcription model download failed",
+
description: `${err}`,
+
type: "error",
+
duration: 5000,
+
};
+
if (toastId) toaster.update(toastId, toastOpts);
+
else toaster.create(toastOpts);
+
+
model = null;
+
+
throw err;
+
})
+
.finally(() => {
+
transcriberPromise = null;
+
});
+
+
return transcriberPromise;
+
};
+
+
export const preloadModel = () => {
+
model = null;
+
loadModel().catch((e) => console.error("preload failed", e));
+
};
+
+
export const transcribe = async (file: File): Promise<string> => {
+
const url = URL.createObjectURL(file);
+
try {
+
await loadModel();
+
if (!model) throw "model not loaded";
+
+
const output = await model(url);
+
return [output].flat()[0].text.trim();
+
} catch (err) {
+
console.error("transcription failed", err);
+
toaster.create({
+
title: "transcription failed",
+
description: `${err}`,
+
type: "error",
+
});
+
throw err;
+
} finally {
+
URL.revokeObjectURL(url);
+
}
+
};