{"_id":"@aphrody/frames","_rev":"2-d2de70b8d0b827fa1587e7d7360faea4","name":"@aphrody/frames","dist-tags":{"latest":"0.2.1"},"versions":{"0.2.0":{"name":"@aphrody/frames","version":"0.2.0","keywords":["anime","frame","color-layout","mpeg-7","reverse-image-search","trace.moe"],"license":"Apache-2.0","_id":"@aphrody/frames@0.2.0","maintainers":[{"name":"yohan971","email":"yohanpierre@live.fr"}],"dist":{"shasum":"96977c334672c7bbc9ba62c1de1718f3e536b325","tarball":"https://registry.npmjs.org/@aphrody/frames/-/frames-0.2.0.tgz","fileCount":8,"integrity":"sha512-tmpectcukahFGtx3yrKCnUioWzw25zsvjuqPhQfCkninyZ5yy4RCgyyhdFLZZW8QR4Wmg7KJ87uTWaeINuM06Q==","signatures":[{"sig":"MEQCIAMnA1b21XzKKhye0IxIe0E24pf6TDZ53WMZRBdg7FzgAiBjEX1XAJXSI3M4+aVLv+bbo7dSbio1zzRSNSclztouvw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":59050},"main":"./src/index.ts","type":"module","types":"./src/index.ts","shasum":"96977c334672c7bbc9ba62c1de1718f3e536b325","engines":{"bun":">=1.3.14"},"exports":{".":"./src/index.ts","./store":"./src/store.ts","./search":"./src/search.ts","./extract":"./src/extract.ts","./trace-moe":"./src/trace-moe.ts","./descriptor":"./src/descriptor.ts"},"scripts":{"test":"bun test","typecheck":"tsc --noEmit"},"_npmUser":{"name":"yohan971","email":"yohanpierre@live.fr"},"_integrity":"sha512-tmpectcukahFGtx3yrKCnUioWzw25zsvjuqPhQfCkninyZ5yy4RCgyyhdFLZZW8QR4Wmg7KJ87uTWaeINuM06Q==","_npmVersion":"10.8.3","description":"Index et recherche image par image d'épisodes d'anime — descripteur MPEG-7 ColorLayout local, compatible trace.moe","directories":{},"_nodeVersion":"24.3.0","dependencies":{"trace.moe-id":"^2.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/frames_0.2.0_1788431540098_0.565846784045078","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@aphrody/frames","version":"0.2.1","description":"Index et recherche image par image d'épisodes d'anime — descripteur MPEG-7 ColorLayout local, compatible trace.moe","publishConfig":{"access":"public"},"type":"module","main":"./src/index.ts","types":"./src/index.ts","exports":{".":"./src/index.ts","./descriptor":"./src/descriptor.ts","./extract":"./src/extract.ts","./store":"./src/store.ts","./search":"./src/search.ts","./trace-moe":"./src/trace-moe.ts"},"scripts":{"typecheck":"tsc --noEmit","test":"bun test"},"keywords":["anime","frame","color-layout","mpeg-7","reverse-image-search","trace.moe"],"dependencies":{"trace.moe-id":"^2.0.0"},"engines":{"bun":">=1.3.14"},"license":"Apache-2.0","_id":"@aphrody/frames@0.2.1","_integrity":"sha512-BemtnPVsR9RtzP5KBi4LOJTtKQuS85OMdnAmGC7MYp+j9sYrdVLMpzOGBWejTXRI3IZ/Yv2SeC0f8M4Y2/yS0g==","_nodeVersion":"24.3.0","_npmVersion":"10.8.3","shasum":"6a590249ae0a5f5d02e19f2505fdf977ce764969","dist":{"integrity":"sha512-BemtnPVsR9RtzP5KBi4LOJTtKQuS85OMdnAmGC7MYp+j9sYrdVLMpzOGBWejTXRI3IZ/Yv2SeC0f8M4Y2/yS0g==","shasum":"6a590249ae0a5f5d02e19f2505fdf977ce764969","tarball":"https://registry.npmjs.org/@aphrody/frames/-/frames-0.2.1.tgz","fileCount":8,"unpackedSize":59050,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCICvMapLu571PJHTBM6SS7n7WxreGorfhbD6dsysl0729AiEAlAzLsc1RE/CO7PK6BbX+dC3leYChkyK0Z9hreOL0Cks="}]},"_npmUser":{"name":"yohan971","email":"yohanpierre@live.fr"},"directories":{},"maintainers":[{"name":"yohan971","email":"yohanpierre@live.fr"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/frames_0.2.1_1788681496997_0.7498871783125698"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-03T10:32:19.910Z","modified":"2026-09-06T07:58:17.310Z","0.2.0":"2026-09-03T10:32:20.239Z","0.2.1":"2026-09-06T07:58:17.146Z"},"license":"Apache-2.0","keywords":["anime","frame","color-layout","mpeg-7","reverse-image-search","trace.moe"],"description":"Index et recherche image par image d'épisodes d'anime — descripteur MPEG-7 ColorLayout local, compatible trace.moe","maintainers":[{"name":"yohan971","email":"yohanpierre@live.fr"}],"readme":"# @aphrody/frames\n\nRetrouver **d'où vient une image** : quel épisode, quelle seconde. Index local\nimage par image, avec trace.moe en recours.\n\n```bash\nbxc frames index ~/videos/inazuma-s1e01.mp4 --season 1 --episode 1\nbxc frames search capture.jpg\n#  96.1%  Inazuma Eleven S1E1  2:41.000 → 2:44.000\n```\n\n## Pourquoi un index local\n\nTrois façons de répondre à la question existaient déjà. Mesurées sur Inazuma\nEleven le 3 septembre 2026 :\n\n| | couverture Inazuma Eleven | quota | ce qui sort de la machine |\n| --- | --- | --- | --- |\n| [trace.moe](https://trace.moe) | partielle : 125 fichiers pour la série d'origine (AniList 5231), 47 pour GO, 51 Chrono Stone, 43 Galaxy, 25 Ares, 48 Orion — rien en VF | 100 recherches / 24 h, **1 requête à la fois** | l'image entière (ou 33 entiers, voir plus bas) |\n| [fancaps.net](https://fancaps.net/anime/) | **aucune** — la lettre « I » du catalogue liste 95 séries, pas une seule Inazuma | pas d'API publique, seulement des scrapers tiers | l'URL consultée |\n| index local (ce paquet) | ce qu'on lui donne — les 412 épisodes VF du catalogue IETV, par exemple | aucun | **rien** |\n\nL'index public est excellent là où il est complet, et muet ailleurs : les VF,\nles diffusions récentes, les films, les extraits — tout ce qu'il n'a jamais\nindexé. Un index local coûte 32 Mo pour un catalogue entier et répond en une\ndemi-seconde ; il n'y a pas de raison de s'en priver.\n\n### Ce que valent les deux, mesuré\n\n- **Une vraie trame d'épisode → trace.moe la reconnaît parfaitement** :\n  similarité **0,9919** en envoyant l'image, **0,9920** en n'envoyant que le\n  descripteur, horodatage juste à 0,3 s près, 125 ms de latence.\n- **Une vignette YouTube brandée ne marche pas** : sur 10 requêtes construites\n  à partir des vignettes officielles de la chaîne IETV (bandeau de saison,\n  drapeau, logo, numéro d'épisode), 4 seulement retrouvaient la bonne série et\n  **une seule** dépassait le seuil de 0,90. Recadrer pour retirer les\n  incrustations n'arrange rien : le descripteur est une grille 8×8 sur l'image\n  entière, donc rogner déplace tout. Il faut une capture plein cadre.\n- **L'index local**, sur les mêmes images : 0,966 pour la trame correspondante,\n  et il trouve ce que l'index public n'a pas.\n\n### Débits mesurés (VPS 2 cœurs, ffmpeg 8.0.1)\n\n| | valeur |\n| --- | --- |\n| indexation d'un épisode de 24 min à 1 img/s | 10,7 s (**135× le temps réel**) |\n| idem à 4 img/s | 9,4 s (154×, le décodage domine, pas l'extraction) |\n| poids de l'index | 65 à 82 o par trame — **92 Ko** l'épisode à 1 img/s |\n| catalogue complet simulé (412 épisodes, 593 280 trames) | **32,5 Mo** |\n| recherche exhaustive dans ces 593 280 trames | **~500 ms** |\n\n## Prérequis\n\n`ffmpeg` et `ffprobe` dans le `PATH` (ou `FfmpegDeps.ffmpeg` / `.ffprobe`).\nC'est la seule dépendance externe : le descripteur, la base et la recherche\nsont en TypeScript pur.\n\n## CLI\n\n```bash\nbxc frames index <video...>     # indexe (ffmpeg décode en flux, rien sur disque)\nbxc frames search <image>       # local d'abord, trace.moe si le local ne sait pas\nbxc frames vector <image>       # les 33 coefficients, en base64 — partageable sans l'image\nbxc frames list                 # médias indexés\nbxc frames stats                # taille et couverture de l'index\nbxc frames quota                # quota trace.moe de cette machine\n```\n\nOptions utiles : `--fps` (trames indexées par seconde, défaut 1), `--db`\n(emplacement de l'index, défaut `~/.cache/bxc/frames.db`, ou `BXC_FRAMES_DB`),\n`--local` / `--remote`, `--at 12:34` pour prendre la trame d'une vidéo comme\nrequête, `--json`.\n\n```bash\n# indexer une saison entière\nfor f in ~/videos/inazuma-s1e*.mp4; do bxc frames index \"$f\" --season 1 --fps 2; done\n\n# d'où vient cette capture ?\nbxc frames search capture.png --limit 3\n\n# à quelle seconde de l'épisode 12 se trouve ce plan ?\nbxc frames search plan.jpg --local --limit 1\n```\n\n## API\n\n```ts\nimport { FrameSearch } from \"@aphrody/frames\";\n\nconst frames = new FrameSearch({ indexPath: \"~/.cache/bxc/frames.db\" });\n\nawait frames.indexVideo(\"ep01.mkv\", { fps: 2, title: \"Inazuma Eleven\", season: 1, episode: 1 });\n\nconst found = await frames.search(\"capture.jpg\");\n// { origin: \"local\" | \"remote\", matches: [{ title, episode, fromMs, atMs, toMs, similarity }] }\n```\n\nBriques séparées si la façade ne convient pas :\n`@aphrody/frames/descriptor` (extraction, distance), `/extract` (ffmpeg),\n`/store` (index SQLite), `/search` (recherche + regroupement en scènes),\n`/trace-moe` (client de l'API publique).\n\n## Similarité\n\nLe score suit l'échelle de trace.moe : **au-dessous de 0,90, le résultat est\nprobablement faux**, quelle que soit sa place au classement. Il est dérivé de\nla distance MPEG-7 par `1 − d / 100`, où 100 vient de mesures sur des trames\nd'anime décodées par ce module :\n\n| distance | ce que c'est |\n| --- | --- |\n| 0 – 10 | la même image (ré-encodée, redimensionnée) |\n| 10 – 30 | le même plan, à une seconde près |\n| > 34 | deux images sans rapport |\n\nLa taille de vignette n'influe pas : sur une trame d'épisode, le descripteur\nest **identique** entre 64 et 512 pixels de côté (il ne garde que la moyenne de\n64 blocs). D'où le décodage en 128×128 par défaut — cent fois moins de pixels\nque la pleine résolution, pour le même vecteur.\n\n## Confidentialité\n\nC'est la raison d'être du chemin local. Chercher une image, c'est révéler ce\nqu'on regarde ; un service tiers qui reçoit la capture apprend l'image *et*\nl'intention.\n\n- **Index local** : rien ne quitte la machine.\n- **Recours distant** : `searchByVector` n'envoie que les **33 entiers** du\n  descripteur (28 caractères en base64), jamais l'image — et c'est aussi le\n  chemin le plus rapide, le serveur n'ayant rien à télécharger ni décoder. Même\n  précision mesurée : 0,9920 contre 0,9919 en téléversant le fichier.\n- La clé d'API éventuelle part en en-tête `x-trace-key`, jamais dans l'URL.\n- `bxc frames vector capture.jpg` donne de quoi faire chercher quelqu'un\n  d'autre à votre place, sans lui montrer l'image.\n\n## Limites connues\n\n- **Le descripteur est global** : une incrustation, un bandeau, un recadrage ou\n  une bordure changent le vecteur. Une capture plein cadre marche, une vignette\n  brandée non (mesuré plus haut). `cutBorders` côté trace.moe ne retire que les\n  bandes noires.\n- **Deux plans quasi identiques** (un ciel, un fondu au noir, un écran blanc)\n  se ressemblent forcément : le score sera élevé et le résultat arbitraire.\n- **La recherche est exhaustive** : linéaire en nombre de trames. Une demi-\n  seconde pour 600 000 trames ; au-delà de quelques millions, il faudra un\n  index approché.\n- **L'échantillonnage borne la précision** : à 1 img/s, l'horodatage est juste\n  à la seconde. Monter à 4 img/s coûte 4× l'espace, pas le temps d'indexation.\n- **trace.moe** : 100 recherches par 24 h sans clé, une seule à la fois (une\n  deuxième requête simultanée est refusée avec le même code HTTP qu'un quota\n  épuisé — le client distingue les deux avec le quota connu).\n\n## Tests\n\n```bash\nbun test packages/frames    # 61 cas, sans ffmpeg, sans réseau\n```\n\nLe processus ffmpeg et le `fetch` sont injectables : les tests vérifient les\narguments de commande, rejouent un flux `rawvideo` factice (y compris coupé au\nmilieu d'une trame) et pilotent l'horloge pour les reprises et les budgets.\n","readmeFilename":"README.md"}