File size: 3,131 Bytes
9fa1f67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3709eff
9fa1f67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3709eff
9fa1f67
 
3709eff
9fa1f67
 
 
 
 
 
 
 
 
 
 
3709eff
9fa1f67
 
 
3709eff
9fa1f67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import Koa from 'koa'
import http from 'axios'
import sharp from 'sharp'
import tf from '@tensorflow/tfjs-node'
import nsfw from 'nsfwjs'

import { extractColors } from './extract-colors.js'
import FastAverageColor from './fast-average-color.js'
import { phash } from './phash.js'

const app = new Koa()

app.use(async (ctx) => {
  const { data: image_buffer } = await http({
    method: 'GET',
    url: String(ctx.request.query.url).replace(
      'i.pximg.net',
      'i.pixiv.re',
    ),
    responseType: 'arraybuffer',
  })
  const { data: image_raw, info } = await sharp(image_buffer, {
    animated: true,
  })
    .ensureAlpha()
    .raw()
    .toBuffer({ resolveWithObject: true })
  const image_phash = await phash(image_buffer)
  const colors = await extractColors(
    { data: image_raw, width: info.width, height: info.height },
    {
      pixels: info.width * info.height,
      distance: 0.05,
      splitPower: 6,
      colorValidator: (red, green, blue, alpha = 255) => alpha >= 255,
      hueDistance: 0,
      saturationDistance: 1,
      lightnessDistance: 1,
    },
  )
  const fac = new FastAverageColor()
  const {
    value: [r, g, b, a],
    hex,
    isLight: blight,
  } = fac.prepareResult(fac.getColorFromArray4(image_raw))
  const image_resize = await sharp(image_buffer, { animated: true })
    .resize({
      width: 128,
      height: 128,
      fit: 'inside',
    })
    .gif({ dither: 0 })
    .toBuffer()
  // const model = await nsfw.load(`https://raw.githubusercontent.com/infinitered/nsfwjs/refs/heads/master/models/mobilenet_v2_mid/model.json`, { type: 'graph' })
  //   // const model = await nsfw.load(`https://raw.githubusercontent.com/infinitered/nsfwjs/refs/heads/master/models/mobilenet_v2/model.json`)
  //   // const model = await nsfw.load(`https://s3.amazonaws.com/ir_public/nsfwjscdn/TFJS_nsfw_mobilenet/tfjs_quant_nsfw_mobilenet/model.json`)
  const model = await nsfw.load(`https://raw.githubusercontent.com/infinitered/nsfwjs/refs/heads/master/models/inception_v3/model.json`, { size: 299 })
  const predictions = await model.classifyGif(image_resize, { fps: 1 })
  const detail = new Map()
  predictions
    .flat(Infinity)
    .sort((a, b) => a.probability - b.probability)
    .forEach((i) => {
      detail.set(
        i.className.toLocaleLowerCase(),
        Math.floor(i.probability * 100),
      )
    })
  const fail = ['hentai', 'porn', 'sexy']
  const result = [...detail]
    .filter((i) => i.pop() > 7)
    .some((i) => fail.includes(i.pop()))
  tf.dispose()
  model.model.dispose()
  ctx.body = {
    nsfw: Object.assign({ result }, Object.fromEntries(detail)),
    colors: colors
      .sort((a, b) => b.area - a.area)
      .slice(0, 3)
      .map((i) => {
        // hex: `#${Number(十进制).toString(16).padStart(6, 0)}`
        return {
          color: parseInt(String(i.hex).replace('#', ''), 16),
          ratio: Number(i.area.toFixed(2)),
        }
      }),
    color: hex,
    // blight,
    phash: image_phash,
  }
})

app.listen(process.env.PORT || 7860)