diff --git a/devbox.d/caddy/Caddyfile b/devbox.d/caddy/Caddyfile
deleted file mode 100644
index 8d06494..0000000
--- a/devbox.d/caddy/Caddyfile
+++ /dev/null
@@ -1,15 +0,0 @@
-# See https://caddyserver.com/docs/caddyfile for more details
-{
- admin 0.0.0.0:2020
- auto_https disable_certs
- http_port 8800
- https_port 4443
-}
-
-:8082 {
- root * {$CADDY_ROOT_DIR}
- log {
- output file {$CADDY_LOG_DIR}/caddy.log
- }
- file_server
-}
diff --git a/devbox.d/web/index.html b/devbox.d/web/index.html
deleted file mode 100644
index 21250f0..0000000
--- a/devbox.d/web/index.html
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
- Hello World!
-
-
- Hello World!
-
-
diff --git a/devbox.json b/devbox.json
deleted file mode 100644
index 10813f3..0000000
--- a/devbox.json
+++ /dev/null
@@ -1,46 +0,0 @@
-{
- "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.12.0/.schema/devbox.schema.json",
- "packages": [
- "nodejs@20",
- "ffmpeg@latest",
- "yt-dlp@latest",
- "lazydocker@latest",
- "chisel@latest",
- "entr@latest",
- "act@latest",
- "git-subrepo@latest",
- "opentofu@latest",
- "ggshield@latest",
- "python310@latest",
- "python310Packages.pip@latest",
- "caddy@latest"
- ],
- "env": {
- "DEVBOX_COREPACK_ENABLED": "true",
- "ENV": "development",
- "KUBECONFIG": "$HOME/.kube/futureporn.yaml",
- "VENV_DIR": ".venv"
- },
- "shell": {
- "init_hook": [
- "echo Welcome to Futureporn devbox",
- ". $VENV_DIR/bin/activate",
- "pip install -r requirements.txt"
- ],
- "scripts": {
- "tunnel": "dotenvx run -f ./.env.development -- chisel client bright.fp.sbtp.xyz:9090 R:4000",
- "backup": "docker exec -t postgres_db pg_dumpall -c -U postgres > ./backups/dev_`date +%Y-%m-%d_%H_%M_%S`.sql",
- "act": "dotenvx run -f ./.env.testing -- act -W ./.gitea/workflows --secret-file .env.development",
- "act:builder": "dotenvx run -f ./.env.testing -- act --env-file .env.testing -W ./.gitea/workflows/builder.yaml --secret-file .env.testing --var-file .env.testing --insecure-secrets",
- "act:tests": "dotenvx run -f ./.env.testing -- act --env-file .env.testing -W ./.gitea/workflows/tests.yaml --secret-file .env.testing --var-file .env.testing --insecure-secrets",
- "bright:compile:watch": "cd ./apps/bright && find . -type f -name \"*.ex\" -o -name \"*.exs\" | entr -r mix compile --warnings-as-errors",
- "bright:compile:watch2": "cd ./apps/bright && pnpx chokidar-cli \"**/*\" -i \"deps/**\" -i \"_build/**\" -c \"mix compile --warnings-as-errors\"",
- "bright:dev": "cd ./apps/bright && dotenvx run -f ../../.env.development -e MIX_ENV=dev -- mix phx.server",
- "bright:test:unit:watch": "cd ./apps/bright && pnpx chokidar-cli '**/*' -i \"deps/**\" -i '_build/**' -c 'mix test --only=unit'",
- "bright:act": "cd ./apps/bright && act --env MIX_ENV=test -W ./.gitea/workflows/tests.yaml --secret-file .env.development",
- "test": "act -W ./.gitea/workflows/tests.yaml --secret-file .env.testing --var-file .env.testing && devbox run beep || devbox run boop",
- "beep": "ffplay -nodisp -loglevel quiet -autoexit ./apps/beep/beep2.wav",
- "boop": "ffplay -nodisp -loglevel quiet -autoexit ./apps/beep/beep1.wav"
- }
- }
-}
\ No newline at end of file
diff --git a/devbox.lock b/devbox.lock
deleted file mode 100644
index d11aeb8..0000000
--- a/devbox.lock
+++ /dev/null
@@ -1,827 +0,0 @@
-{
- "lockfile_version": "1",
- "packages": {
- "act@latest": {
- "last_modified": "2025-02-07T11:26:36Z",
- "resolved": "github:NixOS/nixpkgs/d98abf5cf5914e5e4e9d57205e3af55ca90ffc1d#act",
- "source": "devbox-search",
- "version": "0.2.72",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/69mqjq6ysm38yppm5l0a68zaxfk3jsb5-act-0.2.72",
- "default": true
- }
- ],
- "store_path": "/nix/store/69mqjq6ysm38yppm5l0a68zaxfk3jsb5-act-0.2.72"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/di3cp7yr4dq07byl8hm8xwnas7hn8xcn-act-0.2.72",
- "default": true
- }
- ],
- "store_path": "/nix/store/di3cp7yr4dq07byl8hm8xwnas7hn8xcn-act-0.2.72"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/yblc9543pbzncgy0q4bfdj8h7nrs35am-act-0.2.72",
- "default": true
- }
- ],
- "store_path": "/nix/store/yblc9543pbzncgy0q4bfdj8h7nrs35am-act-0.2.72"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/k7nqxipi81pzfdbh2a19np9q84qmgj3w-act-0.2.72",
- "default": true
- }
- ],
- "store_path": "/nix/store/k7nqxipi81pzfdbh2a19np9q84qmgj3w-act-0.2.72"
- }
- }
- },
- "caddy@latest": {
- "last_modified": "2025-02-07T11:26:36Z",
- "plugin_version": "0.0.3",
- "resolved": "github:NixOS/nixpkgs/d98abf5cf5914e5e4e9d57205e3af55ca90ffc1d#caddy",
- "source": "devbox-search",
- "version": "2.9.1",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/wsj8iixhs5iwjhfyy4l00va0dj301sjs-caddy-2.9.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/wsj8iixhs5iwjhfyy4l00va0dj301sjs-caddy-2.9.1"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/w6cg28ws2404fglmhbzddqjjkniqfiam-caddy-2.9.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/w6cg28ws2404fglmhbzddqjjkniqfiam-caddy-2.9.1"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/8q0lw048apajs1rg6vpfbp2p1m37f25a-caddy-2.9.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/8q0lw048apajs1rg6vpfbp2p1m37f25a-caddy-2.9.1"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/ax0979vnibikl5cr180lmxvb80m2zq50-caddy-2.9.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/ax0979vnibikl5cr180lmxvb80m2zq50-caddy-2.9.1"
- }
- }
- },
- "chisel@latest": {
- "last_modified": "2024-12-23T21:10:33Z",
- "resolved": "github:NixOS/nixpkgs/de1864217bfa9b5845f465e771e0ecb48b30e02d#chisel",
- "source": "devbox-search",
- "version": "1.10.1",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/2rls3b9lq2i3g53zpr09d6ph43mgfxwz-chisel-1.10.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/2rls3b9lq2i3g53zpr09d6ph43mgfxwz-chisel-1.10.1"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/7ff1z4mr0ia2ifdgggpqkbc2j795ccy4-chisel-1.10.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/7ff1z4mr0ia2ifdgggpqkbc2j795ccy4-chisel-1.10.1"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/2cb5sa449vpah2g4q4prvqfz1dcf1rdw-chisel-1.10.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/2cb5sa449vpah2g4q4prvqfz1dcf1rdw-chisel-1.10.1"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/pphc5mnhx6mb08ak6mb3rnh061427xbj-chisel-1.10.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/pphc5mnhx6mb08ak6mb3rnh061427xbj-chisel-1.10.1"
- }
- }
- },
- "entr@latest": {
- "last_modified": "2025-01-19T08:16:51Z",
- "resolved": "github:NixOS/nixpkgs/50165c4f7eb48ce82bd063e1fb8047a0f515f8ce#entr",
- "source": "devbox-search",
- "version": "5.6",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/2qbvxdg5asmjs0fjcf84mssrv5pw7apa-entr-5.6",
- "default": true
- }
- ],
- "store_path": "/nix/store/2qbvxdg5asmjs0fjcf84mssrv5pw7apa-entr-5.6"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/xnhjmgv6svqp793vxhpfcvxig5yi9s60-entr-5.6",
- "default": true
- }
- ],
- "store_path": "/nix/store/xnhjmgv6svqp793vxhpfcvxig5yi9s60-entr-5.6"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/fknw34xxmp239x81r201drmlm3g9p1bb-entr-5.6",
- "default": true
- }
- ],
- "store_path": "/nix/store/fknw34xxmp239x81r201drmlm3g9p1bb-entr-5.6"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/yy1d68xcy847axfmb5xws2271xkf61xk-entr-5.6",
- "default": true
- }
- ],
- "store_path": "/nix/store/yy1d68xcy847axfmb5xws2271xkf61xk-entr-5.6"
- }
- }
- },
- "ffmpeg@latest": {
- "last_modified": "2025-01-07T09:15:50Z",
- "resolved": "github:NixOS/nixpkgs/8c9fd3e564728e90829ee7dbac6edc972971cd0f#ffmpeg",
- "source": "devbox-search",
- "version": "7.1",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "bin",
- "path": "/nix/store/m48ypyjnl04mqigi6fbxa7k2c5bsvazz-ffmpeg-7.1-bin",
- "default": true
- },
- {
- "name": "man",
- "path": "/nix/store/10ncvfz8srnzckq16bn14wffcx8j04ki-ffmpeg-7.1-man",
- "default": true
- },
- {
- "name": "data",
- "path": "/nix/store/rqbsc9zyzi988hwcd423qdh1vh40zlzc-ffmpeg-7.1-data"
- },
- {
- "name": "dev",
- "path": "/nix/store/6ml3pn92nbmnc40s5jfpr84y65ysizk3-ffmpeg-7.1-dev"
- },
- {
- "name": "doc",
- "path": "/nix/store/ni6gri7m1pavfjxx0yfj1msvb0cfasgg-ffmpeg-7.1-doc"
- },
- {
- "name": "lib",
- "path": "/nix/store/2v2rr9dmq4grzwmg4zcsxrmbdcshks7m-ffmpeg-7.1-lib"
- },
- {
- "name": "out",
- "path": "/nix/store/nvccyp4dra8f92ywd00lk3ra121gj162-ffmpeg-7.1"
- }
- ],
- "store_path": "/nix/store/m48ypyjnl04mqigi6fbxa7k2c5bsvazz-ffmpeg-7.1-bin"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "bin",
- "path": "/nix/store/lj99z3ldzka2slgm94w2yl8cjkphn4m3-ffmpeg-7.1-bin",
- "default": true
- },
- {
- "name": "man",
- "path": "/nix/store/gisda8d2pifrrjw0yirrs0x2cyn6b96i-ffmpeg-7.1-man",
- "default": true
- },
- {
- "name": "out",
- "path": "/nix/store/pc332zx12j2fqf5m9mdhhs1rwksbk2fk-ffmpeg-7.1"
- },
- {
- "name": "data",
- "path": "/nix/store/4yhsn6lrfn91dk1kq9wyyd71p4hcfans-ffmpeg-7.1-data"
- },
- {
- "name": "dev",
- "path": "/nix/store/71103mmkxhzzmbj9mpzsrzbp988z3p98-ffmpeg-7.1-dev"
- },
- {
- "name": "doc",
- "path": "/nix/store/k9cc4d60frcn27zapkzhag5h9r0ig5xa-ffmpeg-7.1-doc"
- },
- {
- "name": "lib",
- "path": "/nix/store/dgcmpc8az51a4d2bxgrmfz8zlmvqxwa5-ffmpeg-7.1-lib"
- }
- ],
- "store_path": "/nix/store/lj99z3ldzka2slgm94w2yl8cjkphn4m3-ffmpeg-7.1-bin"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "bin",
- "path": "/nix/store/z44kr817zx99vi2cc204i06cnmz83f1n-ffmpeg-7.1-bin",
- "default": true
- },
- {
- "name": "man",
- "path": "/nix/store/slrha22nqrx2xw009hd37svv2nvpp26i-ffmpeg-7.1-man",
- "default": true
- },
- {
- "name": "data",
- "path": "/nix/store/n3xm2rr1k1ari7ysbp9jhg65pih28x6x-ffmpeg-7.1-data"
- },
- {
- "name": "dev",
- "path": "/nix/store/59gcv4rfg38rqnazv1iq9jssjak2l3ih-ffmpeg-7.1-dev"
- },
- {
- "name": "doc",
- "path": "/nix/store/2cqa3lc75ciqzgmxjm36fd733f3gjgil-ffmpeg-7.1-doc"
- },
- {
- "name": "lib",
- "path": "/nix/store/ir47x8skj54wilcyz2x59am4y085lf6l-ffmpeg-7.1-lib"
- },
- {
- "name": "out",
- "path": "/nix/store/gqw958wdgrbfsxczqf56vnq1iffg5qv1-ffmpeg-7.1"
- }
- ],
- "store_path": "/nix/store/z44kr817zx99vi2cc204i06cnmz83f1n-ffmpeg-7.1-bin"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "bin",
- "path": "/nix/store/d1lv3x7iq129vb19ba9ph6k0yhy4fv34-ffmpeg-7.1-bin",
- "default": true
- },
- {
- "name": "man",
- "path": "/nix/store/qqgvvfw2gqp47zl5v9jhdsl8mb77l909-ffmpeg-7.1-man",
- "default": true
- },
- {
- "name": "doc",
- "path": "/nix/store/gmfj9af6za9i2830cgbj1qazz2nir2vs-ffmpeg-7.1-doc"
- },
- {
- "name": "lib",
- "path": "/nix/store/x8j2nr5qmikfaggl34wpm1dlldb7ik06-ffmpeg-7.1-lib"
- },
- {
- "name": "out",
- "path": "/nix/store/cx89mypnxgk0q4dibacg7dsrb4w66mv9-ffmpeg-7.1"
- },
- {
- "name": "data",
- "path": "/nix/store/5y8zk8fqpaammsnz2svgpdsayn4zlsn2-ffmpeg-7.1-data"
- },
- {
- "name": "dev",
- "path": "/nix/store/qdz8aidi7rksr4g5cpxgz9iivrlsa247-ffmpeg-7.1-dev"
- }
- ],
- "store_path": "/nix/store/d1lv3x7iq129vb19ba9ph6k0yhy4fv34-ffmpeg-7.1-bin"
- }
- }
- },
- "ggshield@latest": {
- "last_modified": "2025-02-27T15:48:43Z",
- "resolved": "github:NixOS/nixpkgs/6c5c5f5100281f8f4ff23f13edd17d645178c87c#ggshield",
- "source": "devbox-search",
- "version": "1.36.0",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/2qsc4iii58gq54l8dys7m4jw5lz0xqk0-ggshield-1.36.0",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/1zic4rm52c81303axs6jwpv9i5cbpgpf-ggshield-1.36.0-dist"
- }
- ],
- "store_path": "/nix/store/2qsc4iii58gq54l8dys7m4jw5lz0xqk0-ggshield-1.36.0"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/l9inx1qgmjm9hiz1fncxx15mkyqw9abc-ggshield-1.36.0",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/1hls55sak8fi14s6wq4ar5iiwvmzpw68-ggshield-1.36.0-dist"
- }
- ],
- "store_path": "/nix/store/l9inx1qgmjm9hiz1fncxx15mkyqw9abc-ggshield-1.36.0"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/n2s11c74rlq64w78rnnnz5j8dz72fnvs-ggshield-1.36.0",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/9k2svwlyx8s2ac6dnhc3sq5c3bvlyrvw-ggshield-1.36.0-dist"
- }
- ],
- "store_path": "/nix/store/n2s11c74rlq64w78rnnnz5j8dz72fnvs-ggshield-1.36.0"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/pz8s8n5qrmhfivj51932s5gqwh64c7fy-ggshield-1.36.0",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/i29ambqhmnizdxk9njgsyza953m5mbmn-ggshield-1.36.0-dist"
- }
- ],
- "store_path": "/nix/store/pz8s8n5qrmhfivj51932s5gqwh64c7fy-ggshield-1.36.0"
- }
- }
- },
- "git-subrepo@latest": {
- "last_modified": "2025-02-07T11:26:36Z",
- "resolved": "github:NixOS/nixpkgs/d98abf5cf5914e5e4e9d57205e3af55ca90ffc1d#git-subrepo",
- "source": "devbox-search",
- "version": "0.4.9",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/iagwf8k53inahcs16by5l7vzffbs01k4-git-subrepo-0.4.9",
- "default": true
- }
- ],
- "store_path": "/nix/store/iagwf8k53inahcs16by5l7vzffbs01k4-git-subrepo-0.4.9"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/vn5qc1h596hj3s7qw1q75wzh8n8lw3im-git-subrepo-0.4.9",
- "default": true
- }
- ],
- "store_path": "/nix/store/vn5qc1h596hj3s7qw1q75wzh8n8lw3im-git-subrepo-0.4.9"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/pxhc909zdsd1jxamzy762xhvvdh0lb5a-git-subrepo-0.4.9",
- "default": true
- }
- ],
- "store_path": "/nix/store/pxhc909zdsd1jxamzy762xhvvdh0lb5a-git-subrepo-0.4.9"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/psy4x285pc5dk3z69gzwq7rafdhpjv5x-git-subrepo-0.4.9",
- "default": true
- }
- ],
- "store_path": "/nix/store/psy4x285pc5dk3z69gzwq7rafdhpjv5x-git-subrepo-0.4.9"
- }
- }
- },
- "github:NixOS/nixpkgs/nixpkgs-unstable": {
- "resolved": "github:NixOS/nixpkgs/ba0939c506a03c60a765cd7f7c43794816540eec?lastModified=1739482815&narHash=sha256-%2F5Lwtmp%2F8j%2Bro32gXzitucSdyjJ6QehfJCL58WNA7N0%3D"
- },
- "lazydocker@latest": {
- "last_modified": "2024-12-23T21:10:33Z",
- "resolved": "github:NixOS/nixpkgs/de1864217bfa9b5845f465e771e0ecb48b30e02d#lazydocker",
- "source": "devbox-search",
- "version": "0.24.1",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/5hv519gz95pwhg4vr9ka8g98bdd91mvx-lazydocker-0.24.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/5hv519gz95pwhg4vr9ka8g98bdd91mvx-lazydocker-0.24.1"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/5wb57rjn6xwyf4nc0mifgjd934i545ph-lazydocker-0.24.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/5wb57rjn6xwyf4nc0mifgjd934i545ph-lazydocker-0.24.1"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/bj056q5lmy3dx6xafwlmvhjqgxjr3awq-lazydocker-0.24.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/bj056q5lmy3dx6xafwlmvhjqgxjr3awq-lazydocker-0.24.1"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/4gwbhlb13pvdbxcb31mslkgd6pr62gyk-lazydocker-0.24.1",
- "default": true
- }
- ],
- "store_path": "/nix/store/4gwbhlb13pvdbxcb31mslkgd6pr62gyk-lazydocker-0.24.1"
- }
- }
- },
- "nodejs@20": {
- "last_modified": "2024-12-23T21:10:33Z",
- "plugin_version": "0.0.2",
- "resolved": "github:NixOS/nixpkgs/de1864217bfa9b5845f465e771e0ecb48b30e02d#nodejs_20",
- "source": "devbox-search",
- "version": "20.18.1",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/q49zbi0pzjhg4zn085h9hyz9m1k3hvpb-nodejs-20.18.1",
- "default": true
- },
- {
- "name": "libv8",
- "path": "/nix/store/l08rljd2yr7i1q4x778qazcys2l4ja23-nodejs-20.18.1-libv8"
- }
- ],
- "store_path": "/nix/store/q49zbi0pzjhg4zn085h9hyz9m1k3hvpb-nodejs-20.18.1"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/4m1ql1hsd6kqmzr8a657qhipjgssrr0c-nodejs-20.18.1",
- "default": true
- },
- {
- "name": "libv8",
- "path": "/nix/store/r8s1xalb4rpy1r44i413j84i4ny6mnsc-nodejs-20.18.1-libv8"
- }
- ],
- "store_path": "/nix/store/4m1ql1hsd6kqmzr8a657qhipjgssrr0c-nodejs-20.18.1"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/7ky0mn6k33731jwzhsxq6a6dsbjqamqs-nodejs-20.18.1",
- "default": true
- },
- {
- "name": "libv8",
- "path": "/nix/store/jw62k3wxl38r8fcbvxaxrm696ivv3imr-nodejs-20.18.1-libv8"
- }
- ],
- "store_path": "/nix/store/7ky0mn6k33731jwzhsxq6a6dsbjqamqs-nodejs-20.18.1"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/j7dx1n6m5axf9r2bvly580x2ixx546wq-nodejs-20.18.1",
- "default": true
- },
- {
- "name": "libv8",
- "path": "/nix/store/qljmc2skrldj0g94rib6nl4jq84193fa-nodejs-20.18.1-libv8"
- }
- ],
- "store_path": "/nix/store/j7dx1n6m5axf9r2bvly580x2ixx546wq-nodejs-20.18.1"
- }
- }
- },
- "opentofu@latest": {
- "last_modified": "2025-02-07T11:26:36Z",
- "resolved": "github:NixOS/nixpkgs/d98abf5cf5914e5e4e9d57205e3af55ca90ffc1d#opentofu",
- "source": "devbox-search",
- "version": "1.9.0",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/bk8lbpai9gckl24971cd7sy86nx65vqp-opentofu-1.9.0",
- "default": true
- }
- ],
- "store_path": "/nix/store/bk8lbpai9gckl24971cd7sy86nx65vqp-opentofu-1.9.0"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/k56jwj34f3qj0j0q7rsqsnzcvjm83xfb-opentofu-1.9.0",
- "default": true
- }
- ],
- "store_path": "/nix/store/k56jwj34f3qj0j0q7rsqsnzcvjm83xfb-opentofu-1.9.0"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/q8a4niqvlph0gbch407q19v444qyflpi-opentofu-1.9.0",
- "default": true
- }
- ],
- "store_path": "/nix/store/q8a4niqvlph0gbch407q19v444qyflpi-opentofu-1.9.0"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/1gc7dl8bcrks1y98qlblr08aly4b3qry-opentofu-1.9.0",
- "default": true
- }
- ],
- "store_path": "/nix/store/1gc7dl8bcrks1y98qlblr08aly4b3qry-opentofu-1.9.0"
- }
- }
- },
- "python310@latest": {
- "last_modified": "2025-02-07T11:26:36Z",
- "plugin_version": "0.0.4",
- "resolved": "github:NixOS/nixpkgs/d98abf5cf5914e5e4e9d57205e3af55ca90ffc1d#python310",
- "source": "devbox-search",
- "version": "3.10.16",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/n3bv4blh1n8xcswpn23d8rkdf19z1z67-python3-3.10.16",
- "default": true
- }
- ],
- "store_path": "/nix/store/n3bv4blh1n8xcswpn23d8rkdf19z1z67-python3-3.10.16"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/wdkg0yc6h9fwvvqwh3zss1a2rz4mrmgl-python3-3.10.16",
- "default": true
- },
- {
- "name": "debug",
- "path": "/nix/store/wxnpshdg4kbdmpl44qnqx29i0pszsa7f-python3-3.10.16-debug"
- }
- ],
- "store_path": "/nix/store/wdkg0yc6h9fwvvqwh3zss1a2rz4mrmgl-python3-3.10.16"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/40lamqbgh1g2x9mml635n329h4gh1928-python3-3.10.16",
- "default": true
- }
- ],
- "store_path": "/nix/store/40lamqbgh1g2x9mml635n329h4gh1928-python3-3.10.16"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/8757y4bf8d77z7c3pjz1nlwi2xlkk93h-python3-3.10.16",
- "default": true
- },
- {
- "name": "debug",
- "path": "/nix/store/4rj9krr4hgv14rjlsrlajksi580fg4np-python3-3.10.16-debug"
- }
- ],
- "store_path": "/nix/store/8757y4bf8d77z7c3pjz1nlwi2xlkk93h-python3-3.10.16"
- }
- }
- },
- "python310Packages.pip@latest": {
- "last_modified": "2023-12-13T22:54:10Z",
- "resolved": "github:NixOS/nixpkgs/fd04bea4cbf76f86f244b9e2549fca066db8ddff#python310Packages.pip",
- "source": "devbox-search",
- "version": "23.2.1",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/vjyrxbxqsadvr9g6mzig6y406dhwcrqi-python3.10-pip-23.2.1",
- "default": true
- },
- {
- "name": "man",
- "path": "/nix/store/2ra46imgas5srgxx8mgs424akh5j1msv-python3.10-pip-23.2.1-man",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/xjj6xjh21k08rkyg6hnkbyrygqhqgn8y-python3.10-pip-23.2.1-dist"
- }
- ],
- "store_path": "/nix/store/vjyrxbxqsadvr9g6mzig6y406dhwcrqi-python3.10-pip-23.2.1"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/j8pxwv7vyjm8z2fqglijjvabbkmxbv9r-python3.10-pip-23.2.1",
- "default": true
- },
- {
- "name": "man",
- "path": "/nix/store/bcfliw00z123ddyi8hsfxvfn2npdcpdq-python3.10-pip-23.2.1-man",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/2326b2sr9p2z9bsghd2pzs346g2qjn7f-python3.10-pip-23.2.1-dist"
- }
- ],
- "store_path": "/nix/store/j8pxwv7vyjm8z2fqglijjvabbkmxbv9r-python3.10-pip-23.2.1"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/7lyvqf8wl47wzgsqmlcz39ycmwxyg9zx-python3.10-pip-23.2.1",
- "default": true
- },
- {
- "name": "man",
- "path": "/nix/store/0j02s5hbsdhfxvvay5dm9j68nhalm39v-python3.10-pip-23.2.1-man",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/8dvqh9ai83lqaaixs3bhmf1n4jxgp4v7-python3.10-pip-23.2.1-dist"
- }
- ],
- "store_path": "/nix/store/7lyvqf8wl47wzgsqmlcz39ycmwxyg9zx-python3.10-pip-23.2.1"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/gdhvfi5zaqzpa5l3kk0spmv71r549slf-python3.10-pip-23.2.1",
- "default": true
- },
- {
- "name": "man",
- "path": "/nix/store/p37y2fhm6l3nd624jmr2r680wf4p0544-python3.10-pip-23.2.1-man",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/r4qj7psryk532rkh0srhn25ygv8yk541-python3.10-pip-23.2.1-dist"
- }
- ],
- "store_path": "/nix/store/gdhvfi5zaqzpa5l3kk0spmv71r549slf-python3.10-pip-23.2.1"
- }
- }
- },
- "yt-dlp@latest": {
- "last_modified": "2025-01-03T14:51:55Z",
- "resolved": "github:NixOS/nixpkgs/a27871180d30ebee8aa6b11bf7fef8a52f024733#yt-dlp",
- "source": "devbox-search",
- "version": "2024.12.23",
- "systems": {
- "aarch64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/8fmy039278i7q31zyyrfmr4z5k35xvp6-yt-dlp-2024.12.23",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/v9mplambvs7hlcpcpnvmf7c30pk5cx8d-yt-dlp-2024.12.23-dist"
- }
- ],
- "store_path": "/nix/store/8fmy039278i7q31zyyrfmr4z5k35xvp6-yt-dlp-2024.12.23"
- },
- "aarch64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/wzna2q40a81qj4icma4q20q9fjg9md76-yt-dlp-2024.12.23",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/z0b0nv6a2wl0565f5ffrzhg2zcqflvrl-yt-dlp-2024.12.23-dist"
- }
- ],
- "store_path": "/nix/store/wzna2q40a81qj4icma4q20q9fjg9md76-yt-dlp-2024.12.23"
- },
- "x86_64-darwin": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/mpbp9jar45hk22ddldm0bbk60vz5w643-yt-dlp-2024.12.23",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/gq4l9yfs7fjb05qkl97hmsq69s3k4shr-yt-dlp-2024.12.23-dist"
- }
- ],
- "store_path": "/nix/store/mpbp9jar45hk22ddldm0bbk60vz5w643-yt-dlp-2024.12.23"
- },
- "x86_64-linux": {
- "outputs": [
- {
- "name": "out",
- "path": "/nix/store/wqcs5crm36p603fkh9j2k868zzb8q79c-yt-dlp-2024.12.23",
- "default": true
- },
- {
- "name": "dist",
- "path": "/nix/store/j452c5bii2slx1biix7lfc5k0aqg2rvs-yt-dlp-2024.12.23-dist"
- }
- ],
- "store_path": "/nix/store/wqcs5crm36p603fkh9j2k868zzb8q79c-yt-dlp-2024.12.23"
- }
- }
- }
- }
-}
diff --git a/package.json b/package.json
index a0cd88b..f95da51 100644
--- a/package.json
+++ b/package.json
@@ -10,12 +10,12 @@
"keywords": [],
"author": "@CJ_Clippy",
"license": "Unlicense",
- "packageManager": "pnpm@9.6.0",
"dependencies": {
"chokidar-cli": "^3.0.0",
"types": "^0.1.1"
},
"devDependencies": {
"concurrently": "^8.2.2"
- }
+ },
+ "packageManager": "pnpm@10.13.1+sha512.37ebf1a5c7a30d5fabe0c5df44ee8da4c965ca0c5af3dbab28c3a1681b70a256218d05c81c9c0dcf767ef6b8551eb5b960042b9ed4300c59242336377e01cfad"
}
diff --git a/services/our/.dockerignore b/services/our/.dockerignore
index 9a0ca6a..d007df5 100644
--- a/services/our/.dockerignore
+++ b/services/our/.dockerignore
@@ -1,3 +1,6 @@
+runs
+requirements.txt
+venv
src/test
node_modules
diff --git a/services/our/.gitignore b/services/our/.gitignore
index 209904f..19c3c96 100644
--- a/services/our/.gitignore
+++ b/services/our/.gitignore
@@ -1,3 +1,5 @@
+runs
+venv
vibeui
generated
diff --git a/services/our/package.json b/services/our/package.json
index 9d18cd4..810b884 100644
--- a/services/our/package.json
+++ b/services/our/package.json
@@ -58,6 +58,7 @@
"@types/node": "^22.16.3",
"@types/node-fetch": "^2.6.12",
"cache-manager": "^7.0.1",
+ "canvas": "^3.1.2",
"chokidar-cli": "^3.0.0",
"concurrently": "^9.2.0",
"date-fns": "^4.1.0",
@@ -77,6 +78,7 @@
"nanoid": "^5.1.5",
"node-fetch": "^3.3.2",
"onnxruntime-node": "1.22.0-rev",
+ "protobufjs": "^7.5.3",
"rate-limiter-flexible": "^7.1.1",
"rimraf": "6.0.1",
"sharp": "^0.34.3",
diff --git a/services/our/pnpm-lock.yaml b/services/our/pnpm-lock.yaml
index 62ae16d..326a139 100644
--- a/services/our/pnpm-lock.yaml
+++ b/services/our/pnpm-lock.yaml
@@ -71,6 +71,9 @@ importers:
cache-manager:
specifier: ^7.0.1
version: 7.0.1
+ canvas:
+ specifier: ^3.1.2
+ version: 3.1.2
chokidar-cli:
specifier: ^3.0.0
version: 3.0.0
@@ -128,6 +131,9 @@ importers:
onnxruntime-node:
specifier: 1.22.0-rev
version: 1.22.0-rev
+ protobufjs:
+ specifier: ^7.5.3
+ version: 7.5.3
rate-limiter-flexible:
specifier: ^7.1.1
version: 7.1.1
@@ -956,6 +962,36 @@ packages:
'@prisma/get-platform@6.8.2':
resolution: {integrity: sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==}
+ '@protobufjs/aspromise@1.1.2':
+ resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
+
+ '@protobufjs/base64@1.1.2':
+ resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==}
+
+ '@protobufjs/codegen@2.0.4':
+ resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==}
+
+ '@protobufjs/eventemitter@1.1.0':
+ resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==}
+
+ '@protobufjs/fetch@1.1.0':
+ resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==}
+
+ '@protobufjs/float@1.0.2':
+ resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==}
+
+ '@protobufjs/inquire@1.1.0':
+ resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==}
+
+ '@protobufjs/path@1.1.2':
+ resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==}
+
+ '@protobufjs/pool@1.1.0':
+ resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==}
+
+ '@protobufjs/utf8@1.1.0':
+ resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
+
'@rollup/rollup-android-arm-eabi@4.45.1':
resolution: {integrity: sha512-NEySIFvMY0ZQO+utJkgoMiCAjMrGvnbDLHvcmlA33UXJpYBCvlBEbMMtV837uCkS+plG2umfhn0T5mMAxGrlRA==}
cpu: [arm]
@@ -1640,6 +1676,10 @@ packages:
canvas-renderer@2.2.1:
resolution: {integrity: sha512-RrBgVL5qCEDIXpJ6NrzyRNoTnXxYarqm/cS/W6ERhUJts5UQtt/XPEosGN3rqUkZ4fjBArlnCbsISJ+KCFnIAg==}
+ canvas@3.1.2:
+ resolution: {integrity: sha512-Z/tzFAcBzoCvJlOSlCnoekh1Gu8YMn0J51+UAuXJAbW1Z6I9l2mZgdD7738MepoeeIcUdDtbMnOg6cC7GJxy/g==}
+ engines: {node: ^18.12.0 || >= 20.9.0}
+
chai@5.2.1:
resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==}
engines: {node: '>=18'}
@@ -2427,6 +2467,9 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+ long@5.3.2:
+ resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
+
loupe@3.1.4:
resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==}
@@ -2563,6 +2606,9 @@ packages:
node-addon-api@6.1.0:
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
+ node-addon-api@7.1.1:
+ resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
node-addon-api@8.5.0:
resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==}
engines: {node: ^18 || ^20 || >= 21}
@@ -2868,6 +2914,10 @@ packages:
process-warning@5.0.0:
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
+ protobufjs@7.5.3:
+ resolution: {integrity: sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==}
+ engines: {node: '>=12.0.0'}
+
pstree.remy@1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
@@ -4645,6 +4695,29 @@ snapshots:
dependencies:
'@prisma/debug': 6.8.2
+ '@protobufjs/aspromise@1.1.2': {}
+
+ '@protobufjs/base64@1.1.2': {}
+
+ '@protobufjs/codegen@2.0.4': {}
+
+ '@protobufjs/eventemitter@1.1.0': {}
+
+ '@protobufjs/fetch@1.1.0':
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/inquire': 1.1.0
+
+ '@protobufjs/float@1.0.2': {}
+
+ '@protobufjs/inquire@1.1.0': {}
+
+ '@protobufjs/path@1.1.2': {}
+
+ '@protobufjs/pool@1.1.0': {}
+
+ '@protobufjs/utf8@1.1.0': {}
+
'@rollup/rollup-android-arm-eabi@4.45.1':
optional: true
@@ -5422,6 +5495,11 @@ snapshots:
dependencies:
'@types/node': 22.16.4
+ canvas@3.1.2:
+ dependencies:
+ node-addon-api: 7.1.1
+ prebuild-install: 7.1.3
+
chai@5.2.1:
dependencies:
assertion-error: 2.0.1
@@ -6289,6 +6367,8 @@ snapshots:
lodash@4.17.21: {}
+ long@5.3.2: {}
+
loupe@3.1.4: {}
lru-cache@10.4.3: {}
@@ -6396,6 +6476,8 @@ snapshots:
node-addon-api@6.1.0: {}
+ node-addon-api@7.1.1: {}
+
node-addon-api@8.5.0: {}
node-domexception@1.0.0: {}
@@ -6686,6 +6768,21 @@ snapshots:
process-warning@5.0.0: {}
+ protobufjs@7.5.3:
+ dependencies:
+ '@protobufjs/aspromise': 1.1.2
+ '@protobufjs/base64': 1.1.2
+ '@protobufjs/codegen': 2.0.4
+ '@protobufjs/eventemitter': 1.1.0
+ '@protobufjs/fetch': 1.1.0
+ '@protobufjs/float': 1.0.2
+ '@protobufjs/inquire': 1.1.0
+ '@protobufjs/path': 1.1.2
+ '@protobufjs/pool': 1.1.0
+ '@protobufjs/utf8': 1.1.0
+ '@types/node': 22.16.4
+ long: 5.3.2
+
pstree.remy@1.1.8: {}
pump@3.0.3:
diff --git a/services/our/requirements.txt b/services/our/requirements.txt
new file mode 100644
index 0000000..c6856f7
--- /dev/null
+++ b/services/our/requirements.txt
@@ -0,0 +1 @@
+ultralytics
\ No newline at end of file
diff --git a/services/our/src/config/env.ts b/services/our/src/config/env.ts
index bb2d8f7..8b803a8 100644
--- a/services/our/src/config/env.ts
+++ b/services/our/src/config/env.ts
@@ -8,6 +8,7 @@ dotenvx.config({ path: ['../../.env.development'] })
const EnvSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
+ VENV: z.string(),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
ORIGIN: z.string(),
@@ -27,6 +28,7 @@ const EnvSchema = z.object({
CDN_TOKEN_SECRET: z.string(),
CACHE_ROOT: z.string().default('/tmp/our'),
VIBEUI_DIR: z.string().default('/opt/futureporn/apps/vibeui'),
+ APP_DIR: z.string().default('/app')
});
const parsed = EnvSchema.safeParse(process.env);
diff --git a/services/our/src/tasks/createFunscript.ts.old b/services/our/src/tasks/createFunscript copy.ts.noexec
similarity index 90%
rename from services/our/src/tasks/createFunscript.ts.old
rename to services/our/src/tasks/createFunscript copy.ts.noexec
index 7ce2422..787d69d 100644
--- a/services/our/src/tasks/createFunscript.ts.old
+++ b/services/our/src/tasks/createFunscript copy.ts.noexec
@@ -60,48 +60,48 @@ async function loadDataYaml(yamlPath: string): Promise {
}
-// async function preparePython(helpers) {
-// const spawn = await getNanoSpawn();
-// const venvPath = join(env.VIBEUI_DIR, "venv");
+async function preparePython(helpers) {
+ const spawn = await getNanoSpawn();
+ const venvPath = join(env.VIBEUI_DIR, "venv");
-// // Determine Python executable
-// let pythonCmd;
-// try {
-// pythonCmd = which.sync("python3");
-// } catch {
-// helpers.logger.error("Python is not installed or not in PATH.");
-// throw new Error("Python not found in PATH.");
-// }
+ // Determine Python executable
+ let pythonCmd;
+ try {
+ pythonCmd = which.sync("python3");
+ } catch {
+ helpers.logger.error("Python is not installed or not in PATH.");
+ throw new Error("Python not found in PATH.");
+ }
-// // If venv doesn't exist, create it
-// if (!existsSync(venvPath)) {
-// helpers.logger.info("Python venv not found. Creating one...");
+ // If venv doesn't exist, create it
+ if (!existsSync(venvPath)) {
+ helpers.logger.info("Python venv not found. Creating one...");
-// try {
-// await spawn(pythonCmd, ["-m", "venv", "venv"], {
-// cwd: env.VIBEUI_DIR,
-// });
+ try {
+ await spawn(pythonCmd, ["-m", "venv", "venv"], {
+ cwd: env.VIBEUI_DIR,
+ });
-// helpers.logger.info("Python venv successfully created.");
-// } catch (err) {
-// helpers.logger.error("Failed to create Python venv:", err);
+ helpers.logger.info("Python venv successfully created.");
+ } catch (err) {
+ helpers.logger.error("Failed to create Python venv:", err);
-// // Clean up partially created venv if needed
-// try {
-// if (existsSync(venvPath)) {
-// rmSync(venvPath, { recursive: true, force: true });
-// helpers.logger.warn("Removed broken venv directory.");
-// }
-// } catch (cleanupErr) {
-// helpers.logger.error("Error while cleaning up broken venv:", cleanupErr);
-// }
+ // Clean up partially created venv if needed
+ try {
+ if (existsSync(venvPath)) {
+ rmSync(venvPath, { recursive: true, force: true });
+ helpers.logger.warn("Removed broken venv directory.");
+ }
+ } catch (cleanupErr) {
+ helpers.logger.error("Error while cleaning up broken venv:", cleanupErr);
+ }
-// throw new Error("Python venv creation failed. Check if python3 and python3-venv are installed.");
-// }
-// } else {
-// helpers.logger.info("Using existing Python venv.");
-// }
-// }
+ throw new Error("Python venv creation failed. Check if python3 and python3-venv are installed.");
+ }
+ } else {
+ helpers.logger.info("Using existing Python venv.");
+ }
+}
async function ffprobe(videoPath: string): Promise<{ fps: number; frames: number }> {
diff --git a/services/our/src/tasks/createFunscript.ts b/services/our/src/tasks/createFunscript.ts
index 95da0f0..ccaa5fe 100644
--- a/services/our/src/tasks/createFunscript.ts
+++ b/services/our/src/tasks/createFunscript.ts
@@ -1,18 +1,54 @@
-
import type { Task, Helpers } from "graphile-worker";
import { PrismaClient } from "../../generated/prisma";
import { withAccelerate } from "@prisma/extension-accelerate";
import { getOrDownloadAsset } from "../utils/cache";
import { env } from "../config/env";
import { getS3Client, uploadFile } from "../utils/s3";
-import { buildFunscript } from '../utils/funscripts';
-import { vibeuiInference } from "../utils/vibeui";
-import { join } from "node:path";
+import { nanoid } from "nanoid";
+import { existsSync, rmSync } from "node:fs";
+import { join, basename, extname } from "node:path";
+import { readFile, writeFile, readdir } from 'node:fs/promises';
+import yaml from 'js-yaml';
+import { getNanoSpawn } from "../utils/nanoSpawn";
+import which from "which";
+import * as ort from 'onnxruntime-node';
+import { inference, loadDataYaml } from "../utils/vibeui";
+import { ffprobe } from "../utils/vibeui";
+import { preparePython } from "../utils/python";
+import { generateActions2, buildFunscript } from "../utils/funscripts";
+
interface Payload {
vodId: string;
}
+// interface Detection {
+// startFrame: number;
+// endFrame: number;
+// className: string;
+// }
+
+// interface DataYaml {
+// path: string;
+// train: string;
+// val: string;
+// names: Record;
+// }
+
+// interface FunscriptAction {
+// at: number;
+// pos: number;
+// }
+
+// interface Funscript {
+// version: string;
+// actions: FunscriptAction[];
+// }
+
+
+// interface ClassPositionMap {
+// [className: string]: number | 'pattern';
+// }
const prisma = new PrismaClient().$extends(withAccelerate());
@@ -24,6 +60,209 @@ function assertPayload(payload: any): asserts payload is Payload {
}
+
+
+
+// export async function buildFunscript(
+// helpers: Helpers,
+// predictionOutput: string,
+// videoPath: string
+// ): Promise {
+// const labelDir = join(predictionOutput, 'labels');
+// const yamlPath = join(predictionOutput, 'data.yaml');
+// const outputPath = join(process.env.CACHE_ROOT ?? '/tmp', `${nanoid()}.funscript`);
+// helpers.logger.info('Starting Funscript generation');
+
+// try {
+
+// const data = await loadDataYaml(join(env.VIBEUI_DIR, 'data.yaml'))
+// const classPositionMap = await loadClassPositionMap(data, helpers);
+// const { fps, totalFrames } = await loadVideoMetadata(videoPath, helpers);
+// const detectionSegments = await processLabelFiles(labelDir, helpers, data);
+// const totalDurationMs = Math.floor((totalFrames / fps) * 1000);
+// const actions = generateActions2(totalDurationMs, fps, detectionSegments, classPositionMap);
+// await writeFunscript(outputPath, actions, helpers);
+
+// return outputPath;
+// } catch (error) {
+// helpers.logger.error(`Error generating Funscript: ${error instanceof Error ? error.message : 'Unknown error'}`);
+// throw error;
+// }
+// }
+
+
+
+// async function loadClassPositionMap(data: DataYaml, helpers: Helpers): Promise {
+// try {
+
+// if (
+// !data ||
+// typeof data !== 'object' ||
+// !('names' in data) ||
+// typeof data.names !== 'object' ||
+// data.names === null ||
+// Object.keys(data.names).length === 0
+// ) {
+// throw new Error('Invalid data.yaml: "names" field is missing, not an object, or empty');
+// }
+
+// const positionMap: ClassPositionMap = {
+// ControlledByTipper: 50,
+// ControlledByTipperHigh: 80,
+// ControlledByTipperLow: 20,
+// ControlledByTipperMedium: 50,
+// ControlledByTipperUltrahigh: 95,
+// Ring1: 30,
+// Ring2: 40,
+// Ring3: 50,
+// Ring4: 60,
+// Earthquake: 'pattern',
+// Fireworks: 'pattern',
+// Pulse: 'pattern',
+// Wave: 'pattern',
+// Pause: 0,
+// RandomTime: 70,
+// HighLevel: 80,
+// LowLevel: 20,
+// MediumLevel: 50,
+// UltraHighLevel: 95
+// };
+
+// const names = Object.values(data.names);
+// for (const name of names) {
+// if (typeof name !== 'string' || name.trim() === '') {
+// helpers.logger.info(`Skipping invalid class name: ${name}`);
+// continue;
+// }
+// if (!(name in positionMap)) {
+// helpers.logger.info(`No position mapping for class "${name}", defaulting to 0`);
+// positionMap[name] = 0;
+// }
+// }
+
+// helpers.logger.info(`Loaded class position map: ${JSON.stringify(positionMap)}`);
+// return positionMap;
+// } catch (error) {
+// helpers.logger.error(`Error loading data.yaml: ${error instanceof Error ? error.message : 'Unknown error'}`);
+// throw error;
+// }
+// }
+
+// function generatePatternPositions(startMs: number, durationMs: number, className: string, fps: number): FunscriptAction[] {
+// const actions: FunscriptAction[] = [];
+// const frameDurationMs = 1000 / fps;
+// const totalFrames = Math.floor(durationMs / frameDurationMs);
+// const intervalMs = 100;
+
+// for (let timeMs = 0; timeMs < durationMs; timeMs += intervalMs) {
+// const progress = timeMs / durationMs;
+// let pos = 0;
+
+// switch (className) {
+// case 'Pulse':
+// pos = Math.round(50 * Math.sin(progress * 2 * Math.PI));
+// break;
+// case 'Wave':
+// pos = Math.round(50 + 50 * Math.sin(progress * 2 * Math.PI));
+// break;
+// case 'Fireworks':
+// pos = Math.random() > 0.5 ? 80 : 0;
+// break;
+// case 'Earthquake':
+// pos = Math.round(90 * Math.sin(progress * 4 * Math.PI) + (Math.random() - 0.5) * 10);
+// pos = Math.max(0, Math.min(90, pos));
+// break;
+// }
+
+// actions.push({ at: startMs + timeMs, pos });
+// }
+
+// return actions;
+// }
+
+// async function loadVideoMetadata(videoPath: string, helpers: Helpers) {
+// const { fps, frames: totalFrames } = await ffprobe(videoPath);
+// helpers.logger.info(`Video metadata: fps=${fps}, frames=${totalFrames}`);
+// return { fps, totalFrames };
+// }
+
+
+// async function processLabelFiles(labelDir: string, helpers: Helpers, data: DataYaml): Promise {
+// const labelFiles = (await readdir(labelDir)).filter(file => file.endsWith('.txt'));
+// const detections: Map = new Map();
+// const names = data.names;
+
+// for (const file of labelFiles) {
+// const match = file.match(/(\d+)\.txt$/);
+// if (!match) {
+// helpers.logger.info(`Skipping invalid filename: ${file}`);
+// continue;
+// }
+// const frameIndex = parseInt(match[1], 10);
+// if (isNaN(frameIndex)) {
+// helpers.logger.info(`Skipping invalid frame index from filename: ${file}`);
+// continue;
+// }
+
+// const content = await readFile(join(labelDir, file), 'utf8');
+// const lines = content.trim().split('\n');
+// const frameDetections: Detection[] = [];
+// let maxConfidence = 0;
+// let selectedClassIndex = -1;
+
+// for (const line of lines) {
+// const parts = line.trim().split(/\s+/);
+// if (parts.length < 6) continue;
+
+// const classIndex = parseInt(parts[0], 10);
+// const confidence = parseFloat(parts[5]);
+// if (isNaN(classIndex) || isNaN(confidence)) continue;
+
+// if (confidence >= 0.7 && confidence > maxConfidence) {
+// maxConfidence = confidence;
+// selectedClassIndex = classIndex;
+// }
+// }
+
+// if (maxConfidence > 0) {
+// const className = (data.names as Record)[selectedClassIndex.toString()];
+// if (className) {
+// frameDetections.push({ startFrame: frameIndex, endFrame: frameIndex, className });
+// }
+// }
+
+// if (frameDetections.length > 0) {
+// detections.set(frameIndex, frameDetections);
+// }
+// }
+
+// // Merge overlapping detections into continuous segments
+// const detectionSegments: Detection[] = [];
+// let currentDetection: Detection | null = null;
+
+// for (const [frameIndex, frameDetections] of detections.entries()) {
+// for (const detection of frameDetections) {
+// if (!currentDetection || currentDetection.className !== detection.className) {
+// if (currentDetection) detectionSegments.push(currentDetection);
+// currentDetection = { ...detection, endFrame: frameIndex };
+// } else {
+// currentDetection.endFrame = frameIndex;
+// }
+// }
+// }
+// if (currentDetection) detectionSegments.push(currentDetection);
+
+// return detectionSegments;
+// }
+
+
+// async function writeFunscript(outputPath: string, actions: FunscriptAction[], helpers: Helpers) {
+// const funscript: Funscript = { version: '1.0', actions };
+// await writeFile(outputPath, JSON.stringify(funscript, null, 2));
+// helpers.logger.info(`Funscript generated: ${outputPath} (${actions.length} actions)`);
+// }
+
+
const createFunscript: Task = async (payload: any, helpers: Helpers) => {
assertPayload(payload);
const { vodId } = payload;
@@ -31,6 +270,7 @@ const createFunscript: Task = async (payload: any, helpers: Helpers) => {
const vod = await prisma.vod.findFirstOrThrow({ where: { id: vodId } });
+ await preparePython()
if (vod.funscript) {
helpers.logger.info(`Doing nothing-- vod ${vodId} already has a funscript.`);
@@ -51,12 +291,11 @@ const createFunscript: Task = async (payload: any, helpers: Helpers) => {
helpers.logger.info(`Creating funscript for vod ${vodId}...`);
- const modelPath = join(env.VIBEUI_DIR, 'vibeui.onnx')
- const predictionOutput = await vibeuiInference(modelPath, videoFilePath);
- helpers.logger.info(`prediction output ${predictionOutput}`);
+ const predictionOutputPath = await inference(videoFilePath);
+ helpers.logger.info(`prediction output ${predictionOutputPath}`);
- const funscriptFilePath = await buildFunscript(predictionOutput, videoFilePath)
+ const funscriptFilePath = await buildFunscript(predictionOutputPath, videoFilePath)
const s3Key = `funscripts/${vodId}.funscript`;
diff --git a/services/our/src/tasks/createFunscript.ts.ai.noexec b/services/our/src/tasks/createFunscript.ts.ai.noexec
new file mode 100644
index 0000000..cf5ab17
--- /dev/null
+++ b/services/our/src/tasks/createFunscript.ts.ai.noexec
@@ -0,0 +1,75 @@
+
+import type { Task, Helpers } from "graphile-worker";
+import { PrismaClient } from "../../generated/prisma";
+import { withAccelerate } from "@prisma/extension-accelerate";
+import { getOrDownloadAsset } from "../utils/cache";
+import { env } from "../config/env";
+import { getS3Client, uploadFile } from "../utils/s3";
+import { buildFunscript } from '../utils/funscripts';
+import { getModelClasses, vibeuiInference } from "../utils/vibeui";
+import { join } from "node:path";
+
+interface Payload {
+ vodId: string;
+}
+
+
+
+const prisma = new PrismaClient().$extends(withAccelerate());
+
+
+function assertPayload(payload: any): asserts payload is Payload {
+ if (typeof payload !== "object" || !payload) throw new Error("invalid payload-- was not an object.");
+ if (typeof payload.vodId !== "string") throw new Error("invalid payload-- was missing vodId");
+}
+
+
+const createFunscript: Task = async (payload: any, helpers: Helpers) => {
+ assertPayload(payload);
+ const { vodId } = payload;
+
+
+
+ const vod = await prisma.vod.findFirstOrThrow({ where: { id: vodId } });
+
+ if (vod.funscript) {
+ helpers.logger.info(`Doing nothing-- vod ${vodId} already has a funscript.`);
+ return;
+ }
+
+ if (!vod.sourceVideo) {
+ const msg = `Cannot create funscript: Vod ${vodId} is missing a source video.`;
+ helpers.logger.warn(msg);
+ throw new Error(msg);
+ }
+
+
+
+ const s3Client = getS3Client();
+ const videoFilePath = await getOrDownloadAsset(s3Client, env.S3_BUCKET, vod.sourceVideo);
+ helpers.logger.info(`Downloaded video to ${videoFilePath}`);
+
+ helpers.logger.info(`Creating funscript for vod ${vodId}...`);
+
+ const modelPath = join(env.VIBEUI_DIR, 'vibeui.onnx')
+ const predictionOutput = await vibeuiInference(modelPath, videoFilePath);
+ helpers.logger.info(`prediction output ${predictionOutput}`);
+ const classes = await getModelClasses(modelPath)
+
+ const funscriptFilePath = await buildFunscript(classes, predictionOutput, videoFilePath)
+
+
+ const s3Key = `funscripts/${vodId}.funscript`;
+ const s3Url = await uploadFile(s3Client, env.S3_BUCKET, s3Key, funscriptFilePath, "application/json");
+
+ helpers.logger.info(`Uploaded funscript to S3: ${s3Url}`);
+
+ await prisma.vod.update({
+ where: { id: vodId },
+ data: { funscript: s3Key }
+ });
+
+ helpers.logger.info(`Funscript saved to database for vod ${vodId}`);
+};
+
+export default createFunscript;
diff --git a/services/our/src/tests/createFunscript.integration.test.ts b/services/our/src/tests/createFunscript.integration.test.ts
new file mode 100644
index 0000000..b3c0899
--- /dev/null
+++ b/services/our/src/tests/createFunscript.integration.test.ts
@@ -0,0 +1,141 @@
+
+import { describe, it, expect, beforeAll } from 'vitest';
+import { join } from 'node:path';
+import { readdir, readFile } from 'node:fs/promises';
+import { loadDataYaml, loadVideoMetadata, inference } from '../utils/vibeui';
+import { env } from '../config/env';
+import { DataYaml, writeFunscript, generateActions1, classPositionMap, buildFunscript } from '../utils/funscripts';
+import { nanoid } from 'nanoid';
+import { getNanoSpawn } from '../utils/nanoSpawn';
+import { preparePython } from '../utils/python';
+
+interface Detection {
+ startFrame: number;
+ endFrame: number;
+ className: string;
+}
+
+
+const __dirname = import.meta.dirname;
+
+const fixturesDir = join(__dirname, '..', 'tests', 'fixtures')
+const sampleVideoPath = join(fixturesDir, 'sample.mp4')
+
+
+/**
+ * Processes YOLO label files and constructs time-aligned detection segments.
+ *
+ * - Reads all `.txt` label files from a directory, one per video frame.
+ * - Parses bounding box class indices and confidence scores.
+ * - Selects the most confident detection per frame above a confidence threshold (0.7).
+ * - Maps class indices to class names using the provided `data.yaml`.
+ * - Groups consecutive frames with the same class into continuous detection segments.
+ *
+ * @param labelDir - Path to the directory containing YOLO label `.txt` files.
+ * @param data - Parsed data.yaml object containing class name mappings.
+ * @returns An array of merged `Detection` objects with start and end frame ranges.
+ * @throws If file reading or parsing fails.
+ */
+async function processLabelFiles(labelDir: string, data: DataYaml): Promise {
+ const labelFiles = (await readdir(labelDir)).filter(file => file.endsWith('.txt'));
+ console.log("Label files found:", labelFiles);
+ const detections: Map = new Map();
+ const names = data.names;
+
+ for (const file of labelFiles) {
+ const match = file.match(/(\d+)\.txt$/);
+ if (!match) {
+ console.log(`Skipping invalid filename: ${file}`);
+ continue;
+ }
+ const frameIndex = parseInt(match[1], 10);
+ if (isNaN(frameIndex)) {
+ console.log(`Skipping invalid frame index from filename: ${file}`);
+ continue;
+ }
+
+ const content = await readFile(join(labelDir, file), 'utf8');
+ const lines = content.trim().split('\n');
+ const frameDetections: Detection[] = [];
+ let maxConfidence = 0;
+ let selectedClassIndex = -1;
+
+ for (const line of lines) {
+ const parts = line.trim().split(/\s+/);
+ if (parts.length < 6) continue;
+
+ const classIndex = parseInt(parts[0], 10);
+ const confidence = parseFloat(parts[5]);
+ if (isNaN(classIndex) || isNaN(confidence)) continue;
+
+ if (confidence >= 0.7 && confidence > maxConfidence) {
+ maxConfidence = confidence;
+ selectedClassIndex = classIndex;
+ }
+ }
+
+ if (maxConfidence > 0) {
+ const className = (data.names as Record)[selectedClassIndex.toString()];
+ if (className) {
+ frameDetections.push({ startFrame: frameIndex, endFrame: frameIndex, className });
+ }
+ }
+
+ if (frameDetections.length > 0) {
+ detections.set(frameIndex, frameDetections);
+ }
+ }
+
+ // Merge overlapping detections into continuous segments
+ const detectionSegments: Detection[] = [];
+ let currentDetection: Detection | null = null;
+
+ for (const [frameIndex, frameDetections] of detections.entries()) {
+ for (const detection of frameDetections) {
+ if (!currentDetection || currentDetection.className !== detection.className) {
+ if (currentDetection) detectionSegments.push(currentDetection);
+ currentDetection = { ...detection, endFrame: frameIndex };
+ } else {
+ currentDetection.endFrame = frameIndex;
+ }
+ }
+ }
+ if (currentDetection) detectionSegments.push(currentDetection);
+
+ return detectionSegments;
+}
+
+
+
+
+
+describe('createFunscript', () => {
+
+ let predictionOutputPath: string
+
+ beforeAll(async () => {
+ predictionOutputPath = await inference(sampleVideoPath)
+ }, 35_000)
+
+ it('should contain positions other than 0', async () => {
+
+
+ const videoPath = join(__dirname, 'fixtures', 'sample.mp4');
+
+ const funscriptPath = await buildFunscript(predictionOutputPath, videoPath);
+ console.log(`built funscript at ${funscriptPath}`)
+
+
+ const content = JSON.parse(await readFile(funscriptPath, 'utf8') as string);
+
+ expect(content).toHaveProperty('version', '1.0');
+ expect(Array.isArray(content.actions)).toBe(true);
+
+ const allPosZero = content.actions.every((a: any) => a.pos === 0);
+ expect(allPosZero).toBe(false); // if this fails, it means the funscript did not generate position values that map to the various detected classes
+
+ expect(content.actions.length).toBeGreaterThan(0);
+ })
+
+
+})
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/data.yaml b/services/our/src/tests/data.yaml
similarity index 100%
rename from services/our/src/tests/fixtures/data.yaml
rename to services/our/src/tests/data.yaml
diff --git a/services/our/src/tests/fixtures/prediction/labels/1.txt b/services/our/src/tests/fixtures/prediction/labels/1.txt
deleted file mode 100644
index b1d4a41..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/1.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.038431 0.089941 0.063310 0.075142 53.145020
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/10.txt b/services/our/src/tests/fixtures/prediction/labels/10.txt
deleted file mode 100644
index b5602a0..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/10.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.038782 0.087836 0.061486 0.073967 52.052288
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/100.txt b/services/our/src/tests/fixtures/prediction/labels/100.txt
deleted file mode 100644
index 38dad46..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/100.txt
+++ /dev/null
@@ -1 +0,0 @@
-56 0.035719 0.076428 0.052471 0.062387 45.073280
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/101.txt b/services/our/src/tests/fixtures/prediction/labels/101.txt
deleted file mode 100644
index 689eab7..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/101.txt
+++ /dev/null
@@ -1 +0,0 @@
-55 0.035468 0.077997 0.051584 0.064488 45.364609
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/102.txt b/services/our/src/tests/fixtures/prediction/labels/102.txt
deleted file mode 100644
index c068591..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/102.txt
+++ /dev/null
@@ -1 +0,0 @@
-49 0.037174 0.075309 0.052773 0.061913 42.606949
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/103.txt b/services/our/src/tests/fixtures/prediction/labels/103.txt
deleted file mode 100644
index 4a7839a..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/103.txt
+++ /dev/null
@@ -1 +0,0 @@
-49 0.036868 0.075512 0.051822 0.062220 42.845398
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/104.txt b/services/our/src/tests/fixtures/prediction/labels/104.txt
deleted file mode 100644
index d5a8317..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/104.txt
+++ /dev/null
@@ -1 +0,0 @@
-58 0.033982 0.077916 0.052823 0.061902 49.985241
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/105.txt b/services/our/src/tests/fixtures/prediction/labels/105.txt
deleted file mode 100644
index 9485f59..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/105.txt
+++ /dev/null
@@ -1 +0,0 @@
-55 0.031318 0.063680 0.047338 0.051505 42.080074
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/106.txt b/services/our/src/tests/fixtures/prediction/labels/106.txt
deleted file mode 100644
index 9eb18ef..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/106.txt
+++ /dev/null
@@ -1 +0,0 @@
-49 0.032635 0.051113 0.043934 0.045176 29.531828
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/107.txt b/services/our/src/tests/fixtures/prediction/labels/107.txt
deleted file mode 100644
index 937f351..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/107.txt
+++ /dev/null
@@ -1 +0,0 @@
-59 0.029333 0.040158 0.037184 0.046115 42.523987
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/108.txt b/services/our/src/tests/fixtures/prediction/labels/108.txt
deleted file mode 100644
index 1538b63..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/108.txt
+++ /dev/null
@@ -1 +0,0 @@
-60 0.023820 0.041723 0.024071 0.052091 51.896393
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/109.txt b/services/our/src/tests/fixtures/prediction/labels/109.txt
deleted file mode 100644
index edd340f..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/109.txt
+++ /dev/null
@@ -1 +0,0 @@
-59 0.028336 0.039560 0.034117 0.048055 45.588940
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/11.txt b/services/our/src/tests/fixtures/prediction/labels/11.txt
deleted file mode 100644
index 754f960..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/11.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.040177 0.089766 0.063404 0.075716 52.578529
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/110.txt b/services/our/src/tests/fixtures/prediction/labels/110.txt
deleted file mode 100644
index d5e8fe1..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/110.txt
+++ /dev/null
@@ -1 +0,0 @@
-52 0.030120 0.048511 0.038019 0.047205 38.291862
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/111.txt b/services/our/src/tests/fixtures/prediction/labels/111.txt
deleted file mode 100644
index 433613d..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/111.txt
+++ /dev/null
@@ -1 +0,0 @@
-51 0.028028 0.048727 0.035394 0.045787 41.533302
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/112.txt b/services/our/src/tests/fixtures/prediction/labels/112.txt
deleted file mode 100644
index 5f7e547..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/112.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.030124 0.056849 0.042351 0.057114 45.306530
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/113.txt b/services/our/src/tests/fixtures/prediction/labels/113.txt
deleted file mode 100644
index 5c030d4..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/113.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.031095 0.054593 0.043671 0.062581 45.146286
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/114.txt b/services/our/src/tests/fixtures/prediction/labels/114.txt
deleted file mode 100644
index f86440f..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/114.txt
+++ /dev/null
@@ -1 +0,0 @@
-55 0.032167 0.056456 0.045219 0.064320 46.130051
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/115.txt b/services/our/src/tests/fixtures/prediction/labels/115.txt
deleted file mode 100644
index ab2b64d..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/115.txt
+++ /dev/null
@@ -1 +0,0 @@
-46 0.022684 0.037057 0.020501 0.039577 37.016544
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/116.txt b/services/our/src/tests/fixtures/prediction/labels/116.txt
deleted file mode 100644
index 4d51bae..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/116.txt
+++ /dev/null
@@ -1 +0,0 @@
-44 0.020022 0.036946 0.020763 0.039563 37.264973
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/117.txt b/services/our/src/tests/fixtures/prediction/labels/117.txt
deleted file mode 100644
index 8807154..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/117.txt
+++ /dev/null
@@ -1 +0,0 @@
-46 0.023001 0.038526 0.025594 0.027946 25.011108
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/118.txt b/services/our/src/tests/fixtures/prediction/labels/118.txt
deleted file mode 100644
index 928fffd..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/118.txt
+++ /dev/null
@@ -1 +0,0 @@
-45 0.021071 0.038914 0.026670 0.028368 23.412777
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/119.txt b/services/our/src/tests/fixtures/prediction/labels/119.txt
deleted file mode 100644
index 4f6551d..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/119.txt
+++ /dev/null
@@ -1 +0,0 @@
-34 0.022739 0.043524 0.029497 0.030117 21.047352
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/12.txt b/services/our/src/tests/fixtures/prediction/labels/12.txt
deleted file mode 100644
index a8d92d2..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/12.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.038738 0.088193 0.061146 0.073904 52.342270
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/120.txt b/services/our/src/tests/fixtures/prediction/labels/120.txt
deleted file mode 100644
index 2c3ce39..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/120.txt
+++ /dev/null
@@ -1 +0,0 @@
-33 0.023468 0.047506 0.032993 0.033216 22.122324
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/121.txt b/services/our/src/tests/fixtures/prediction/labels/121.txt
deleted file mode 100644
index da79eb3..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/121.txt
+++ /dev/null
@@ -1 +0,0 @@
-29 0.024802 0.050099 0.036248 0.036333 23.996782
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/122.txt b/services/our/src/tests/fixtures/prediction/labels/122.txt
deleted file mode 100644
index 59d654b..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/122.txt
+++ /dev/null
@@ -1 +0,0 @@
-27 0.026263 0.054922 0.039299 0.039462 25.030342
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/123.txt b/services/our/src/tests/fixtures/prediction/labels/123.txt
deleted file mode 100644
index c53b1bb..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/123.txt
+++ /dev/null
@@ -1 +0,0 @@
-27 0.027968 0.059208 0.042927 0.041978 26.387854
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/124.txt b/services/our/src/tests/fixtures/prediction/labels/124.txt
deleted file mode 100644
index c42c9f1..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/124.txt
+++ /dev/null
@@ -1 +0,0 @@
-27 0.026905 0.057451 0.040628 0.041066 26.058964
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/125.txt b/services/our/src/tests/fixtures/prediction/labels/125.txt
deleted file mode 100644
index 64969f2..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/125.txt
+++ /dev/null
@@ -1 +0,0 @@
-28 0.027567 0.057046 0.041958 0.042930 27.190252
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/126.txt b/services/our/src/tests/fixtures/prediction/labels/126.txt
deleted file mode 100644
index 4131868..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/126.txt
+++ /dev/null
@@ -1 +0,0 @@
-28 0.027424 0.058000 0.041726 0.043075 27.314507
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/127.txt b/services/our/src/tests/fixtures/prediction/labels/127.txt
deleted file mode 100644
index b21cfc1..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/127.txt
+++ /dev/null
@@ -1 +0,0 @@
-28 0.027354 0.059303 0.041741 0.043003 27.271029
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/128.txt b/services/our/src/tests/fixtures/prediction/labels/128.txt
deleted file mode 100644
index fee2376..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/128.txt
+++ /dev/null
@@ -1 +0,0 @@
-28 0.027216 0.060089 0.041448 0.042873 27.201962
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/129.txt b/services/our/src/tests/fixtures/prediction/labels/129.txt
deleted file mode 100644
index 9cbbd6f..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/129.txt
+++ /dev/null
@@ -1 +0,0 @@
-28 0.027259 0.059818 0.041481 0.042875 27.166683
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/13.txt b/services/our/src/tests/fixtures/prediction/labels/13.txt
deleted file mode 100644
index dd3f9c0..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/13.txt
+++ /dev/null
@@ -1 +0,0 @@
-55 0.038905 0.088440 0.061114 0.074878 52.844406
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/130.txt b/services/our/src/tests/fixtures/prediction/labels/130.txt
deleted file mode 100644
index 32bf287..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/130.txt
+++ /dev/null
@@ -1 +0,0 @@
-28 0.027364 0.058919 0.041654 0.043002 27.260029
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/131.txt b/services/our/src/tests/fixtures/prediction/labels/131.txt
deleted file mode 100644
index c324648..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/131.txt
+++ /dev/null
@@ -1 +0,0 @@
-28 0.027422 0.059680 0.041713 0.043012 27.289568
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/132.txt b/services/our/src/tests/fixtures/prediction/labels/132.txt
deleted file mode 100644
index 641675b..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/132.txt
+++ /dev/null
@@ -1 +0,0 @@
-28 0.027505 0.060784 0.041890 0.042989 27.195068
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/133.txt b/services/our/src/tests/fixtures/prediction/labels/133.txt
deleted file mode 100644
index cfad7df..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/133.txt
+++ /dev/null
@@ -1 +0,0 @@
-28 0.027367 0.061949 0.041727 0.043011 27.209845
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/134.txt b/services/our/src/tests/fixtures/prediction/labels/134.txt
deleted file mode 100644
index 86f5737..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/134.txt
+++ /dev/null
@@ -1 +0,0 @@
-28 0.027787 0.063247 0.042555 0.043069 27.263746
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/135.txt b/services/our/src/tests/fixtures/prediction/labels/135.txt
deleted file mode 100644
index fa9b794..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/135.txt
+++ /dev/null
@@ -1 +0,0 @@
-31 0.029057 0.066847 0.045142 0.044002 27.712589
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/136.txt b/services/our/src/tests/fixtures/prediction/labels/136.txt
deleted file mode 100644
index a8cbcb8..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/136.txt
+++ /dev/null
@@ -1 +0,0 @@
-47 0.038125 0.073041 0.063277 0.069168 46.141243
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/137.txt b/services/our/src/tests/fixtures/prediction/labels/137.txt
deleted file mode 100644
index 89cdeb6..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/137.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.038465 0.077648 0.063928 0.070589 47.159187
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/138.txt b/services/our/src/tests/fixtures/prediction/labels/138.txt
deleted file mode 100644
index 3af86a1..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/138.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.038857 0.077233 0.064686 0.071919 47.977119
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/139.txt b/services/our/src/tests/fixtures/prediction/labels/139.txt
deleted file mode 100644
index a859b20..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/139.txt
+++ /dev/null
@@ -1 +0,0 @@
-49 0.039132 0.078201 0.065272 0.072841 48.427032
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/14.txt b/services/our/src/tests/fixtures/prediction/labels/14.txt
deleted file mode 100644
index 59ef193..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/14.txt
+++ /dev/null
@@ -1 +0,0 @@
-56 0.041007 0.094351 0.065533 0.077863 53.407448
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/140.txt b/services/our/src/tests/fixtures/prediction/labels/140.txt
deleted file mode 100644
index 7a926ff..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/140.txt
+++ /dev/null
@@ -1 +0,0 @@
-49 0.039132 0.079005 0.065348 0.073051 48.494751
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/141.txt b/services/our/src/tests/fixtures/prediction/labels/141.txt
deleted file mode 100644
index 910fd58..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/141.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.039090 0.075240 0.065312 0.072294 47.814663
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/142.txt b/services/our/src/tests/fixtures/prediction/labels/142.txt
deleted file mode 100644
index 9f25805..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/142.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.039028 0.076693 0.065173 0.072094 47.912033
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/143.txt b/services/our/src/tests/fixtures/prediction/labels/143.txt
deleted file mode 100644
index 3432488..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/143.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.038899 0.073056 0.064984 0.071549 47.317184
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/144.txt b/services/our/src/tests/fixtures/prediction/labels/144.txt
deleted file mode 100644
index 1c31a49..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/144.txt
+++ /dev/null
@@ -1 +0,0 @@
-47 0.038281 0.067039 0.063682 0.068646 45.645699
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/145.txt b/services/our/src/tests/fixtures/prediction/labels/145.txt
deleted file mode 100644
index 949a350..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/145.txt
+++ /dev/null
@@ -1 +0,0 @@
-47 0.037488 0.066094 0.062121 0.068092 46.430637
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/146.txt b/services/our/src/tests/fixtures/prediction/labels/146.txt
deleted file mode 100644
index e5aa4dc..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/146.txt
+++ /dev/null
@@ -1 +0,0 @@
-43 0.036806 0.065111 0.060806 0.064199 42.797428
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/147.txt b/services/our/src/tests/fixtures/prediction/labels/147.txt
deleted file mode 100644
index 4903cfd..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/147.txt
+++ /dev/null
@@ -1 +0,0 @@
-43 0.036809 0.068780 0.060680 0.063662 42.322411
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/148.txt b/services/our/src/tests/fixtures/prediction/labels/148.txt
deleted file mode 100644
index ac3a9f3..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/148.txt
+++ /dev/null
@@ -1 +0,0 @@
-41 0.036440 0.072136 0.059879 0.061923 40.799137
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/149.txt b/services/our/src/tests/fixtures/prediction/labels/149.txt
deleted file mode 100644
index 7a14ba6..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/149.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.036187 0.067445 0.056100 0.063240 44.677353
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/15.txt b/services/our/src/tests/fixtures/prediction/labels/15.txt
deleted file mode 100644
index ddeebd0..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/15.txt
+++ /dev/null
@@ -1 +0,0 @@
-55 0.040299 0.095144 0.065489 0.078165 53.380753
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/16.txt b/services/our/src/tests/fixtures/prediction/labels/16.txt
deleted file mode 100644
index fea4b94..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/16.txt
+++ /dev/null
@@ -1 +0,0 @@
-55 0.040405 0.097322 0.066765 0.079255 53.374428
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/17.txt b/services/our/src/tests/fixtures/prediction/labels/17.txt
deleted file mode 100644
index 2735d71..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/17.txt
+++ /dev/null
@@ -1 +0,0 @@
-55 0.038764 0.090667 0.063157 0.076659 53.355133
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/18.txt b/services/our/src/tests/fixtures/prediction/labels/18.txt
deleted file mode 100644
index 00b1802..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/18.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.038673 0.089891 0.063106 0.076448 53.004646
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/19.txt b/services/our/src/tests/fixtures/prediction/labels/19.txt
deleted file mode 100644
index 1b5cf50..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/19.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.038899 0.091212 0.063614 0.076383 52.351440
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/2.txt b/services/our/src/tests/fixtures/prediction/labels/2.txt
deleted file mode 100644
index 9a2b904..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/2.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.038586 0.088787 0.063747 0.075643 53.104782
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/20.txt b/services/our/src/tests/fixtures/prediction/labels/20.txt
deleted file mode 100644
index 9a91d09..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/20.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.037872 0.087844 0.061520 0.075063 52.219879
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/21.txt b/services/our/src/tests/fixtures/prediction/labels/21.txt
deleted file mode 100644
index 9d617e3..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/21.txt
+++ /dev/null
@@ -1 +0,0 @@
-50 0.037195 0.086863 0.060343 0.073201 50.691704
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/22.txt b/services/our/src/tests/fixtures/prediction/labels/22.txt
deleted file mode 100644
index 20fb9a6..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/22.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.038759 0.092685 0.063432 0.076361 52.002579
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/23.txt b/services/our/src/tests/fixtures/prediction/labels/23.txt
deleted file mode 100644
index cbebb54..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/23.txt
+++ /dev/null
@@ -1 +0,0 @@
-52 0.040353 0.090205 0.065287 0.077373 51.746628
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/24.txt b/services/our/src/tests/fixtures/prediction/labels/24.txt
deleted file mode 100644
index 0473012..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/24.txt
+++ /dev/null
@@ -1 +0,0 @@
-52 0.040583 0.091694 0.066280 0.077802 51.581718
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/25.txt b/services/our/src/tests/fixtures/prediction/labels/25.txt
deleted file mode 100644
index 6efe144..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/25.txt
+++ /dev/null
@@ -1 +0,0 @@
-49 0.041297 0.094172 0.067527 0.076987 50.565460
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/26.txt b/services/our/src/tests/fixtures/prediction/labels/26.txt
deleted file mode 100644
index 411ea8a..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/26.txt
+++ /dev/null
@@ -1 +0,0 @@
-50 0.040312 0.090680 0.065473 0.075966 50.742157
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/27.txt b/services/our/src/tests/fixtures/prediction/labels/27.txt
deleted file mode 100644
index 458492e..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/27.txt
+++ /dev/null
@@ -1 +0,0 @@
-50 0.039734 0.092913 0.064935 0.076198 50.960922
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/28.txt b/services/our/src/tests/fixtures/prediction/labels/28.txt
deleted file mode 100644
index 8608d0b..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/28.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.040137 0.089994 0.065064 0.074114 49.276997
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/29.txt b/services/our/src/tests/fixtures/prediction/labels/29.txt
deleted file mode 100644
index 95eb993..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/29.txt
+++ /dev/null
@@ -1 +0,0 @@
-47 0.040732 0.094854 0.067243 0.076296 49.356400
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/3.txt b/services/our/src/tests/fixtures/prediction/labels/3.txt
deleted file mode 100644
index 1bb0c4a..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/3.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.038630 0.087661 0.063683 0.075626 52.907776
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/30.txt b/services/our/src/tests/fixtures/prediction/labels/30.txt
deleted file mode 100644
index 0782515..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/30.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.041461 0.093046 0.068027 0.076524 49.280266
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/31.txt b/services/our/src/tests/fixtures/prediction/labels/31.txt
deleted file mode 100644
index 7b30659..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/31.txt
+++ /dev/null
@@ -1 +0,0 @@
-46 0.040502 0.082802 0.064679 0.074476 48.285706
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/32.txt b/services/our/src/tests/fixtures/prediction/labels/32.txt
deleted file mode 100644
index ea5376b..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/32.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.040258 0.083422 0.064046 0.075326 49.381264
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/33.txt b/services/our/src/tests/fixtures/prediction/labels/33.txt
deleted file mode 100644
index 61e917f..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/33.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.040601 0.090676 0.066223 0.076538 49.821045
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/34.txt b/services/our/src/tests/fixtures/prediction/labels/34.txt
deleted file mode 100644
index 0123411..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/34.txt
+++ /dev/null
@@ -1 +0,0 @@
-46 0.039033 0.083210 0.063061 0.073626 48.440258
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/35.txt b/services/our/src/tests/fixtures/prediction/labels/35.txt
deleted file mode 100644
index 4d3ddb1..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/35.txt
+++ /dev/null
@@ -1 +0,0 @@
-43 0.032891 0.045700 0.029322 0.035714 35.217300
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/36.txt b/services/our/src/tests/fixtures/prediction/labels/36.txt
deleted file mode 100644
index 2cb5cb0..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/36.txt
+++ /dev/null
@@ -1 +0,0 @@
-50 0.041740 0.059307 0.038156 0.048756 40.737473
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/37.txt b/services/our/src/tests/fixtures/prediction/labels/37.txt
deleted file mode 100644
index d895592..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/37.txt
+++ /dev/null
@@ -1 +0,0 @@
-51 0.043773 0.062391 0.039251 0.053317 44.349907
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/38.txt b/services/our/src/tests/fixtures/prediction/labels/38.txt
deleted file mode 100644
index 0aede67..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/38.txt
+++ /dev/null
@@ -1 +0,0 @@
-50 0.044851 0.059177 0.037444 0.047610 41.626480
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/39.txt b/services/our/src/tests/fixtures/prediction/labels/39.txt
deleted file mode 100644
index 82fdf32..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/39.txt
+++ /dev/null
@@ -1 +0,0 @@
-49 0.043650 0.054320 0.035061 0.041959 39.615734
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/4.txt b/services/our/src/tests/fixtures/prediction/labels/4.txt
deleted file mode 100644
index e64aaf5..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/4.txt
+++ /dev/null
@@ -1 +0,0 @@
-55 0.039047 0.089085 0.064675 0.076483 53.375942
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/40.txt b/services/our/src/tests/fixtures/prediction/labels/40.txt
deleted file mode 100644
index da8a00b..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/40.txt
+++ /dev/null
@@ -1 +0,0 @@
-52 0.047915 0.067200 0.042890 0.058544 45.186440
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/41.txt b/services/our/src/tests/fixtures/prediction/labels/41.txt
deleted file mode 100644
index 7d1dba8..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/41.txt
+++ /dev/null
@@ -1 +0,0 @@
-52 0.047528 0.066358 0.042432 0.058007 44.913727
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/42.txt b/services/our/src/tests/fixtures/prediction/labels/42.txt
deleted file mode 100644
index eaa1cb1..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/42.txt
+++ /dev/null
@@ -1 +0,0 @@
-52 0.041072 0.056998 0.035125 0.052732 44.344086
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/43.txt b/services/our/src/tests/fixtures/prediction/labels/43.txt
deleted file mode 100644
index cc1241e..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/43.txt
+++ /dev/null
@@ -1 +0,0 @@
-51 0.043158 0.061411 0.039824 0.057852 44.955292
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/44.txt b/services/our/src/tests/fixtures/prediction/labels/44.txt
deleted file mode 100644
index 12f1502..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/44.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.039598 0.052803 0.031997 0.049200 40.695629
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/45.txt b/services/our/src/tests/fixtures/prediction/labels/45.txt
deleted file mode 100644
index 83a8aad..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/45.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.038581 0.050030 0.030199 0.045505 40.698929
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/46.txt b/services/our/src/tests/fixtures/prediction/labels/46.txt
deleted file mode 100644
index 6dfeb40..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/46.txt
+++ /dev/null
@@ -1 +0,0 @@
-41 0.036938 0.043018 0.028460 0.033426 32.071121
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/47.txt b/services/our/src/tests/fixtures/prediction/labels/47.txt
deleted file mode 100644
index 9deee52..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/47.txt
+++ /dev/null
@@ -1 +0,0 @@
-40 0.036963 0.042987 0.028471 0.033255 31.860889
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/48.txt b/services/our/src/tests/fixtures/prediction/labels/48.txt
deleted file mode 100644
index 864857a..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/48.txt
+++ /dev/null
@@ -1 +0,0 @@
-36 0.042042 0.054596 0.038157 0.050414 32.152676
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/49.txt b/services/our/src/tests/fixtures/prediction/labels/49.txt
deleted file mode 100644
index 8462647..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/49.txt
+++ /dev/null
@@ -1 +0,0 @@
-36 0.042830 0.055505 0.039300 0.051146 32.325150
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/5.txt b/services/our/src/tests/fixtures/prediction/labels/5.txt
deleted file mode 100644
index 1499d38..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/5.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.039289 0.088586 0.064755 0.075773 52.296677
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/50.txt b/services/our/src/tests/fixtures/prediction/labels/50.txt
deleted file mode 100644
index 1864c11..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/50.txt
+++ /dev/null
@@ -1 +0,0 @@
-39 0.041617 0.047316 0.032837 0.037152 32.227730
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/51.txt b/services/our/src/tests/fixtures/prediction/labels/51.txt
deleted file mode 100644
index 67dfaf5..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/51.txt
+++ /dev/null
@@ -1 +0,0 @@
-39 0.041716 0.047384 0.032923 0.037170 32.099144
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/52.txt b/services/our/src/tests/fixtures/prediction/labels/52.txt
deleted file mode 100644
index 9770109..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/52.txt
+++ /dev/null
@@ -1 +0,0 @@
-39 0.036623 0.042752 0.029092 0.031746 31.368042
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/53.txt b/services/our/src/tests/fixtures/prediction/labels/53.txt
deleted file mode 100644
index a0aea58..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/53.txt
+++ /dev/null
@@ -1 +0,0 @@
-39 0.036620 0.042687 0.029048 0.031663 31.362764
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/54.txt b/services/our/src/tests/fixtures/prediction/labels/54.txt
deleted file mode 100644
index d11e013..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/54.txt
+++ /dev/null
@@ -1 +0,0 @@
-41 0.038580 0.054718 0.037448 0.051601 35.002811
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/55.txt b/services/our/src/tests/fixtures/prediction/labels/55.txt
deleted file mode 100644
index 7655d83..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/55.txt
+++ /dev/null
@@ -1 +0,0 @@
-41 0.037821 0.054225 0.036801 0.051092 34.945328
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/56.txt b/services/our/src/tests/fixtures/prediction/labels/56.txt
deleted file mode 100644
index a7fb0ef..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/56.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.034458 0.054829 0.035582 0.055812 40.425453
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/57.txt b/services/our/src/tests/fixtures/prediction/labels/57.txt
deleted file mode 100644
index 015255f..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/57.txt
+++ /dev/null
@@ -1 +0,0 @@
-48 0.034686 0.054091 0.035186 0.054277 40.062107
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/58.txt b/services/our/src/tests/fixtures/prediction/labels/58.txt
deleted file mode 100644
index 14426b9..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/58.txt
+++ /dev/null
@@ -1 +0,0 @@
-57 0.038837 0.082894 0.062552 0.069445 51.872215
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/59.txt b/services/our/src/tests/fixtures/prediction/labels/59.txt
deleted file mode 100644
index 685a8f1..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/59.txt
+++ /dev/null
@@ -1 +0,0 @@
-32 0.022999 0.043750 0.022722 0.048968 31.945200
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/6.txt b/services/our/src/tests/fixtures/prediction/labels/6.txt
deleted file mode 100644
index 7e83cb9..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/6.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.040092 0.091829 0.066258 0.077107 52.671627
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/60.txt b/services/our/src/tests/fixtures/prediction/labels/60.txt
deleted file mode 100644
index f405358..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/60.txt
+++ /dev/null
@@ -1 +0,0 @@
-41 0.026247 0.049599 0.024864 0.050194 36.840691
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/61.txt b/services/our/src/tests/fixtures/prediction/labels/61.txt
deleted file mode 100644
index 3baa9e3..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/61.txt
+++ /dev/null
@@ -1 +0,0 @@
-66 0.022907 0.039306 0.022662 0.046313 48.672504
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/62.txt b/services/our/src/tests/fixtures/prediction/labels/62.txt
deleted file mode 100644
index 7d0d892..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/62.txt
+++ /dev/null
@@ -1 +0,0 @@
-57 0.039894 0.090226 0.061569 0.068168 51.285439
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/63.txt b/services/our/src/tests/fixtures/prediction/labels/63.txt
deleted file mode 100644
index 9d3b156..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/63.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.048924 0.091334 0.059758 0.067995 50.548450
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/64.txt b/services/our/src/tests/fixtures/prediction/labels/64.txt
deleted file mode 100644
index 3b71f65..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/64.txt
+++ /dev/null
@@ -1 +0,0 @@
-58 0.039831 0.087696 0.062446 0.068166 53.084751
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/65.txt b/services/our/src/tests/fixtures/prediction/labels/65.txt
deleted file mode 100644
index db561b8..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/65.txt
+++ /dev/null
@@ -1 +0,0 @@
-58 0.040612 0.090068 0.065153 0.071738 54.392399
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/66.txt b/services/our/src/tests/fixtures/prediction/labels/66.txt
deleted file mode 100644
index 2cb3e26..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/66.txt
+++ /dev/null
@@ -1 +0,0 @@
-60 0.041616 0.093175 0.065634 0.073091 55.869972
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/67.txt b/services/our/src/tests/fixtures/prediction/labels/67.txt
deleted file mode 100644
index b418ac6..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/67.txt
+++ /dev/null
@@ -1 +0,0 @@
-56 0.040496 0.086040 0.062963 0.069799 53.591637
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/68.txt b/services/our/src/tests/fixtures/prediction/labels/68.txt
deleted file mode 100644
index 2e7ee85..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/68.txt
+++ /dev/null
@@ -1 +0,0 @@
-59 0.041022 0.083298 0.064152 0.072956 55.851673
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/69.txt b/services/our/src/tests/fixtures/prediction/labels/69.txt
deleted file mode 100644
index ce017be..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/69.txt
+++ /dev/null
@@ -1 +0,0 @@
-62 0.043478 0.091953 0.069978 0.079008 58.172695
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/7.txt b/services/our/src/tests/fixtures/prediction/labels/7.txt
deleted file mode 100644
index 6016a81..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/7.txt
+++ /dev/null
@@ -1 +0,0 @@
-55 0.039878 0.092010 0.065897 0.077365 53.188076
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/70.txt b/services/our/src/tests/fixtures/prediction/labels/70.txt
deleted file mode 100644
index 492af89..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/70.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.034369 0.058474 0.056676 0.065891 48.764679
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/71.txt b/services/our/src/tests/fixtures/prediction/labels/71.txt
deleted file mode 100644
index d3200f5..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/71.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034419 0.063677 0.056727 0.065873 48.853603
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/72.txt b/services/our/src/tests/fixtures/prediction/labels/72.txt
deleted file mode 100644
index cdbbd9a..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/72.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.034378 0.063580 0.056653 0.065783 48.770065
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/73.txt b/services/our/src/tests/fixtures/prediction/labels/73.txt
deleted file mode 100644
index 00696ad..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/73.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034456 0.063362 0.056788 0.065928 48.954887
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/74.txt b/services/our/src/tests/fixtures/prediction/labels/74.txt
deleted file mode 100644
index ec57a03..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/74.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034375 0.059440 0.056584 0.065834 48.820354
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/75.txt b/services/our/src/tests/fixtures/prediction/labels/75.txt
deleted file mode 100644
index ccea3bd..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/75.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034421 0.059616 0.056723 0.066008 48.958206
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/76.txt b/services/our/src/tests/fixtures/prediction/labels/76.txt
deleted file mode 100644
index 4c9e2ed..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/76.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034434 0.059725 0.056748 0.066033 48.979515
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/77.txt b/services/our/src/tests/fixtures/prediction/labels/77.txt
deleted file mode 100644
index f72eaf4..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/77.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034548 0.054414 0.057141 0.066319 49.319530
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/78.txt b/services/our/src/tests/fixtures/prediction/labels/78.txt
deleted file mode 100644
index 8c5635e..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/78.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034338 0.052701 0.056745 0.066531 49.484806
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/79.txt b/services/our/src/tests/fixtures/prediction/labels/79.txt
deleted file mode 100644
index 758062c..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/79.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034612 0.054135 0.057341 0.066714 49.643581
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/8.txt b/services/our/src/tests/fixtures/prediction/labels/8.txt
deleted file mode 100644
index 4b37b3b..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/8.txt
+++ /dev/null
@@ -1 +0,0 @@
-52 0.039667 0.088961 0.064458 0.075846 52.022839
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/80.txt b/services/our/src/tests/fixtures/prediction/labels/80.txt
deleted file mode 100644
index 3ae0bef..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/80.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034607 0.054053 0.057334 0.066706 49.647636
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/81.txt b/services/our/src/tests/fixtures/prediction/labels/81.txt
deleted file mode 100644
index 02a32d1..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/81.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034249 0.053221 0.056632 0.066137 49.059483
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/82.txt b/services/our/src/tests/fixtures/prediction/labels/82.txt
deleted file mode 100644
index 9426df9..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/82.txt
+++ /dev/null
@@ -1 +0,0 @@
-54 0.034260 0.053261 0.056656 0.066161 49.091011
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/83.txt b/services/our/src/tests/fixtures/prediction/labels/83.txt
deleted file mode 100644
index be803d0..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/83.txt
+++ /dev/null
@@ -1 +0,0 @@
-46 0.035543 0.078270 0.053312 0.060655 39.456207
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/84.txt b/services/our/src/tests/fixtures/prediction/labels/84.txt
deleted file mode 100644
index a16151d..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/84.txt
+++ /dev/null
@@ -1 +0,0 @@
-47 0.035702 0.079416 0.054139 0.061860 40.916161
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/85.txt b/services/our/src/tests/fixtures/prediction/labels/85.txt
deleted file mode 100644
index 802f54d..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/85.txt
+++ /dev/null
@@ -1 +0,0 @@
-44 0.035436 0.079698 0.053465 0.059294 37.065166
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/86.txt b/services/our/src/tests/fixtures/prediction/labels/86.txt
deleted file mode 100644
index 3cb891b..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/86.txt
+++ /dev/null
@@ -1 +0,0 @@
-39 0.034088 0.075423 0.050748 0.055254 32.651058
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/87.txt b/services/our/src/tests/fixtures/prediction/labels/87.txt
deleted file mode 100644
index ecabf64..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/87.txt
+++ /dev/null
@@ -1 +0,0 @@
-39 0.032986 0.070509 0.048970 0.054720 32.961876
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/88.txt b/services/our/src/tests/fixtures/prediction/labels/88.txt
deleted file mode 100644
index 2c81227..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/88.txt
+++ /dev/null
@@ -1 +0,0 @@
-34 0.032306 0.068780 0.047561 0.052743 30.510756
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/89.txt b/services/our/src/tests/fixtures/prediction/labels/89.txt
deleted file mode 100644
index 92fcf70..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/89.txt
+++ /dev/null
@@ -1 +0,0 @@
-33 0.031765 0.067288 0.046688 0.050976 29.676157
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/9.txt b/services/our/src/tests/fixtures/prediction/labels/9.txt
deleted file mode 100644
index 63a0b1c..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/9.txt
+++ /dev/null
@@ -1 +0,0 @@
-53 0.039416 0.087546 0.062930 0.075004 51.850227
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/90.txt b/services/our/src/tests/fixtures/prediction/labels/90.txt
deleted file mode 100644
index dadb00a..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/90.txt
+++ /dev/null
@@ -1 +0,0 @@
-32 0.032639 0.069295 0.046686 0.049750 28.727793
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/91.txt b/services/our/src/tests/fixtures/prediction/labels/91.txt
deleted file mode 100644
index 4a1e1b3..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/91.txt
+++ /dev/null
@@ -1 +0,0 @@
-34 0.032972 0.061634 0.045092 0.051609 29.038794
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/92.txt b/services/our/src/tests/fixtures/prediction/labels/92.txt
deleted file mode 100644
index 956e47b..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/92.txt
+++ /dev/null
@@ -1 +0,0 @@
-34 0.034734 0.065177 0.044635 0.050590 28.128407
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/93.txt b/services/our/src/tests/fixtures/prediction/labels/93.txt
deleted file mode 100644
index 92df475..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/93.txt
+++ /dev/null
@@ -1 +0,0 @@
-34 0.032320 0.056962 0.042639 0.047532 27.319426
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/94.txt b/services/our/src/tests/fixtures/prediction/labels/94.txt
deleted file mode 100644
index 9ac2a36..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/94.txt
+++ /dev/null
@@ -1 +0,0 @@
-36 0.031656 0.055654 0.040966 0.049437 28.343576
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/95.txt b/services/our/src/tests/fixtures/prediction/labels/95.txt
deleted file mode 100644
index 4546e86..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/95.txt
+++ /dev/null
@@ -1 +0,0 @@
-34 0.031890 0.055906 0.042168 0.049285 28.531000
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/96.txt b/services/our/src/tests/fixtures/prediction/labels/96.txt
deleted file mode 100644
index 1d3e469..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/96.txt
+++ /dev/null
@@ -1 +0,0 @@
-32 0.034397 0.063852 0.044452 0.051638 29.653748
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/97.txt b/services/our/src/tests/fixtures/prediction/labels/97.txt
deleted file mode 100644
index b57ac97..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/97.txt
+++ /dev/null
@@ -1 +0,0 @@
-32 0.035273 0.066313 0.045359 0.051649 29.548565
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/98.txt b/services/our/src/tests/fixtures/prediction/labels/98.txt
deleted file mode 100644
index fed89b6..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/98.txt
+++ /dev/null
@@ -1 +0,0 @@
-36 0.035006 0.063150 0.044154 0.051334 30.672239
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/labels/99.txt b/services/our/src/tests/fixtures/prediction/labels/99.txt
deleted file mode 100644
index 1619706..0000000
--- a/services/our/src/tests/fixtures/prediction/labels/99.txt
+++ /dev/null
@@ -1 +0,0 @@
-61 0.035569 0.067777 0.045799 0.061890 44.171967
\ No newline at end of file
diff --git a/services/our/src/tests/fixtures/prediction/output-sharp.png b/services/our/src/tests/fixtures/prediction/output-sharp.png
new file mode 100644
index 0000000..e27c90d
Binary files /dev/null and b/services/our/src/tests/fixtures/prediction/output-sharp.png differ
diff --git a/services/our/src/tests/funscripts.integration.test.ts b/services/our/src/tests/funscripts.integration.test.ts
index 37ca9cd..7fd6d07 100644
--- a/services/our/src/tests/funscripts.integration.test.ts
+++ b/services/our/src/tests/funscripts.integration.test.ts
@@ -1,5 +1,5 @@
// funscripts.integration.ts
-import { describe, it, expect, vi, beforeEach, afterEach, beforeAll } from 'vitest';
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import {
buildFunscript,
} from '../utils/funscripts';
@@ -8,14 +8,9 @@ import { join } from 'node:path';
import { tmpdir } from 'node:os';
-// Integration test (no mocks)
describe('[integration] buildFunscript', () => {
const TMP_DIR = join(tmpdir(), 'funscript-test');
- beforeAll(() => {
- vi.doUnmock('fs-extra');
- });
-
beforeEach(async () => {
await mkdir(TMP_DIR, { recursive: true });
});
diff --git a/services/our/src/tests/funscripts.unit.test.ts b/services/our/src/tests/funscripts.unit.test.ts
index 8901e82..a9bbd32 100644
--- a/services/our/src/tests/funscripts.unit.test.ts
+++ b/services/our/src/tests/funscripts.unit.test.ts
@@ -1,7 +1,7 @@
// funscripts.unit.ts
import { describe, it, expect, vi, beforeEach, afterEach, beforeAll } from 'vitest';
import {
- loadClassPositionMap,
+ classPositionMap,
generatePatternPositions,
generateActions,
writeFunscript,
@@ -31,19 +31,6 @@ const mockedWriteJson = vi.mocked(writeJson);
describe('funscript utils', () => {
- describe('loadClassPositionMap', () => {
- it('maps known and unknown class names correctly', async () => {
- const data: DataYaml = {
- names: { '0': 'RespondingTo', '1': 'UnknownClass' },
- path: '',
- train: '',
- val: ''
- };
- const result = await loadClassPositionMap(data);
- expect(result.RespondingTo).toBe(5);
- expect(result.UnknownClass).toBe(0);
- });
- });
describe('generatePatternPositions', () => {
it('generates wave pattern correctly', () => {
diff --git a/services/our/src/tests/inference.integration.test.ts.noexec b/services/our/src/tests/inference.integration.test.ts.noexec
new file mode 100644
index 0000000..46b705e
--- /dev/null
+++ b/services/our/src/tests/inference.integration.test.ts.noexec
@@ -0,0 +1,214 @@
+// inference.integration.test.ts
+
+// this idea was to use onnx, so we could run inference via node.
+// I couldn't figure it out-- the detection bounding boxes were in the wrong place.
+// I'm going back to shelling out to pytorch. saving this for reference.
+
+import { describe, it, expect, vi, beforeEach, afterEach, beforeAll } from 'vitest';
+import { InferenceSession, Tensor } from 'onnxruntime-node';
+import { join } from 'node:path';
+import { preprocessImage } from '../utils/vibeui';
+import { createCanvas, loadImage } from 'canvas';
+import { writeFileSync } from 'fs';
+import sharp from 'sharp';
+
+const __dirname = import.meta.dirname;
+
+const distDir = join(__dirname, '../../dist')
+const fixturesDir = join(__dirname, '..', 'tests', 'fixtures')
+const modelPath = join(distDir, 'vibeui', 'vibeui.onnx')
+const sampleFrame = join(fixturesDir, 'prediction', 'frames', '000001.jpg')
+
+const NUM_BOXES = 8400;
+const NUM_CLASSES = 19;
+const CONFIDENCE_THRESHOLD = 0.9; // tune as needed
+const IMAGE_WIDTH = 640;
+const IMAGE_HEIGHT = 640;
+
+type Detection = {
+ x1: number;
+ y1: number;
+ x2: number;
+ y2: number;
+ confidence: number;
+ classIndex: number;
+ classScore: number;
+};
+
+function iou(a: Detection, b: Detection): number {
+ const x1 = Math.max(a.x1, b.x1);
+ const y1 = Math.max(a.y1, b.y1);
+ const x2 = Math.min(a.x2, b.x2);
+ const y2 = Math.min(a.y2, b.y2);
+
+ const intersection = Math.max(0, x2 - x1) * Math.max(0, y2 - y1);
+ const areaA = (a.x2 - a.x1) * (a.y2 - a.y1);
+ const areaB = (b.x2 - b.x1) * (b.y2 - b.y1);
+ const union = areaA + areaB - intersection;
+
+ return intersection / union;
+}
+
+function nms(detections: Detection[], iouThreshold = 0.45, topK = 50): Detection[] {
+ const sorted = [...detections].sort((a, b) => b.confidence - a.confidence);
+ const selected: Detection[] = [];
+
+ while (sorted.length > 0 && selected.length < topK) {
+ const best = sorted.shift()!;
+ selected.push(best);
+
+ for (let i = sorted.length - 1; i >= 0; i--) {
+ if (iou(best, sorted[i]) > iouThreshold) {
+ sorted.splice(i, 1); // remove overlapping box
+ }
+ }
+ }
+
+ return selected;
+}
+
+function softmax(logits: Float32Array): number[] {
+ const max = Math.max(...logits);
+ const exps = logits.map(x => Math.exp(x - max));
+ const sum = exps.reduce((a, b) => a + b, 0);
+ return exps.map(e => e / sum);
+}
+
+function sigmoid(x: number): number {
+ return 1 / (1 + Math.exp(-x));
+}
+
+function postprocessTensor(tensor: Tensor): Detection[] {
+ const results: Detection[] = [];
+ const data = tensor.cpuData;
+
+ for (let i = 0; i < NUM_BOXES; i++) {
+ const offset = i * 24;
+
+ const cx = data[offset + 0]; // already in pixel coords
+ const cy = data[offset + 1];
+ const w = data[offset + 2];
+ const h = data[offset + 3];
+
+ const objectness = sigmoid(data[offset + 4]);
+ if (objectness < CONFIDENCE_THRESHOLD) continue;
+
+ const classLogits = data.slice(offset + 5, offset + 24);
+ const classScores = softmax(classLogits as Float32Array);
+
+ const maxClassScore = Math.max(...classScores);
+ const classIndex = classScores.findIndex(score => score === maxClassScore);
+
+ const confidence = objectness * maxClassScore;
+ if (confidence < CONFIDENCE_THRESHOLD) continue;
+
+ const x1 = cx - w / 2;
+ const y1 = cy - h / 2;
+ const x2 = cx + w / 2;
+ const y2 = cy + h / 2;
+
+ results.push({
+ x1,
+ y1,
+ x2,
+ y2,
+ confidence,
+ classIndex,
+ classScore: maxClassScore,
+ });
+ }
+
+ return results;
+}
+
+
+async function renderDetectionsSharp(
+ imagePath: string,
+ detections: Detection[],
+ outputPath: string,
+ classNames?: string[]
+) {
+ const baseImage = sharp(imagePath);
+ const { width, height } = await baseImage.metadata();
+
+ if (!width || !height) throw new Error('Image must have width and height');
+
+ const svg = createSvgOverlay(width, height, detections, classNames);
+ const overlay = Buffer.from(svg);
+
+ await baseImage
+ .composite([{ input: overlay, blend: 'over' }])
+ .toFile(outputPath);
+}
+
+function createSvgOverlay(
+ width: number,
+ height: number,
+ detections: Detection[],
+ classNames?: string[]
+): string {
+ const elements = detections.map(det => {
+ const x = det.x1;
+ const y = det.y1;
+ const w = det.x2 - det.x1;
+ const h = det.y2 - det.y1;
+ const className = classNames?.[det.classIndex] ?? `cls ${det.classIndex}`;
+ const confPct = (det.confidence * 100).toFixed(1);
+
+ return `
+
+
+ ${className} (${confPct}%)
+
+ `;
+ });
+
+ return `
+
+ `;
+}
+
+
+
+describe('inference', () => {
+ it('pytorch', async () => {
+ const session = await InferenceSession.create(modelPath)
+ const imageTensor = await preprocessImage(sampleFrame);
+ console.log(session.inputNames)
+ console.log(session.outputNames)
+ const feeds = {
+ images: imageTensor
+ }
+
+ const res = await session.run(feeds)
+
+ const { output0 } = res
+
+ const detections = postprocessTensor(output0);
+ // console.log(detections)
+ // console.log(detections.length)
+ // console.log(detections.slice(0, 5)); // first 5 predictions
+
+ const finalDetections = nms(detections, undefined, 3);
+ console.log(finalDetections);
+ console.log(`there were ${finalDetections.length} detections`)
+
+ const classNames = Array.from({ length: 19 }, (_, i) => `class${i}`);
+ await renderDetectionsSharp(
+ sampleFrame,
+ finalDetections,
+ join(fixturesDir, 'prediction', 'output-sharp.png'),
+ classNames
+ );
+
+
+ expect(output0.dims).toEqual([1, 24, 8400]);
+ expect(output0.type).toBe('float32');
+ expect(output0.cpuData[0]).toBeGreaterThan(0); // or some expected value
+
+ })
+})
\ No newline at end of file
diff --git a/services/our/src/tests/runInferenceOnFrame.unit.test.ts b/services/our/src/tests/runInferenceOnFrame.unit.test.ts
new file mode 100644
index 0000000..d961261
--- /dev/null
+++ b/services/our/src/tests/runInferenceOnFrame.unit.test.ts
@@ -0,0 +1,107 @@
+// import { describe, it, expect, vi } from 'vitest';
+// import { runInferenceOnFrame } from '../utils/vibeui';
+// import {
+// type InferenceSession,
+// type Tensor,
+// } from 'onnxruntime-node'
+
+// type DetectionOutput = {
+// bbox: [number, number, number, number];
+// confidence: number;
+// classIndex: number;
+// };
+
+// describe('runInferenceOnFrame', () => {
+// it('parses detections and filters by confidence, rounds classIndex, includes out-of-range classes', async () => {
+// // Mock session
+// const mockSession = {
+// inputNames: ['input'],
+// outputNames: ['output'],
+// run: vi.fn().mockResolvedValue({
+// output: {
+// data: new Float32Array([
+// 0.1, 0.2, 0.3, 0.4, 0.5, 10, // valid detection
+// 0.2, 0.3, 0.4, 0.5, 0.2, 5, // confidence too low, filtered out
+// 0.3, 0.4, 0.5, 0.6, 0.9, 54 // class 54 out of range, but included
+// ]),
+// dims: [3, 6]
+// }
+// }),
+// } as unknown as InferenceSession;
+
+// // Mock tensor input, content irrelevant here
+// const mockTensor = {} as Tensor;
+
+// const detections = await runInferenceOnFrame(mockSession, mockTensor);
+
+// expect(detections).toHaveLength(2);
+// expect(detections[0]).toEqual({
+// bbox: [0.1, 0.2, 0.3, 0.4],
+// confidence: 0.5,
+// classIndex: 10,
+// });
+// expect(detections[1]).toEqual({
+// bbox: [0.3, 0.4, 0.5, 0.6],
+// confidence: 0.9,
+// classIndex: 54,
+// });
+// });
+
+// it('throws if output missing or data is wrong type', async () => {
+// const badSession = {
+// inputNames: ['input'],
+// outputNames: ['output'],
+// run: vi.fn().mockResolvedValue({
+// output: {
+// data: [1, 2, 3], // not Float32Array
+// dims: [1, 6]
+// }
+// }),
+// } as unknown as InferenceSession;
+
+// await expect(runInferenceOnFrame(badSession, {} as Tensor)).rejects.toThrow(
+// 'Unexpected model output format'
+// );
+// });
+// });
+
+
+
+import { describe, it, expect } from 'vitest';
+import fs from 'fs/promises';
+import path, { resolve } from 'path';
+import ort from 'onnxruntime-node';
+import sharp from 'sharp';
+import { preprocessImage, runModelInference } from '../utils/vibeui';
+
+
+const __dirname = import.meta.dirname;
+const FIXTURE_DIR = resolve(__dirname, 'fixtures');
+const DIST_DIR = resolve(__dirname, '..', '..', 'dist');
+const VIBEUI_DIR = resolve(DIST_DIR, 'vibeui');
+const VIDEO = resolve(FIXTURE_DIR, 'sample.mp4');
+const MODEL_PATH = resolve(VIBEUI_DIR, 'vibeui.onnx');
+const IMAGE_PATH = resolve(FIXTURE_DIR, 'prediction/frames/000001.jpg');
+
+
+describe.skip('runInferenceOnFrame integration', () => {
+ it('runs inference on real image and returns valid detections', async () => {
+ // Load ONNX model session
+ const session = await ort.InferenceSession.create(MODEL_PATH);
+
+ // Prepare input tensor from JPG image
+ const inputTensor = await preprocessImage(IMAGE_PATH);
+
+ // Run inference
+ const detections = await runModelInference(session, inputTensor);
+
+ // Check output is not empty and class indices are within range
+ expect(detections.length).toBeGreaterThan(0);
+ for (const det of detections) {
+ expect(det.confidence).toBeGreaterThan(0.3);
+ expect(det.classIndex).toBeGreaterThanOrEqual(0);
+ expect(det.classIndex).toBeLessThan(19); // since you have 19 classes
+ expect(det.bbox).toHaveLength(4);
+ }
+ });
+});
diff --git a/services/our/src/tests/vibeui.integration.test.ts b/services/our/src/tests/vibeui.integration.test.ts
new file mode 100644
index 0000000..304e287
--- /dev/null
+++ b/services/our/src/tests/vibeui.integration.test.ts
@@ -0,0 +1,65 @@
+import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, type Mock } from 'vitest';
+import { vibeuiInference, processLabelFiles, getModelClasses } from '../utils/vibeui';
+import { resolve, join } from 'node:path';
+import { readdir, readFile, rm } from 'node:fs/promises';
+import { readJson } from 'fs-extra';
+import { DataYaml } from '../utils/funscripts';
+
+
+const __dirname = import.meta.dirname;
+const FIXTURE_DIR = resolve(__dirname, 'fixtures');
+const DIST_DIR = resolve(__dirname, '..', '..', 'dist');
+const VIBEUI_DIR = resolve(DIST_DIR, 'vibeui');
+const VIDEO = resolve(FIXTURE_DIR, 'sample.mp4');
+const MODEL = resolve(VIBEUI_DIR, 'vibeui.onnx');
+
+
+
+describe('[integration] vibeuiInference', () => {
+ let outputPath: string;
+
+ beforeAll(async () => {
+ outputPath = await vibeuiInference(MODEL, VIDEO);
+ console.log(`outputPath=${outputPath}`)
+ }, 35_000);
+
+ afterAll(async () => {
+ // await rm(outputPath, { recursive: true, force: true });
+ console.log('@todo cleanup')
+ });
+
+ it('outputs detection labels and frames', async () => {
+ const frames = await readdir(join(outputPath, 'frames'));
+ const labels = await readdir(join(outputPath, 'labels'));
+ expect(frames.length).toBeGreaterThan(0);
+ expect(labels.length).toBeGreaterThan(0);
+ });
+
+ it('writes properly formatted label files', async () => {
+ const labelFile = join(outputPath, 'labels', '1.txt');
+ const content = await readFile(labelFile, 'utf8');
+
+ const firstLine = content.split('\n')[0].trim().replace(/\r/g, '');
+ console.log('First line content:', JSON.stringify(firstLine));
+
+ // Expect initial class number + exactly 5 floats/ints after it (6 total numbers)
+ expect(firstLine).toMatch(/^\d+( (-?\d+(\.\d+)?)){5}$/);
+ });
+
+ it('contains only 1-10 lines', async () => {
+ const labelFile = join(outputPath, 'labels', '1.txt');
+ const content = await readFile(labelFile, 'utf8');
+
+ const lines = content
+ .split('\n')
+ .map(line => line.trim())
+ .filter(line => line.length > 0); // ignore empty lines
+
+ expect(lines.length).toBeGreaterThanOrEqual(1);
+ expect(lines.length).toBeLessThanOrEqual(10);
+ });
+
+
+
+
+});
diff --git a/services/our/src/tests/vibeui.spec.ts b/services/our/src/tests/vibeui.spec.ts
deleted file mode 100644
index a0c4670..0000000
--- a/services/our/src/tests/vibeui.spec.ts
+++ /dev/null
@@ -1,125 +0,0 @@
-import { describe, it, expect, beforeAll, afterAll, beforeEach, vi, type Mock } from 'vitest';
-import { vibeuiInference, processLabelFiles } from '../utils/vibeui';
-import { resolve, join } from 'node:path';
-import { readdir, readFile, rm } from 'node:fs/promises';
-import { DataYaml } from '../utils/funscripts';
-
-const __dirname = import.meta.dirname;
-const FIXTURE_DIR = resolve(__dirname, 'fixtures');
-const DIST_DIR = resolve(__dirname, '..', '..', 'dist');
-const VIBEUI_DIR = resolve(DIST_DIR, 'vibeui');
-const VIDEO = resolve(FIXTURE_DIR, 'sample.mp4');
-const MODEL = resolve(VIBEUI_DIR, 'vibeui.onnx');
-const YAML = resolve(VIBEUI_DIR, 'data.yaml');
-
-describe('[unit] processLabelFiles', () => {
- const mockLabelDir = '/mock/labels';
-
- beforeEach(() => {
- vi.clearAllMocks();
- });
-
- it('processes label files and returns merged detection segments', async () => {
- // Mock data.names with string keys for class indexes
- const data: DataYaml = {
- names: {
- '0': 'person',
- '1': 'car',
- },
- path: '',
- train: '',
- val: '',
- };
-
- // Mock file list in directory
- (readdir as Mock).mockResolvedValue([
- '0.txt',
- '1.txt',
- 'invalid.txt',
- 'notalabel.jpg',
- '2.txt',
- ]);
-
- // Mock content for each label file
- (readFile as Mock).mockImplementation(async (filePath: string) => {
- const file = filePath.split('/').pop();
- switch (file) {
- case '0.txt':
- // Two detections, only one with confidence >= 0.7
- return '0 0.1 0.2 0.3 0.4 0.6\n1 0.5 0.5 0.5 0.5 0.8';
- case '1.txt':
- // One detection below threshold
- return '0 0.1 0.2 0.3 0.4 0.5';
- case '2.txt':
- // Multiple lines, highest confidence 0.9 for class 0
- return '0 0.1 0.2 0.3 0.4 0.9\n1 0.2 0.3 0.4 0.5 0.85';
- default:
- return '';
- }
- });
-
- const results = await processLabelFiles(mockLabelDir, data);
-
- // Expected:
- // - frame 0: class 1 (car), confidence 0.8
- // - frame 1: no detection >= 0.7
- // - frame 2: class 0 (person), confidence 0.9 (higher than class 1's 0.85)
- // Merge segments by className:
- // Since frame 0 detection is 'car', frame 2 detection is 'person', segments should not merge.
- expect(results).toEqual([
- { startFrame: 0, endFrame: 0, className: 'car' },
- { startFrame: 2, endFrame: 2, className: 'person' },
- ]);
- });
-
- it('skips files with invalid filenames or frame indices', async () => {
- (readdir as Mock).mockResolvedValue(['abc.txt', '123.txt']);
- (readFile as Mock).mockResolvedValue('0 0.1 0.2 0.3 0.4 0.75');
-
- const data = { names: { '0': 'person' }, val: '', train: '', path: '' };
- const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
-
- const results = await processLabelFiles(mockLabelDir, data);
-
- expect(consoleSpy).toHaveBeenCalledWith('Skipping invalid filename: abc.txt');
- expect(results).toEqual([
- { startFrame: 123, endFrame: 123, className: 'person' },
- ]);
-
- consoleSpy.mockRestore();
- });
-});
-
-describe('[integration] vibeuiInference', () => {
- let outputPath: string;
-
-
- beforeAll(async () => {
- outputPath = await vibeuiInference(MODEL, VIDEO);
- }, 35_000);
-
- afterAll(async () => {
- await rm(outputPath, { recursive: true, force: true });
- });
-
- it('outputs detection labels and frames', async () => {
- const frames = await readdir(join(outputPath, 'frames'));
- const labels = await readdir(join(outputPath, 'labels'));
- expect(frames.length).toBeGreaterThan(0);
- expect(labels.length).toBeGreaterThan(0);
- });
-
- it('writes properly formatted label files', async () => {
- const labelFile = join(outputPath, 'labels', '1.txt');
- const content = await readFile(labelFile, 'utf8');
-
- const firstLine = content.split('\n')[0].trim().replace(/\r/g, '');
- console.log('First line content:', JSON.stringify(firstLine));
-
- // Expect initial class number + exactly 5 floats/ints after it (6 total numbers)
- expect(firstLine).toMatch(/^\d+( (-?\d+(\.\d+)?)){5}$/);
- });
-
-
-
-});
diff --git a/services/our/src/utils/funscripts.ts b/services/our/src/utils/funscripts.ts
index b8532e4..1abbd49 100644
--- a/services/our/src/utils/funscripts.ts
+++ b/services/our/src/utils/funscripts.ts
@@ -32,62 +32,30 @@ export interface ClassPositionMap {
}
-export async function loadClassPositionMap(data: DataYaml): Promise {
- try {
- if (
- !data ||
- typeof data !== 'object' ||
- !('names' in data) ||
- typeof data.names !== 'object' ||
- data.names === null ||
- Object.keys(data.names).length === 0
- ) {
- throw new Error('Invalid data.yaml: "names" field is missing, not an object, or empty');
- }
+export const classPositionMap: ClassPositionMap = {
+ RespondingTo: 5,
+ ControlledByTipper: 50,
+ ControlledByTipperHigh: 80,
+ ControlledByTipperLow: 20,
+ ControlledByTipperMedium: 50,
+ ControlledByTipperUltrahigh: 100,
+ Ring1: 30,
+ Ring2: 40,
+ Ring3: 50,
+ Ring4: 60,
+ Earthquake: 'pattern',
+ Fireworks: 'pattern',
+ Pulse: 'pattern',
+ Wave: 'pattern',
+ Pause: 0,
+ RandomTime: 70,
+ HighLevel: 80,
+ LowLevel: 20,
+ MediumLevel: 50,
+ UltraHighLevel: 95
+};
- const positionMap: ClassPositionMap = {
- RespondingTo: 5,
- ControlledByTipper: 50,
- ControlledByTipperHigh: 80,
- ControlledByTipperLow: 20,
- ControlledByTipperMedium: 50,
- ControlledByTipperUltrahigh: 100,
- Ring1: 30,
- Ring2: 40,
- Ring3: 50,
- Ring4: 60,
- Earthquake: 'pattern',
- Fireworks: 'pattern',
- Pulse: 'pattern',
- Wave: 'pattern',
- Pause: 0,
- RandomTime: 70,
- HighLevel: 80,
- LowLevel: 20,
- MediumLevel: 50,
- UltraHighLevel: 95
- };
-
- const names = Object.values(data.names);
- for (const name of names) {
- if (typeof name !== 'string' || name.trim() === '') {
- console.log(`Skipping invalid class name: ${name}`);
- continue;
- }
- if (!(name in positionMap)) {
- console.log(`No position mapping for class "${name}", defaulting to 0`);
- positionMap[name] = 0;
- }
- }
-
- console.log(`Loaded class position map: ${JSON.stringify(positionMap)}`);
- return positionMap;
- } catch (error) {
- console.error(`Error loading data.yaml: ${error instanceof Error ? error.message : 'Unknown error'}`);
- throw error;
- }
-}
export function generatePatternPositions(startMs: number, durationMs: number, className: string, fps: number): FunscriptAction[] {
const actions: FunscriptAction[] = [];
@@ -121,9 +89,21 @@ export function generatePatternPositions(startMs: number, durationMs: number, cl
return actions;
}
-
-
-export function generateActions(totalDurationMs: number, fps: number, detectionSegments: Detection[], classPositionMap: ClassPositionMap): FunscriptAction[] {
+/**
+ * Generates Funscript actions based on detection segments and a class-to-position mapping.
+ *
+ * - Creates static position actions at regular intervals (default 100ms).
+ * - Maps each timestamp to a position if a detection segment is active at that frame.
+ * - If a class is mapped to `"pattern"` in `classPositionMap`, generates additional patterned actions for that segment.
+ * - Merges and deduplicates actions by timestamp.
+ *
+ * @param totalDurationMs - Total video duration in milliseconds.
+ * @param fps - Frames per second of the video.
+ * @param detectionSegments - Array of detection segments with frame ranges and class names.
+ * @param classPositionMap - Maps class names to either a static position (0–100) or the string `"pattern"` to trigger pattern generation.
+ * @returns An array of unique, time-sorted `FunscriptAction` objects.
+ */
+export function generateActions1(totalDurationMs: number, fps: number, detectionSegments: Detection[], classPositionMap: ClassPositionMap): FunscriptAction[] {
const intervalMs = 100;
const actions: FunscriptAction[] = [];
@@ -169,6 +149,92 @@ export function generateActions(totalDurationMs: number, fps: number, detectionS
return uniqueActions;
}
+export function generateActions2(
+ totalDurationMs: number,
+ fps: number,
+ detectionSegments: Detection[],
+ classPositionMap: ClassPositionMap
+): FunscriptAction[] {
+ const intervalMs = 100;
+ const actions: FunscriptAction[] = [];
+
+
+
+ console.debug('[generateActions] Total duration (ms):', totalDurationMs);
+ console.debug('[generateActions] FPS:', fps);
+ console.debug('[generateActions] Detection segments:', detectionSegments);
+ console.debug('[generateActions] Class position map:', classPositionMap);
+
+ // Generate static position actions
+ for (let timeMs = 0; timeMs <= totalDurationMs; timeMs += intervalMs) {
+ const frameIndex = Math.floor((timeMs / 1000) * fps);
+ let position = 0;
+ let matchedSegment: Detection | undefined;
+
+ for (const segment of detectionSegments) {
+ if (frameIndex >= segment.startFrame && frameIndex <= segment.endFrame) {
+ const className = segment.className;
+ matchedSegment = segment;
+
+ if (typeof classPositionMap[className] === 'number') {
+ position = classPositionMap[className];
+ break;
+ } else {
+ console.warn(`[generateActions] Static class not mapped to number: ${className}`);
+ }
+ }
+ }
+
+ if (!matchedSegment) {
+ console.debug(`[generateActions] No matching segment for time ${timeMs} (frame ${frameIndex})`);
+ }
+
+ actions.push({ at: timeMs, pos: position });
+ }
+
+ // Overlay pattern-based actions
+ for (const segment of detectionSegments) {
+ const className = segment.className;
+
+ if (classPositionMap[className] === 'pattern') {
+ const startMs = Math.floor((segment.startFrame / fps) * 1000);
+ const durationMs = Math.floor(((segment.endFrame - segment.startFrame + 1) / fps) * 1000);
+
+ console.debug(`[generateActions] Generating pattern for class "${className}" from ${startMs}ms for ${durationMs}ms`);
+ const patternActions = generatePatternPositions(startMs, durationMs, className, fps);
+
+ console.debug(`[generateActions] Generated ${patternActions.length} pattern actions for class "${className}"`);
+ actions.push(...patternActions);
+ }
+ }
+
+ // Sort actions by time and remove duplicates
+ actions.sort((a, b) => a.at - b.at);
+ const uniqueActions: FunscriptAction[] = [];
+ let lastTime = -1;
+ for (const action of actions) {
+ if (action.at !== lastTime) {
+ uniqueActions.push(action);
+ lastTime = action.at;
+ }
+ }
+
+ console.debug(`[generateActions] Final action count: ${uniqueActions.length}`);
+
+ return uniqueActions;
+}
+
+/**
+ * Writes a Funscript file to disk in JSON format.
+ *
+ * - Wraps the provided actions in a Funscript object with version `1.0`.
+ * - Saves the JSON to the specified output path.
+ * - Logs the file path and action count to the console.
+ *
+ * @param outputPath - Destination file path for the .funscript output.
+ * @param actions - Array of `FunscriptAction` entries to include.
+ * @throws If writing to the file system fails.
+ */
export async function writeFunscript(outputPath: string, actions: FunscriptAction[]) {
const funscript: Funscript = { version: '1.0', actions };
@@ -179,26 +245,36 @@ export async function writeFunscript(outputPath: string, actions: FunscriptActio
+/**
+ * Builds a Funscript file from YOLO prediction output and video metadata.
+ *
+ * - Loads the model's class definitions and video FPS/frame count.
+ * - Parses label files and maps detections to time-based actions.
+ * - Generates a Funscript JSON object and writes it to disk.
+ *
+ * @param predictionOutput - Path to the YOLO prediction output directory.
+ * @param videoPath - Path to the source video file.
+ * @returns Path to the generated Funscript file (.funscript).
+ * @throws If there is an error processing labels or writing the Funscript.
+ */
export async function buildFunscript(
- dataYamlPath: string,
predictionOutput: string,
videoPath: string
): Promise {
const labelDir = join(predictionOutput, 'labels');
const outputPath = join(process.env.CACHE_ROOT ?? '/tmp', `${nanoid()}.funscript`);
- console.log(`Starting Funscript generation. outputPath=${outputPath}`);
try {
- const data = await loadDataYaml(dataYamlPath)
- const classPositionMap = await loadClassPositionMap(data);
+ const data = await loadDataYaml(join(env.VIBEUI_DIR, 'data.yaml'))
const { fps, totalFrames } = await loadVideoMetadata(videoPath);
const detectionSegments = await processLabelFiles(labelDir, data);
const totalDurationMs = Math.floor((totalFrames / fps) * 1000);
- const actions = generateActions(totalDurationMs, fps, detectionSegments, classPositionMap);
- await writeFunscript(outputPath, actions);
- return outputPath;
+ const actions = generateActions1(totalDurationMs, fps, detectionSegments, classPositionMap);
+ await writeFunscript(outputPath, actions);
+
+ return outputPath;
} catch (error) {
console.error(`Error generating Funscript: ${error instanceof Error ? error.message : 'Unknown error'}`);
throw error;
diff --git a/services/our/src/utils/python.ts b/services/our/src/utils/python.ts
new file mode 100644
index 0000000..ba6144a
--- /dev/null
+++ b/services/our/src/utils/python.ts
@@ -0,0 +1,70 @@
+import { getNanoSpawn } from "./nanoSpawn";
+import { join } from "node:path";
+import { env } from "../config/env";
+import which from "which";
+import { existsSync } from "fs-extra";
+
+export async function preparePython() {
+ const spawn = await getNanoSpawn();
+ const venvPath = env.VENV;
+
+ // Determine Python executable
+ let pythonCmd;
+ try {
+ pythonCmd = which.sync("python3");
+ } catch {
+ console.error("Python is not installed or not in PATH.");
+ throw new Error("Python not found in PATH.");
+ }
+
+ // If venv doesn't exist, create it
+ if (!existsSync(venvPath)) {
+ console.error("Python venv not found. Creating one...");
+
+ try {
+ await spawn(pythonCmd, ["-m", "venv", venvPath], {
+ cwd: env.APP_DIR,
+ });
+
+ console.log("Python venv successfully created.");
+ } catch (err) {
+ console.error("Failed to create Python venv:", err);
+ throw new Error(
+ "Python venv creation failed. Check if python3 and python3-venv are installed."
+ );
+ }
+ } else {
+ console.log("Using existing Python venv.");
+ }
+
+ // Activate pip in the venv
+ const pipCmd = join(venvPath, "bin", "pip");
+
+ // Check if YOLO exists (example: checking if 'yolo' package is installed)
+ // This check can be customized to your specific condition (e.g., check a file or run `pip show yolo`)
+ let yoloExists = false;
+ try {
+ // Run `pip show yolov5` or your yolo package name to check if installed
+ await spawn(pipCmd, ["show", "yolov5"], { cwd: env.APP_DIR });
+ yoloExists = true;
+ } catch {
+ yoloExists = false;
+ }
+
+ if (!yoloExists) {
+ console.log("YOLO not found in venv. Installing requirements.txt...");
+
+ try {
+ // Install requirements.txt using pip inside venv
+ await spawn(pipCmd, ["install", "-r", "requirements.txt"], {
+ cwd: env.APP_DIR,
+ });
+ console.log("✅ requirements.txt installed successfully.");
+ } catch (err) {
+ console.error("Failed to install requirements.txt:", err);
+ throw new Error("requirements.txt installation failed.");
+ }
+ } else {
+ console.log("YOLO detected in venv, skipping requirements installation.");
+ }
+}
diff --git a/services/our/src/utils/vibeui.ts b/services/our/src/utils/vibeui.ts
index 11f223d..a75b2d2 100644
--- a/services/our/src/utils/vibeui.ts
+++ b/services/our/src/utils/vibeui.ts
@@ -8,6 +8,9 @@ import { getNanoSpawn } from "./nanoSpawn";
import { existsSync, mkdirSync, rmSync } from "node:fs";
import which from "which";
import { env } from '../config/env';
+import fs from 'fs/promises';
+import { readJson } from 'fs-extra';
+import { preparePython } from './python';
interface Detection {
startFrame: number;
@@ -72,54 +75,9 @@ export async function preprocessImage(imagePath: string): Promise {
return new ort.Tensor('float32', floatArray, [1, 3, inputHeight, inputWidth]);
}
-export async function runInferenceOnFrame(session: ort.InferenceSession, tensor: ort.Tensor): Promise {
- // The input name depends on your ONNX model, often 'images' or similar.
- const feeds: Record = {};
- const inputName = session.inputNames[0];
- feeds[inputName] = tensor;
- const results = await session.run(feeds);
- // You need to parse outputs according to your ONNX model
- // Example:
- // Let's say outputNames: ['boxes', 'scores', 'labels']
- // This depends on your model. For YOLO models, outputs vary by implementation.
- // For demonstration, let's assume single output with shape [num_detections, 6]
- // where each row = [x, y, w, h, confidence, classIndex]
-
- const outputName = session.outputNames[0];
- const output = results[outputName];
-
- if (!output || !(output.data instanceof Float32Array)) {
- throw new Error('Unexpected model output format');
- }
-
- const data = output.data as Float32Array;
- const numDetections = output.dims[0];
- const detections: DetectionOutput[] = [];
-
- for (let i = 0; i < numDetections; i++) {
- const offset = i * 6;
- const [x, y, w, h, confidence, classIdx] = [
- data[offset],
- data[offset + 1],
- data[offset + 2],
- data[offset + 3],
- data[offset + 4],
- data[offset + 5]
- ];
-
- if (confidence > 0.3) { // threshold
- detections.push({
- bbox: [x, y, w, h],
- confidence,
- classIndex: Math.round(classIdx),
- });
- }
- }
- return detections;
-}
export async function writeLabels(outputPath: string, detectionsByFrame: Map, classNames: Record) {
// Write labels in YOLO txt format per frame:
@@ -147,73 +105,186 @@ export async function writeLabels(outputPath: string, detectionsByFrame: Map {
const yamlContent = await readFile(yamlPath, 'utf8');
return yaml.load(yamlContent) as DataYaml;
}
-export async function vibeuiInference(
- modelPath: string,
- videoFilePath: string
-): Promise {
- if (!modelPath) throw new Error('missing modelPath, arg0');
- if (!videoFilePath) throw new Error('missing videoFilePath, arg1');
+/**
+ * Runs YOLO inference on the given video file using the configured model.
+ *
+ * - Prepares the Python environment and loads the YOLO model.
+ * - Generates a unique output directory for the results.
+ * - Executes YOLO with flags to save text labels and confidence scores (but not images).
+ *
+ * @param videoFilePath - Path to the input video file to analyze.
+ * @returns Path to the output directory containing the prediction results.
+ */
+export async function inference(videoFilePath: string): Promise {
+ await preparePython()
+ const spawn = await getNanoSpawn()
- // Load ONNX model
- console.log(`Loading ONNX model from ${modelPath}`);
+ const modelPath = join(env.VIBEUI_DIR, 'vibeui.pt')
- const session = await ort.InferenceSession.create(modelPath);
+ // Generate a unique name based on video name + UUID
+ const uniqueName = nanoid()
+ const customProjectDir = 'runs' // or any custom folder
+ const outputPath = join(env.APP_DIR, customProjectDir, uniqueName)
- // Prepare output dir
- // const videoExt = extname(videoFilePath);
- // const videoName = basename(videoFilePath, videoExt);
- // const uniqueName = `${videoName}-${nanoid()}`;
- const outputPath = join(env.CACHE_ROOT, nanoid());
+ await spawn('./venv/bin/yolo', [
+ 'predict',
+ `model=${modelPath}`,
+ `source=${videoFilePath}`,
+ 'save=False',
+ 'save_txt=True',
+ 'save_conf=True',
+ `project=${customProjectDir}`,
+ `name=${uniqueName}`,
+ ], {
+ cwd: env.APP_DIR,
+ stdio: 'inherit',
+ })
-
- // Extract frames
- const framesDir = join(outputPath, 'frames');
- mkdirSync(framesDir, { recursive: true })
- console.log(`Extracting video frames from ${videoFilePath} to ${framesDir}...`);
- await extractFrames(videoFilePath, framesDir);
-
- // Load class names from data.yaml
- const dataYaml = await loadDataYaml(join(env.VIBEUI_DIR, 'data.yaml'));
- const classNames = dataYaml.names;
-
- // Read all frames and run inference
- const frameFiles = (await readdir(framesDir))
- .filter(f => f.endsWith('.jpg'))
- .sort();
-
- console.log(`frameFiles=${JSON.stringify(frameFiles)}`)
- const detectionsByFrame = new Map();
-
- if (frameFiles.length === 0) throw new Error(`No frames extracted! This is likely a bug.`);
-
- console.log(`Running inference on ${frameFiles.length} frames...`);
-
- for (const file of frameFiles) {
- const frameIndex = parseInt(file.match(/(\d+)\.jpg$/)?.[1] ?? '0', 10);
- const imagePath = join(framesDir, file);
- const inputTensor = await preprocessImage(imagePath);
- const detections = await runInferenceOnFrame(session, inputTensor);
- detectionsByFrame.set(frameIndex, detections);
- }
-
- // Write YOLO format label txt files
- await writeLabels(outputPath, detectionsByFrame, classNames);
-
- // Optionally cleanup frames dir to save space:
- // await rmSync(framesDir, { recursive: true, force: true });
-
- return outputPath;
+ return outputPath // contains labels/ folder and predictions
}
+// export async function runModelInference(session: ort.InferenceSession, inputTensor: ort.Tensor): Promise {
+// const feeds = { input: inputTensor };
+// const results = await session.run(feeds);
+// // Adjust 'output' to your model's output key
+// const outputTensor = results.output;
+// return postprocess(outputTensor);
+// }
+
+
+// export async function yoloInference(modelPath: string, videoFilePath: string) {
+
+// await preparePython()
+// const spawn = await getNanoSpawn()
+
+// const uniqueName = nanoid()
+// const customProjectDir = 'vibeui' // or any custom folder
+// const outputPath = join(env.CACHE_ROOT, customProjectDir, uniqueName)
+
+// const predictionOutput = await spawn('./venv/bin/yolo', [
+// 'predict',
+// `model=${modelPath}`,
+// `source=${videoFilePath}`,
+// 'save=False',
+// 'save_txt=True',
+// 'save_conf=True',
+// `project=${customProjectDir}`,
+// `name=${uniqueName}`,
+// ], {
+// cwd: env.VIBEUI_DIR,
+// stdio: 'inherit',
+// })
+
+// return outputPath // contains labels/ folder and predictions
+// console.log(`prediction output ${predictionOutput}`);
+
+
+// const funscriptFilePath = await buildFunscript(helpers, predictionOutput, videoFilePath)
+
+
+// const s3Key = `funscripts/${vodId}.funscript`;
+// const s3Url = await uploadFile(s3Client, env.S3_BUCKET, s3Key, funscriptFilePath, "application/json");
+
+// console.log(`Uploaded funscript to S3: ${s3Url}`);
+
+
+// console.log(`Funscript saved to database for vod ${vodId}`);
+
+// }
+
+// export async function vibeuiInference(
+// modelPath: string,
+// videoFilePath: string
+// ): Promise {
+
+// if (!modelPath) throw new Error('missing modelPath, arg0');
+// if (!videoFilePath) throw new Error('missing videoFilePath, arg1');
+
+// // Load ONNX model
+// console.log(`Loading ONNX model from ${modelPath}`);
+
+// const session = await ort.InferenceSession.create(modelPath);
+
+// console.log(`inputNames=${session.inputNames} outputNames=${session.outputNames}`)
+
+// // Prepare output dir
+// // const videoExt = extname(videoFilePath);
+// // const videoName = basename(videoFilePath, videoExt);
+// // const uniqueName = `${videoName}-${nanoid()}`;
+// const outputPath = join(env.CACHE_ROOT, nanoid());
+
+
+// // Extract frames
+// const framesDir = join(outputPath, 'frames');
+// mkdirSync(framesDir, { recursive: true })
+// console.log(`Extracting video frames from ${videoFilePath} to ${framesDir}...`);
+// await extractFrames(videoFilePath, framesDir);
+
+// // Load class names from data.yaml
+// const dataYaml = await loadDataYaml(join(env.VIBEUI_DIR, 'data.yaml'));
+// const classNames = dataYaml.names;
+
+// // Read all frames and run inference
+// const frameFiles = (await readdir(framesDir))
+// .filter(f => f.endsWith('.jpg'))
+// .sort();
+
+// // console.log(`frameFiles=${JSON.stringify(frameFiles)}`)
+// const detectionsByFrame = new Map();
+
+// if (frameFiles.length === 0) throw new Error(`No frames extracted! This is likely a bug.`);
+
+// console.log(`Running inference on ${frameFiles.length} frames...`);
+
+
+// for (const file of frameFiles) {
+// const frameIndex = parseInt(file.match(/(\d+)\.jpg$/)?.[1] ?? '0', 10);
+// const imagePath = join(framesDir, file);
+// const inputTensor = await preprocessImage(imagePath);
+// const detections = await runModelInference(session, inputTensor)
+// console.log(`[frame ${frameIndex}] detections.length = ${detections.length}`);
+// detectionsByFrame.set(frameIndex, detections);
+
+// }
+
+// // Write YOLO format label txt files
+// await writeLabels(outputPath, detectionsByFrame, classNames);
+
+// // Optionally cleanup frames dir to save space:
+// // await rmSync(framesDir, { recursive: true, force: true });
+
+// return outputPath;
+// }
+/**
+ * Extracts video metadata (FPS and frame count) using ffprobe.
+ *
+ * - Spawns an ffprobe subprocess to analyze the video stream.
+ * - Retrieves the frame rate (`r_frame_rate`) and total frame count (`nb_read_frames`).
+ * - Parses and returns both values as numbers.
+ *
+ * @param videoPath - Path to the video file to analyze.
+ * @returns An object containing the video's frames per second (`fps`) and total number of frames (`frames`).
+ * @throws If ffprobe fails or returns malformed output.
+ */
export async function ffprobe(videoPath: string): Promise<{ fps: number; frames: number }> {
const spawn = await getNanoSpawn()
const { stdout } = await spawn('ffprobe', [
@@ -233,6 +304,41 @@ export async function ffprobe(videoPath: string): Promise<{ fps: number; frames:
return { fps, frames }
}
+
+
+
+
+
+// export async function getModelClasses(modelPath: string): Promise> {
+// const jsonPath = modelPath.replace(/\.onnx$/, '.json');
+
+// try {
+// const data = await readJson(jsonPath);
+
+// if (data.labels && typeof data.labels === 'object') {
+// // Return the labels object as-is, retaining numeric keys as strings
+// return data.labels;
+// } else {
+
+// throw new Error('Invalid labels format in JSON');
+// }
+// } catch (err) {
+// console.error(`Failed to read labels from ${jsonPath}:`, err);
+// throw err;
+// }
+// }
+
+
+/**
+ * Loads basic metadata from a video file using ffprobe.
+ *
+ * - Retrieves the video's frame rate (fps) and total frame count.
+ * - Logs the extracted metadata to the console.
+ *
+ * @param videoPath - Path to the video file to analyze.
+ * @returns An object containing `fps` and `totalFrames`.
+ * @throws If metadata extraction fails.
+ */
export async function loadVideoMetadata(videoPath: string) {
const { fps, frames: totalFrames } = await ffprobe(videoPath);
console.log(`Video metadata: fps=${fps}, frames=${totalFrames}`);
@@ -241,24 +347,29 @@ export async function loadVideoMetadata(videoPath: string) {
export async function processLabelFiles(labelDir: string, data: DataYaml): Promise {
const labelFiles = (await readdir(labelDir)).filter(file => file.endsWith('.txt'));
+ console.log(`[processLabelFiles] Found label files: ${labelFiles.length}`);
+ if (labelFiles.length === 0) console.warn(`⚠️⚠️⚠️ no label files found! this should normally NOT happen unless the video contained no lovense overlay. ⚠️⚠️⚠️`);
+
const detections: Map = new Map();
const names = data.names;
for (const file of labelFiles) {
const match = file.match(/(\d+)\.txt$/);
if (!match) {
- console.log(`Skipping invalid filename: ${file}`);
+ console.log(`[processLabelFiles] Skipping invalid filename: ${file}`);
continue;
}
+
const frameIndex = parseInt(match[1], 10);
if (isNaN(frameIndex)) {
- console.log(`Skipping invalid frame index from filename: ${file}`);
+ console.log(`[processLabelFiles] Skipping invalid frame index: ${file}`);
continue;
}
const content = await readFile(join(labelDir, file), 'utf8');
const lines = content.trim().split('\n');
const frameDetections: Detection[] = [];
+
let maxConfidence = 0;
let selectedClassIndex = -1;
@@ -268,6 +379,7 @@ export async function processLabelFiles(labelDir: string, data: DataYaml): Promi
const classIndex = parseInt(parts[0], 10);
const confidence = parseFloat(parts[5]);
+
if (isNaN(classIndex) || isNaN(confidence)) continue;
if (confidence >= 0.7 && confidence > maxConfidence) {
@@ -276,10 +388,13 @@ export async function processLabelFiles(labelDir: string, data: DataYaml): Promi
}
}
- if (maxConfidence > 0) {
- const className = (data.names as Record)[selectedClassIndex.toString()];
+ if (maxConfidence > 0 && selectedClassIndex !== -1) {
+ const className = names[selectedClassIndex.toString()];
if (className) {
+ console.log(`[processLabelFiles] Frame ${frameIndex}: detected class "${className}" with confidence ${maxConfidence}`);
frameDetections.push({ startFrame: frameIndex, endFrame: frameIndex, className });
+ } else {
+ console.log(`[processLabelFiles] Frame ${frameIndex}: class index ${selectedClassIndex} has no name`);
}
}
@@ -292,7 +407,7 @@ export async function processLabelFiles(labelDir: string, data: DataYaml): Promi
const detectionSegments: Detection[] = [];
let currentDetection: Detection | null = null;
- for (const [frameIndex, frameDetections] of detections.entries()) {
+ for (const [frameIndex, frameDetections] of [...detections.entries()].sort((a, b) => a[0] - b[0])) {
for (const detection of frameDetections) {
if (!currentDetection || currentDetection.className !== detection.className) {
if (currentDetection) detectionSegments.push(currentDetection);
@@ -302,7 +417,13 @@ export async function processLabelFiles(labelDir: string, data: DataYaml): Promi
}
}
}
+
if (currentDetection) detectionSegments.push(currentDetection);
+ console.log(`[processLabelFiles] Total detection segments: ${detectionSegments.length}`);
+ for (const segment of detectionSegments) {
+ console.log(` - Class "${segment.className}": frames ${segment.startFrame}–${segment.endFrame}`);
+ }
+
return detectionSegments;
}