fuzzyworld

explodorama / files / tools / audit-art.mjs

audit-art.mjs

Blame: the commit that last changed each line · file · history

d64916df 2026-11-01 Pavel Safronov1// Art provenance check (2026-11-01): is every file under assets/kenney* really Kenney's?
2// Hashes each file and looks for the same bytes anywhere in a folder of unpacked Kenney packs.
3// Files that match are "verbatim"; the rest are listed, so each can be explained in
4// docs/art-provenance.md (a scaled copy, a converted sound). The packs are not in the repo.
5//
6// node tools/audit-art.mjs [folder of unpacked packs, default ~/code/kenney] [--table]
7//
8// --table prints the verbatim files as markdown rows (file | pack | pack path).
9import { createHash } from 'node:crypto'
10import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
11import { homedir } from 'node:os'
12import { join, relative } from 'node:path'
13import { fileURLToPath } from 'node:url'
14
15const args = process.argv.slice(2)
16const table = args.includes('--table')
17const packs = args.find((a) => !a.startsWith('--')) ?? join(homedir(), 'code/kenney')
18const repo = fileURLToPath(new URL('..', import.meta.url))
19if (!existsSync(packs)) {
20 console.error(`No folder of Kenney packs at ${packs}; pass its path as the first argument.`)
21 process.exit(1)
22}
23
24const walk = (dir) =>
25 readdirSync(dir).flatMap((n) => {
26 const p = join(dir, n)
27 return statSync(p).isDirectory() ? walk(p) : [p]
28 })
29const md5 = (p) => createHash('md5').update(readFileSync(p)).digest('hex')
30
31const byHash = new Map()
32for (const p of walk(packs).filter((f) => !f.endsWith('.zip'))) byHash.set(md5(p), relative(packs, p))
33
34const verbatim = []
35const other = []
36for (const p of walk(join(repo, 'assets')).sort()) {
37 const rel = relative(repo, p)
38 if (/composed\/|\.DS_Store|License\.txt|license\.txt/i.test(rel)) continue
39 const hit = byHash.get(md5(p))
40 if (hit) verbatim.push([rel, hit])
41 else other.push(rel)
42}
43
44if (table) {
45 for (const [rel, hit] of verbatim) {
46 const [pack, ...rest] = hit.split('/')
47 console.log(`| ${rel.replace('assets/', '')} | ${pack.replace('kenney_', '')} | ${rest.join('/')} |`)
48 }
49} else {
50 console.log(`${verbatim.length} files are byte-identical to a file in ${packs}`)
51 console.log(`${other.length} are not (explain each in docs/art-provenance.md):`)
52 for (const rel of other) console.log(' ' + rel)
53}