misskey/packages/backend/src/core/AiService.ts

78 lines
2.1 KiB
TypeScript
Raw Normal View History

/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
2022-09-18 03:27:08 +09:00
import * as fs from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { Injectable } from '@nestjs/common';
2022-09-18 03:27:08 +09:00
import * as nsfw from 'nsfwjs';
import si from 'systeminformation';
import { Mutex } from 'async-mutex';
2022-12-04 17:05:32 +09:00
import { bindThis } from '@/decorators.js';
import type Logger from '@/logger.js';
import { LoggerService } from '@/core/LoggerService.js';
2022-09-18 03:27:08 +09: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 {
private logger: Logger;
2022-09-19 03:11:50 +09:00
private model: nsfw.NSFWJS;
private modelLoadMutex: Mutex = new Mutex();
2022-09-18 03:27:08 +09:00
constructor(
private loggerService: LoggerService,
2022-09-18 03:27:08 +09:00
) {
this.logger = this.loggerService.getLogger('ai');
2022-09-18 03:27:08 +09:00
}
@bindThis
2022-09-18 03:27:08 +09:00
public async detectSensitive(path: string): Promise<nsfw.predictionType[] | null> {
try {
if (isSupportedCpu === undefined) {
2022-09-19 03:11:50 +09:00
const cpuFlags = await this.getCpuFlags();
2022-09-18 03:27:08 +09:00
isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required));
}
2022-09-18 03:27:08 +09:00
if (!isSupportedCpu) {
this.logger.error('This CPU cannot use TensorFlow.');
2022-09-18 03:27:08 +09:00
return null;
}
2022-09-18 03:27:08 +09:00
const tf = await import('@tensorflow/tfjs-node');
if (this.model == null) {
await this.modelLoadMutex.runExclusive(async () => {
if (this.model == null) {
this.model = await nsfw.load(`file://${_dirname}/../../nsfw-model/`, { size: 299 });
}
});
}
2022-09-18 03:27:08 +09:00
const buffer = await fs.promises.readFile(path);
const image = await tf.node.decodeImage(buffer, 3) as any;
try {
2022-09-19 03:11:50 +09:00
const predictions = await this.model.classify(image);
2022-09-18 03:27:08 +09:00
return predictions;
} finally {
image.dispose();
}
} catch (err) {
this.logger.error('Failed to detect sensitive', { error: err });
2022-09-18 03:27:08 +09:00
return null;
}
}
@bindThis
2022-09-19 03:11:50 +09:00
private async getCpuFlags(): Promise<string[]> {
2022-09-18 03:27:08 +09:00
const str = await si.cpuFlags();
return str.split(/\s+/);
}
}