 8d06a6475e
			
		
	
	8d06a6475e
	
	
	
		
			
			* chore: 著作権とライセンスについての情報を各ファイルに追加する * chore: Add the SPDX information to each file Add copyright and licensing information as defined in version 3.0 of the REUSE Specification. * tweak format --------- Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> * chore: Add SPDX-License-Identifier [skip ci] * add missing SPDX-License-Identifier * remove unused file --------- Co-authored-by: Shun Sakai <sorairolake@protonmail.ch> Co-authored-by: syuilo <Syuilotan@yahoo.co.jp> Co-authored-by: Chocolate Pie <106949016+chocolate-pie@users.noreply.github.com>
		
			
				
	
	
		
			73 lines
		
	
	
		
			1.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			73 lines
		
	
	
		
			1.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /*
 | |
|  * SPDX-FileCopyrightText: syuilo and other misskey contributors
 | |
|  * SPDX-License-Identifier: AGPL-3.0-only
 | |
|  */
 | |
| 
 | |
| import * as fs from 'node:fs';
 | |
| import { fileURLToPath } from 'node:url';
 | |
| import { dirname } from 'node:path';
 | |
| import { Injectable } from '@nestjs/common';
 | |
| import * as nsfw from 'nsfwjs';
 | |
| import si from 'systeminformation';
 | |
| import { Mutex } from 'async-mutex';
 | |
| import { bindThis } from '@/decorators.js';
 | |
| 
 | |
| 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 model: nsfw.NSFWJS;
 | |
| 	private modelLoadMutex: Mutex = new Mutex();
 | |
| 
 | |
| 	constructor(
 | |
| 	) {
 | |
| 	}
 | |
| 
 | |
| 	@bindThis
 | |
| 	public async detectSensitive(path: string): Promise<nsfw.predictionType[] | null> {
 | |
| 		try {
 | |
| 			if (isSupportedCpu === undefined) {
 | |
| 				const cpuFlags = await this.getCpuFlags();
 | |
| 				isSupportedCpu = REQUIRED_CPU_FLAGS.every(required => cpuFlags.includes(required));
 | |
| 			}
 | |
| 
 | |
| 			if (!isSupportedCpu) {
 | |
| 				console.error('This CPU cannot use TensorFlow.');
 | |
| 				return null;
 | |
| 			}
 | |
| 
 | |
| 			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 });
 | |
| 					}
 | |
| 				});
 | |
| 			}
 | |
| 
 | |
| 			const buffer = await fs.promises.readFile(path);
 | |
| 			const image = await tf.node.decodeImage(buffer, 3) as any;
 | |
| 			try {
 | |
| 				const predictions = await this.model.classify(image);
 | |
| 				return predictions;
 | |
| 			} finally {
 | |
| 				image.dispose();
 | |
| 			}
 | |
| 		} catch (err) {
 | |
| 			console.error(err);
 | |
| 			return null;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	@bindThis
 | |
| 	private async getCpuFlags(): Promise<string[]> {
 | |
| 		const str = await si.cpuFlags();
 | |
| 		return str.split(/\s+/);
 | |
| 	}
 | |
| }
 |