2023-07-27 07:31:52 +02:00
|
|
|
/*
|
2024-02-13 16:50:11 +01:00
|
|
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
2023-07-27 07:31:52 +02:00
|
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-09-17 20:27:08 +02:00
|
|
|
import * as fs from 'node:fs';
|
|
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
import { dirname } from 'node:path';
|
2023-08-05 03:33:00 +02:00
|
|
|
import { Injectable } from '@nestjs/common';
|
2022-09-17 20:27:08 +02:00
|
|
|
import * as nsfw from 'nsfwjs';
|
|
|
|
import si from 'systeminformation';
|
2023-07-08 00:27:26 +02:00
|
|
|
import { Mutex } from 'async-mutex';
|
2022-12-04 09:05:32 +01:00
|
|
|
import { bindThis } from '@/decorators.js';
|
2022-09-17 20:27:08 +02:00
|
|
|
|
|
|
|
const _filename = fileURLToPath(import.meta.url);
|
|
|
|
const _dirname = dirname(_filename);
|
|
|
|
|
|
|
|
const REQUIRED_CPU_FLAGS = ['avx2', 'fma'];
|
|
|
|
let isSupportedCpu: undefined | boolean = undefined;
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class AiService {
|
2022-09-18 20:11:50 +02:00
|
|
|
private model: nsfw.NSFWJS;
|
2023-07-08 00:27:26 +02:00
|
|
|
private modelLoadMutex: Mutex = new Mutex();
|
2022-09-17 20:27:08 +02:00
|
|
|
|
|
|
|
constructor(
|
|
|
|
) {
|
|
|
|
}
|
|
|
|
|
2022-12-04 07:03:09 +01:00
|
|
|
@bindThis
|
2022-09-17 20:27:08 +02:00
|
|
|
public async detectSensitive(path: string): Promise<nsfw.predictionType[] | null> {
|
|
|
|
try {
|
|
|
|
if (isSupportedCpu === undefined) {
|
2022-09-18 20:11:50 +02:00
|
|
|
const cpuFlags = await this.getCpuFlags();
|
2022-09-17 20:27:08 +02:00
|
|
|
isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required));
|
|
|
|
}
|
2023-07-08 00:08:16 +02:00
|
|
|
|
2022-09-17 20:27:08 +02:00
|
|
|
if (!isSupportedCpu) {
|
|
|
|
console.error('This CPU cannot use TensorFlow.');
|
|
|
|
return null;
|
|
|
|
}
|
2023-07-08 00:08:16 +02:00
|
|
|
|
2022-09-17 20:27:08 +02:00
|
|
|
const tf = await import('@tensorflow/tfjs-node');
|
2023-07-08 00:08:16 +02:00
|
|
|
|
2023-07-08 00:27:26 +02:00
|
|
|
if (this.model == null) {
|
|
|
|
await this.modelLoadMutex.runExclusive(async () => {
|
|
|
|
if (this.model == null) {
|
|
|
|
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2023-07-08 00:08:16 +02:00
|
|
|
|
2022-09-17 20:27:08 +02:00
|
|
|
const buffer = await fs.promises.readFile(path);
|
|
|
|
const image = await tf.node.decodeImage(buffer, 3) as any;
|
|
|
|
try {
|
2022-09-18 20:11:50 +02:00
|
|
|
const predictions = await this.model.classify(image);
|
2022-09-17 20:27:08 +02:00
|
|
|
return predictions;
|
|
|
|
} finally {
|
|
|
|
image.dispose();
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-04 07:03:09 +01:00
|
|
|
@bindThis
|
2022-09-18 20:11:50 +02:00
|
|
|
private async getCpuFlags(): Promise<string[]> {
|
2022-09-17 20:27:08 +02:00
|
|
|
const str = await si.cpuFlags();
|
|
|
|
return str.split(/\s+/);
|
|
|
|
}
|
|
|
|
}
|