🐛 fixed the logic of judging screenshots

This commit is contained in:
2025-01-23 19:15:36 +08:00
parent 0255e6b75e
commit a2e80b9a91
3 changed files with 54 additions and 46 deletions

View File

@@ -2,11 +2,11 @@ import localforage from 'localforage';
interface UploadPredictResult { interface UploadPredictResult {
isAnime: boolean; isAnime: boolean;
hasFace: boolean;
objectArray: string[] | unknown[]; objectArray: string[] | unknown[];
landscape: 'building' | 'forest' | 'glacier' | 'mountain' | 'sea' | 'street' | 'none'; landscape: 'building' | 'forest' | 'glacier' | 'mountain' | 'sea' | 'street' | 'none';
isScreenshot: boolean; isScreenshot: boolean;
topCategory: string | undefined; topCategory: string | undefined;
exif: object | null;
} }
@@ -15,14 +15,13 @@ export const useUploadStore = defineStore(
() => { () => {
const openUploadDrawer = ref<boolean>(false); const openUploadDrawer = ref<boolean>(false);
const exifData = ref<any>();
const predictResult = reactive<UploadPredictResult>({ const predictResult = reactive<UploadPredictResult>({
isAnime: false, isAnime: false,
hasFace: false,
objectArray: [], objectArray: [],
landscape: 'none', landscape: 'none',
isScreenshot: false, isScreenshot: false,
topCategory: '' topCategory: '',
exif: {}
}); });
/** /**
@@ -37,18 +36,17 @@ export const useUploadStore = defineStore(
*/ */
function clearPredictResult() { function clearPredictResult() {
predictResult.isAnime = false; predictResult.isAnime = false;
predictResult.hasFace = false;
predictResult.objectArray = []; predictResult.objectArray = [];
predictResult.landscape = 'none'; predictResult.landscape = 'none';
predictResult.isScreenshot = false; predictResult.isScreenshot = false;
predictResult.topCategory = ''; predictResult.topCategory = '';
predictResult.exif = {};
} }
return { return {
openUploadDrawer, openUploadDrawer,
predictResult, predictResult,
exifData,
openUploadDrawerFn, openUploadDrawerFn,
clearPredictResult, clearPredictResult,
}; };

View File

@@ -16,8 +16,11 @@ async function isScreenshot(file) {
'screenshot', '屏幕截图', '截屏', 'Snip', 'Capture', 'Snapshot', '截图' 'screenshot', '屏幕截图', '截屏', 'Snip', 'Capture', 'Snapshot', '截图'
]; ];
// 获取文件的 MIME 类型
const fileType = file.type.toLowerCase();
try { try {
// 文件名匹配 // 判断文件名是否包含截图相关关键词
const fileName = file.name.toLowerCase(); const fileName = file.name.toLowerCase();
const matchesFilename = screenshotFilenameKeywords.some(keyword => const matchesFilename = screenshotFilenameKeywords.some(keyword =>
fileName.includes(keyword.toLowerCase()) fileName.includes(keyword.toLowerCase())
@@ -26,45 +29,48 @@ async function isScreenshot(file) {
return true; // 如果文件名包含截图相关关键词,直接判断为截图 return true; // 如果文件名包含截图相关关键词,直接判断为截图
} }
// 提取 EXIF 数据 // 判断文件类型是否为支持的格式
const exifData = await exifr.parse(file, ['ImageWidth', 'ImageHeight', 'Software', 'XResolution', 'YResolution']); const isSupportedFormat = ['image/jpeg', 'image/tiff', 'image/heif', 'image/png'].includes(fileType);
const {ImageWidth, ImageHeight, Software} = exifData || {};
// 如果图片没有宽高信息,直接返回 false if (isSupportedFormat) {
if (!ImageWidth || !ImageHeight) { // 如果是支持的格式,提取 EXIF 数据
return false; const exifData = await exifr.parse(file, ['ImageWidth', 'ImageHeight', 'Software', 'XResolution', 'YResolution']);
} const {ImageWidth, ImageHeight, Software} = exifData || {};
// 校验宽高比是否接近常见屏幕比例 // 如果图片没有宽高信息,直接返回 false
const aspectRatio = Math.max(ImageWidth, ImageHeight) / Math.min(ImageWidth, ImageHeight); if (!ImageWidth || !ImageHeight) {
const matchesAspectRatio = commonAspectRatios.some( return false;
(ratio) => Math.abs(ratio - aspectRatio) <= aspectRatioTolerance
);
// 如果宽高比匹配,进一步检查
if (matchesAspectRatio) {
// 检查软件标记是否与截图工具相关
const screenshotSoftwareKeywords = ['Screenshot', 'Snipping Tool', 'Screen Capture', 'Grab', 'Sketch'];
if (Software && screenshotSoftwareKeywords.some((keyword) => Software.includes(keyword))) {
return true;
} }
// 检查文件大小(截图通常较小) // 校验宽高比是否接近常见屏幕比例
const fileSizeMB = file.size / (1024 * 1024); const aspectRatio = Math.max(ImageWidth, ImageHeight) / Math.min(ImageWidth, ImageHeight);
if (fileSizeMB <= maxScreenshotSizeMB) { const matchesAspectRatio = commonAspectRatios.some(
return true; (ratio) => Math.abs(ratio - aspectRatio) <= aspectRatioTolerance
);
// 如果宽高比匹配,进一步检查
if (matchesAspectRatio) {
// 检查软件标记是否与截图工具相关
const screenshotSoftwareKeywords = ['Screenshot', 'Snipping Tool', 'Screen Capture', 'Grab', 'Sketch'];
if (Software && screenshotSoftwareKeywords.some((keyword) => Software.includes(keyword))) {
return true;
}
// 检查文件大小(截图通常较小)
const fileSizeMB = file.size / (1024 * 1024);
if (fileSizeMB <= maxScreenshotSizeMB) {
return true;
}
} }
} }
// 无 EXIF 数据或不匹配时,通过宽高比的容差检测 // 无 EXIF 数据或不匹配时,通过宽高比的容差检测
const img: any = await getImageDimensions(file); const img: any = await getImageDimensions(file);
const imgAspectRatio = img.width / img.height; const imgAspectRatio = img.width / img.height;
const imgMatchesAspectRatio = commonAspectRatios.some( return commonAspectRatios.some(
(ratio) => Math.abs(ratio - imgAspectRatio) <= aspectRatioTolerance (ratio) => Math.abs(ratio - imgAspectRatio) <= aspectRatioTolerance
); );
return imgMatchesAspectRatio;
} catch (error) { } catch (error) {
console.error('判断截图时发生错误:', error); console.error('判断截图时发生错误:', error);
return false; return false;

View File

@@ -21,6 +21,8 @@
:disabled="predicting" :disabled="predicting"
:progress="progress" :progress="progress"
@remove="removeFile" @remove="removeFile"
@reject="rejectFile"
:openFileDialogOnClick="true"
accept="image/*" accept="image/*"
list-type="picture" list-type="picture"
method="post" method="post"
@@ -52,7 +54,6 @@ import i18n from "@/locales";
import {NSFWJS} from "nsfwjs"; import {NSFWJS} from "nsfwjs";
import {animePredictImagePro} from "@/utils/tfjs/anime_classifier_pro.ts"; import {animePredictImagePro} from "@/utils/tfjs/anime_classifier_pro.ts";
import {fnDetectFace} from "@/utils/tfjs/face_extraction.ts";
import {cocoSsdPredict} from "@/utils/tfjs/mobilenet.ts"; import {cocoSsdPredict} from "@/utils/tfjs/mobilenet.ts";
import {predictLandscape} from "@/utils/tfjs/landscape_recognition.ts"; import {predictLandscape} from "@/utils/tfjs/landscape_recognition.ts";
import {useRequest} from 'alova/client'; import {useRequest} from 'alova/client';
@@ -92,8 +93,9 @@ const progress: UploadProps['progress'] = {
/** /**
* 图片上传前的校验 * 图片上传前的校验
* @param file * @param file
* @param fileList
*/ */
async function beforeUpload(file: File) { async function beforeUpload(file: File, fileList: File[]) {
predicting.value = true; predicting.value = true;
upload.clearPredictResult(); upload.clearPredictResult();
progressPercent.value = 0; // 初始化进度条 progressPercent.value = 0; // 初始化进度条
@@ -132,16 +134,18 @@ async function beforeUpload(file: File) {
if (isNSFW) { if (isNSFW) {
message.error(i18n.global.t('comment.illegalImage')); message.error(i18n.global.t('comment.illegalImage'));
predicting.value = false;
progressPercent.value = 100; // 重置进度条 progressPercent.value = 100; // 重置进度条
progressStatus.value = 'exception'; // 异常状态 progressStatus.value = 'exception'; // 异常状态
fileList.pop(); // 清空文件列表
predicting.value = false;
return false; return false;
} }
// 提取 EXIF 数据 // 提取 EXIF 数据
const exifData = await extractAllExifData(file); const exifData = await extractAllExifData(file);
if (exifData) { if (exifData) {
upload.exifData = exifData; upload.predictResult.exif = exifData;
} }
// 判断是否为截图 // 判断是否为截图
@@ -158,14 +162,7 @@ async function beforeUpload(file: File) {
progressPercent.value = 100; // 直接完成 progressPercent.value = 100; // 直接完成
return true; return true;
} }
// 人脸检测
const faceImageData = await fnDetectFace(image);
if (faceImageData) {
upload.predictResult.hasFace = true;
predicting.value = false;
progressPercent.value = 100; // 直接完成
return true;
}
//目标检测和风景检测并行处理 //目标检测和风景检测并行处理
const [cocoResults, landscape] = await Promise.all([ const [cocoResults, landscape] = await Promise.all([
cocoSsdPredict(image), // 目标检测 cocoSsdPredict(image), // 目标检测
@@ -218,7 +215,6 @@ async function customUploadRequest(file: any) {
formData.append("data", JSON.stringify({ formData.append("data", JSON.stringify({
fileType: file.file.type, fileType: file.file.type,
...upload.predictResult, ...upload.predictResult,
exif: JSON.stringify(upload.exifData) || '',
})); }));
watch( watch(
() => uploading.value, () => uploading.value,
@@ -248,6 +244,14 @@ function cancelUpload() {
progressPercent.value = 0; // 重置进度条 progressPercent.value = 0; // 重置进度条
} }
/**
* 拒绝文件回调
* @param fileList
*/
function rejectFile(fileList: any) {
fileList.value.pop();
}
/** /**
* 删除文件 * 删除文件
* @param file * @param file