root commited on
Commit
dfe9a5e
·
1 Parent(s): 934878b
Files changed (46) hide show
  1. .dockerignore +8 -0
  2. .eslintrc.js +48 -0
  3. .gitignore +5 -0
  4. Dockerfile +19 -3
  5. NSFW-API/.gitattributes +35 -0
  6. NSFW-API/Dockerfile +5 -0
  7. NSFW-API/README.md +11 -0
  8. README.md +122 -11
  9. models/default/group1-shard10of22 +3 -0
  10. models/default/group1-shard11of22 +3 -0
  11. models/default/group1-shard12of22 +3 -0
  12. models/default/group1-shard13of22 +3 -0
  13. models/default/group1-shard14of22 +3 -0
  14. models/default/group1-shard15of22 +3 -0
  15. models/default/group1-shard16of22 +3 -0
  16. models/default/group1-shard17of22 +3 -0
  17. models/default/group1-shard18of22 +3 -0
  18. models/default/group1-shard19of22 +3 -0
  19. models/default/group1-shard1of22 +3 -0
  20. models/default/group1-shard20of22 +3 -0
  21. models/default/group1-shard21of22 +3 -0
  22. models/default/group1-shard22of22 +3 -0
  23. models/default/group1-shard2of22 +3 -0
  24. models/default/group1-shard3of22 +3 -0
  25. models/default/group1-shard4of22 +3 -0
  26. models/default/group1-shard5of22 +3 -0
  27. models/default/group1-shard6of22 +3 -0
  28. models/default/group1-shard7of22 +3 -0
  29. models/default/group1-shard8of22 +3 -0
  30. models/default/group1-shard9of22 +3 -0
  31. models/default/model.json +3 -0
  32. models/min/group1-shard1of6 +3 -0
  33. models/min/group1-shard2of6 +3 -0
  34. models/min/group1-shard3of6 +3 -0
  35. models/min/group1-shard4of6 +3 -0
  36. models/min/group1-shard5of6 +3 -0
  37. models/min/group1-shard6of6 +3 -0
  38. models/min/model.json +3 -0
  39. nsfw-api +1 -0
  40. package.json +37 -0
  41. src/NsfwImageClassifier.ts +46 -0
  42. src/config/model.ts +5 -0
  43. src/controllers/NsfwController.ts +42 -0
  44. src/index.ts +17 -0
  45. tsconfig.json +33 -0
  46. yarn.lock +0 -0
.dockerignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ Dockerfile
2
+ node_modules
3
+ build
4
+ .eslintrc.js
5
+ .git
6
+ .gitattributes
7
+ .gitignore
8
+ .github
.eslintrc.js ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ env: {node: true},
3
+ root: true,
4
+ parser: '@typescript-eslint/parser',
5
+ plugins: [
6
+ '@typescript-eslint',
7
+ ],
8
+ extends: [
9
+ 'eslint:recommended',
10
+ 'plugin:@typescript-eslint/recommended',
11
+ ],
12
+ rules: {
13
+ 'no-async-promise-executor': 0,
14
+ 'no-case-declarations': ['warn'],
15
+ 'no-irregular-whitespace': 0,
16
+ 'require-await': 1,
17
+ 'no-console': 0,
18
+ 'indent': 'off',
19
+ '@typescript-eslint/indent': [
20
+ 'error',
21
+ 2,
22
+ {
23
+ 'SwitchCase': 1,
24
+ 'ignoredNodes': [
25
+ 'FunctionExpression > .params[decorators.length > 0]',
26
+ 'FunctionExpression > .params > :matches(Decorator, :not(:first-child))',
27
+ 'ClassBody.body > PropertyDefinition[decorators.length > 0] > .key'
28
+ ]
29
+ }
30
+ ],
31
+ 'linebreak-style': ['error', 'unix'],
32
+ 'quotes': ['error', 'single'],
33
+ 'semi': ['error', 'always'],
34
+ 'prefer-const': ['warn'],
35
+ '@typescript-eslint/no-unused-vars': [
36
+ 'warn',
37
+ {'vars': 'all', 'args': 'after-used', 'ignoreRestSiblings': false}
38
+ ],
39
+ 'switch-colon-spacing': [
40
+ 'error',
41
+ {'after': false, 'before': false}
42
+ ],
43
+ '@typescript-eslint/no-explicit-any': 0,
44
+ '@typescript-eslint/explicit-module-boundary-types': 0,
45
+ '@typescript-eslint/no-var-requires': 0,
46
+ '@typescript-eslint/ban-ts-comment': 0,
47
+ },
48
+ };
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .idea
2
+ node_modules
3
+ build
4
+ .env
5
+ yarn-error.log
Dockerfile CHANGED
@@ -1,5 +1,21 @@
1
- FROM ghcr.io/arnidan/nsfw-api:latest
2
 
 
 
3
 
4
- # 暴露7860端口
5
- EXPOSE 7860
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:lts-slim
2
 
3
+ RUN apt-get update && apt-get install -y python3 build-essential \
4
+ && rm -rf /var/lib/apt/lists/*
5
 
6
+ COPY yarn.lock package.json ./
7
+
8
+ RUN npm_config_build_from_source=true yarn install --prod
9
+
10
+ COPY src ./src
11
+ COPY tsconfig.json ./
12
+
13
+ ARG modelType=default
14
+
15
+ COPY ./models/$modelType ./model
16
+
17
+ RUN yarn build
18
+
19
+ EXPOSE 3000
20
+
21
+ ENTRYPOINT ["yarn", "start"]
NSFW-API/.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
NSFW-API/Dockerfile ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ FROM ghcr.io/arnidan/nsfw-api:latest
2
+
3
+
4
+ # 暴露7860端口
5
+ EXPOSE 7860
NSFW-API/README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: NSFW API
3
+ emoji: 😻
4
+ colorFrom: red
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ ---
10
+
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
README.md CHANGED
@@ -1,11 +1,122 @@
1
- ---
2
- title: NSFW API
3
- emoji: 😻
4
- colorFrom: red
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # NSFW API
2
+
3
+ [![build](https://github.com/arnidan/nsfw-api/actions/workflows/build.yml/badge.svg)](https://github.com/arnidan/nsfw-api/actions/workflows/build.yml)
4
+ [![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](https://www.typescriptlang.org/)
5
+
6
+ Wrapper around [NSFWJS](https://github.com/infinitered/nsfwjs) to provide API.
7
+
8
+ ## Usage
9
+
10
+ ### Docker
11
+
12
+ There are docker images bundled with models v3 from https://github.com/gantman/nsfw_model:
13
+
14
+ * `ghcr.io/arnidan/nsfw-api:latest` - bundled with TensorflowJS 299x299 Image Model (better detection, as for me)
15
+ * `ghcr.io/arnidan/nsfw-api:latest-min` - bundled with TensorflowJS Quantized 299x299 Image Model (see [#39](https://github.com/arnidan/nsfw-api/issues/49))
16
+
17
+ Each image is available for `linux/amd64` and `linux/arm64` platforms.
18
+
19
+ ```
20
+ docker run -p 3000:3000 ghcr.io/arnidan/nsfw-api:latest
21
+ ```
22
+
23
+ <details>
24
+ <summary>docker-compose.yml example</summary>
25
+
26
+ ```yaml
27
+ version: "3.9"
28
+
29
+ services:
30
+ nsfw-api:
31
+ image: "ghcr.io/arnidan/nsfw-api:latest"
32
+ ports:
33
+ - "3000:3000"
34
+ restart: always
35
+ ```
36
+
37
+ </details>
38
+
39
+ ### Manual deploy
40
+
41
+ 1. Clone the repo
42
+ 2. Download and unpack model from [models repo](https://github.com/gantman/nsfw_model) to `model` folder
43
+ 3. `yarn`
44
+ 4. `yarn build`
45
+ 5. `yarn start`
46
+
47
+ Now app started on port 3000.
48
+
49
+ ## API Methods
50
+
51
+ - POST /classify
52
+ - POST /classify-many
53
+
54
+ ### POST /classify
55
+
56
+ #### Example of the request
57
+
58
+ ```http request
59
+ POST /classify HTTP/1.1
60
+ Content-Type: multipart/form-data
61
+ ```
62
+
63
+ Image should be provided in `image` field.
64
+
65
+ #### Example of the response
66
+
67
+ ```
68
+ HTTP/1.1 200 OK
69
+ Content-Type: application/json
70
+ ```
71
+ ```json
72
+ {
73
+ "porn": 0.59248286485672,
74
+ "sexy": 0.39802199602127075,
75
+ "hentai": 0.006243097595870495,
76
+ "neutral": 0.0031403270550072193,
77
+ "drawing": 0.00011181648733327165
78
+ }
79
+ ```
80
+
81
+ ### POST /classify-many
82
+
83
+ #### Example of the request
84
+
85
+ ```http request
86
+ POST /classify-many HTTP/1.1
87
+ Content-Type: multipart/form-data
88
+ ```
89
+
90
+ Images should be provided in `images` field.
91
+
92
+ #### Example of the response
93
+
94
+ ```
95
+ HTTP/1.1 200 OK
96
+ Content-Type: application/json
97
+ ```
98
+ ```json
99
+ [
100
+ {
101
+ "porn": 0.3996206820011139,
102
+ "neutral": 0.388679563999176,
103
+ "sexy": 0.19470958411693573,
104
+ "hentai": 0.015063910745084286,
105
+ "drawing": 0.001926235854625702
106
+ },
107
+ {
108
+ "sexy": 0.8366416692733765,
109
+ "porn": 0.13645528256893158,
110
+ "neutral": 0.0222245492041111,
111
+ "hentai": 0.004213324282318354,
112
+ "drawing": 0.0004651622730307281
113
+ },
114
+ {
115
+ "sexy": 0.8017168045043945,
116
+ "porn": 0.1770564466714859,
117
+ "neutral": 0.015829339623451233,
118
+ "hentai": 0.005097625777125359,
119
+ "drawing": 0.00029983260901644826
120
+ }
121
+ ]
122
+ ```
models/default/group1-shard10of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2304b6697ffb4e6b812461722dd44d808ec3dbd2b6d584656d802021d089637f
3
+ size 4194304
models/default/group1-shard11of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c3921daff4e77b822d7d0538013dee405b06e721bd24ca32d7d842eaa33df554
3
+ size 4194304
models/default/group1-shard12of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:081ce576fde1d060a49f2908bbd9b2e9df1e266eb014ee79b75be8b654631bc2
3
+ size 4194304
models/default/group1-shard13of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:63a9eb50544d1b606b33b0b13bf8b671fd6871900a8d1e98183f9155c82d2151
3
+ size 4194304
models/default/group1-shard14of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:67b803099de34d091e7dc65adc8963f2a3d1e287590dcccb979becf3929b4d7f
3
+ size 4194304
models/default/group1-shard15of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b3dd86d30dcb4ca5ae80df0a84045ab1c24a2143abeab1c00e4ae6afeb78edc9
3
+ size 4194304
models/default/group1-shard16of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:987f5eaa433a83bbbef9eb239c8a364c071c0f5d2a56e337b3eadb5b9eef8bef
3
+ size 4194304
models/default/group1-shard17of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b32181ac7995f9f6d25e48f92527b50cbbdb08727205fa577c4576f39a451a6
3
+ size 4194304
models/default/group1-shard18of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:38b08fd98aa97ce5b348978d9a7fd5e94354908faaad5f37ba08a44a1fb9bd03
3
+ size 4194304
models/default/group1-shard19of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aad15be21400a538b2d21c66a900d73a67afd96f3979cc69d8496fe9e4dc41ce
3
+ size 4194304
models/default/group1-shard1of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6f631596a54b464c305eaa32e43a009fe3cf44332001e37735ff73591b0410d7
3
+ size 4194304
models/default/group1-shard20of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:16ec8baa36f0050b108fd3785d40cedd493adc398eb04b726370931543f1f41a
3
+ size 4194304
models/default/group1-shard21of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b146f6e84f07b7fc5379b639b7dfbe71f955b5a678e7962c079824d4013490d9
3
+ size 4194304
models/default/group1-shard22of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:27c141257999d4b20c4b3e3b0b8bc5b4388db4eb65e202499b27a4e0e16a103c
3
+ size 1363092
models/default/group1-shard2of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c3546b791ad3167e2557d145d319c813a81ce9c9261b9a1b880e249e19880eac
3
+ size 4194304
models/default/group1-shard3of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3736ceb6db2accb3e8f6e44ad0a5b0ab5f68fa4b2a8a1ee7859afd2155f069bf
3
+ size 4194304
models/default/group1-shard4of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8f2b24050c3289cf68c6cae7e9a285be4d441cf69b9488cd64000100dc9b702b
3
+ size 4194304
models/default/group1-shard5of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:45cbfc744817cde12b4c5e105799115611b2155c6f7ed39a1e808d19456da7d6
3
+ size 4194304
models/default/group1-shard6of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ba39e02d47d536b791e522bac126d3573d3fab4c58ffc2e5f5af3351c848d9fe
3
+ size 4194304
models/default/group1-shard7of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:529755427a9b89e68b7c171ad5208d426ebfc43b880c1c1da27d3b69c3ffc26c
3
+ size 4194304
models/default/group1-shard8of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:40887e59f348659ff684f18fba7345887ca6d55cadb32a2e767b483a7f7d4b03
3
+ size 4194304
models/default/group1-shard9of22 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:456c4d41b34bfeadfe9b3781cb752e44f024baa32810b0a50840b0fd2390f994
3
+ size 4194304
models/default/model.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fc4dbb6255feaed52a79d80728b6fa947b014655fe44012c07b16590d7198ef7
3
+ size 179999
models/min/group1-shard1of6 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7a3a4c075cfcaa7f0b095f55b2ba54a288c2f770e504c8537394efda9545994b
3
+ size 4194304
models/min/group1-shard2of6 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8826da12bda0db9415aef0e6a8d1f178a0d950deab28c4e79b9da994748702a4
3
+ size 4194304
models/min/group1-shard3of6 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4557a96cf87ceb8df6c9647b647ac9843251a4455457cb40f35c02250a63e51c
3
+ size 4194304
models/min/group1-shard4of6 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:399b99397ccd64351b59f04cdabe30000a15494c82f75c1e2ccb41c9b6338b7d
3
+ size 4194304
models/min/group1-shard5of6 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:994515b0f73407d71740301e9877511b086609dbdc7ead1b31bcb82b3397ae74
3
+ size 4194304
models/min/group1-shard6of6 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cfb856c9d8fcefacca87b6063ed4949adf69f0372407d6edca8cec0c1694a28b
3
+ size 1389349
models/min/model.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1220fc23413405858655f9c7342d5e201d9b8e0356904267d59659da71202cc3
3
+ size 215920
nsfw-api ADDED
@@ -0,0 +1 @@
 
 
1
+ Subproject commit 0155c893c2d688ae4e17aca7dce2de55e4f5602d
package.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "nsfwjs-api",
3
+ "version": "1.0.0",
4
+ "main": "build/index.js",
5
+ "author": "Andrew",
6
+ "private": true,
7
+ "scripts": {
8
+ "build": "tsc -p tsconfig.json",
9
+ "start": "node build/index.js",
10
+ "start-dev": "ts-node --project ./tsconfig.json -T --emit --files src/index.ts",
11
+ "watch": "ts-node-dev -T src/index.ts",
12
+ "lint": "eslint src/ --ext .js,.ts"
13
+ },
14
+ "dependencies": {
15
+ "@tensorflow/tfjs-node": "^3.20.0",
16
+ "@types/body-parser": "^1.19.2",
17
+ "@types/express": "^4.17.13",
18
+ "@types/multer": "^1.4.7",
19
+ "@types/node": "^18.15.11",
20
+ "body-parser": "^1.20.2",
21
+ "express": "^4.17.1",
22
+ "module-alias": "^2.2.2",
23
+ "multer": "^1.4.4",
24
+ "nsfwjs": "^2.4.2",
25
+ "reflect-metadata": "^0.1.13",
26
+ "simple-ts-express-decorators": "1.3.0",
27
+ "typescript": "^5.0.3"
28
+ },
29
+ "devDependencies": {
30
+ "@typescript-eslint/eslint-plugin": "^5.57.0",
31
+ "@typescript-eslint/parser": "^5.57.0",
32
+ "eslint": "^8.37.0"
33
+ },
34
+ "_moduleAliases": {
35
+ "app": "./build"
36
+ }
37
+ }
src/NsfwImageClassifier.ts ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as tf from '@tensorflow/tfjs-node';
2
+ import * as nsfwjs from 'nsfwjs';
3
+ import {NSFWJS} from 'nsfwjs';
4
+ import {Tensor3D} from '@tensorflow/tfjs';
5
+ import {model as config} from 'app/config/model';
6
+
7
+ tf.enableProdMode();
8
+
9
+ export class NsfwImageClassifier {
10
+ #model?: NSFWJS;
11
+
12
+ async classify(imageBuffer: Buffer) {
13
+ const [model, image] = await Promise.all([
14
+ this.#getModel(),
15
+ tf.node.decodeImage(imageBuffer, 3),
16
+ ]);
17
+
18
+ const predictions = await model.classify(image as Tensor3D);
19
+
20
+ image.dispose();
21
+
22
+ return this.#transformData(predictions);
23
+ }
24
+
25
+ async classifyMany(imagesBuffers: Buffer[]) {
26
+ return await Promise.all(imagesBuffers.map(buffer => this.classify(buffer)));
27
+ }
28
+
29
+ async #getModel(): Promise<NSFWJS> {
30
+ if (!this.#model) {
31
+ this.#model = await nsfwjs.load('file://model/', {size: config.size});
32
+ }
33
+
34
+ return this.#model;
35
+ }
36
+
37
+ #transformData(data: { className: string; probability: number }[]): Record<string, number> {
38
+ const result: Record<string, number> = {};
39
+
40
+ for (const item of data) {
41
+ result[item.className.toLowerCase()] = item.probability;
42
+ }
43
+
44
+ return result;
45
+ }
46
+ }
src/config/model.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ const DEFAULT_MODEL_SIZE = 299;
2
+
3
+ export const model = {
4
+ size: Number(process.env.MODEL_SIZE) || DEFAULT_MODEL_SIZE,
5
+ };
src/controllers/NsfwController.ts ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {Controller, Post} from 'simple-ts-express-decorators';
2
+ import multer, {memoryStorage} from 'multer';
3
+ import {Request, Response} from 'express';
4
+ import {NsfwImageClassifier} from 'app/NsfwImageClassifier';
5
+
6
+ const upload = multer({storage: memoryStorage()});
7
+
8
+ @Controller()
9
+ export class NsfwController {
10
+ classifier: NsfwImageClassifier;
11
+
12
+ constructor() {
13
+ this.classifier = new NsfwImageClassifier();
14
+ }
15
+
16
+ @Post('/classify', upload.single('image'))
17
+ async classify(request: Request, response: Response) {
18
+ if (!request.file) {
19
+ return response
20
+ .status(410)
21
+ .json({error: 'Specify image'});
22
+ }
23
+
24
+ const data = await this.classifier.classify(request.file.buffer);
25
+
26
+ return response.json(data);
27
+ }
28
+
29
+ @Post('/classify-many', upload.array('images', 10))
30
+ async classifyMany(request: Request, response: Response) {
31
+ if (!request.files || !request.files.length) {
32
+ return response
33
+ .status(410)
34
+ .json({error: 'Specify images'});
35
+ }
36
+
37
+ const buffers = (request.files as Express.Multer.File[]).map(file => file.buffer);
38
+ const data = await this.classifier.classifyMany(buffers);
39
+
40
+ return response.json(data);
41
+ }
42
+ }
src/index.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import 'reflect-metadata';
2
+ import 'module-alias/register';
3
+
4
+ import express from 'express';
5
+ import bodyParser from 'body-parser';
6
+ import {ControllersLoader} from 'simple-ts-express-decorators';
7
+ import {NsfwController} from 'app/controllers/NsfwController';
8
+
9
+ const app = express();
10
+
11
+ app.use(bodyParser.json());
12
+
13
+ new ControllersLoader({
14
+ controllers: [NsfwController]
15
+ }).load(app);
16
+
17
+ app.listen(3000);
tsconfig.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "module": "commonjs",
5
+ "experimentalDecorators": true,
6
+ "emitDecoratorMetadata": true,
7
+ "outDir": "./build",
8
+ "removeComments": true,
9
+ "moduleResolution": "node",
10
+ "noFallthroughCasesInSwitch": true,
11
+ "noEmitOnError": true,
12
+ "baseUrl": ".",
13
+ "strict": true,
14
+ "strictFunctionTypes": true,
15
+ "strictNullChecks": true,
16
+ "strictPropertyInitialization": true,
17
+ "strictBindCallApply": true,
18
+ "paths": {
19
+ "app/*": ["./src/*"]
20
+ },
21
+ "incremental": true,
22
+ "types": ["reflect-metadata", "node"],
23
+ "allowSyntheticDefaultImports": true,
24
+ "esModuleInterop": true,
25
+ "skipLibCheck": true
26
+ },
27
+ "include": [
28
+ "./src/**/*.ts",
29
+ ],
30
+ "exclude": [
31
+ "./node_modules"
32
+ ]
33
+ }
yarn.lock ADDED
Binary file (116 kB). View file