🐛 fixed the logic of judging screenshots
This commit is contained in:
@@ -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,
|
||||||
};
|
};
|
||||||
|
@@ -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;
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user