修复bug,第一次导出缩略图,后面有了高清图后再次导出应该覆盖

按原图、中图和缩略图对本地图片资源分级,始终优先选择高清变体。

增量导出仅复用满足清晰度要求的图片,重新探测旧缩略图并在高清图出现后替换;仅缩略图降级时显示提示。
This commit is contained in:
Nanin
2026-08-08 20:53:40 +08:00
parent 666d8896ee
commit 291c82f0e2
9 changed files with 185 additions and 32 deletions
+29 -17
View File
@@ -7,6 +7,7 @@ import { execFile } from 'child_process'
import { Worker } from 'worker_threads'
import ffmpegStaticPath from 'ffmpeg-static'
import type { ImageDecoderSource, ImageDecoderStatus } from '../shared/image-decryption'
import { imageFileQuality, imageQualityRank } from '../shared/image-quality'
import { loadSettings } from './services/settings-store'
import { Wcdb4Client } from './wcdb4-client'
@@ -208,6 +209,13 @@ function isThumbnailName(fileName) {
return /(?:_t(?:_m)?|_thumb|\.thumb|_b|_w|_c)\.dat$/i.test(lower)
}
function imageQualityRank(fileName) {
const lower = fileName.toLowerCase()
if (/(?:_t(?:_m)?|_thumb|\.thumb|_b|_w|_c)\.dat$/i.test(lower)) return 1
if (/(?:_hd|\.hd|_h_m|_h|\.h)\.dat$/i.test(lower)) return 3
return 2
}
function buildPreferredDatNames(baseName) {
const base = normalizeDatBase(baseName)
if (!base) return []
@@ -436,8 +444,8 @@ function collectCandidates(datPath, allowThumbnail) {
.map((name) => path.join(directory, name))
.filter((candidate) => fs.existsSync(candidate))
.sort((left, right) => {
const thumbnailOrder = Number(isThumbnailName(path.basename(left))) - Number(isThumbnailName(path.basename(right)))
return thumbnailOrder || fs.statSync(right).size - fs.statSync(left).size
const qualityOrder = imageQualityRank(path.basename(right)) - imageQualityRank(path.basename(left))
return qualityOrder || fs.statSync(right).size - fs.statSync(left).size
})
return Array.from(new Set(candidates.concat(siblings)))
}
@@ -1824,17 +1832,17 @@ export class ImageDecryptService {
.sort((left, right) => right.size - left.size)
const thumbnail = toSized(
paths.filter((candidate) => this.isThumbnailName(basename(candidate)))
paths.filter((candidate) => imageFileQuality(candidate) === 'thumbnail')
)
if (preferThumbnail && thumbnail[0]) return thumbnail[0].candidate
const nonThumb = toSized(
paths.filter((candidate) => !this.isThumbnailName(basename(candidate)))
)
if (nonThumb[0]) return nonThumb[0].candidate
if (!allowThumbnail) return null
const existing = toSized(paths)
return existing[0]?.candidate || null
const allowed = toSized(paths)
.filter((entry) => allowThumbnail || imageFileQuality(entry.candidate) !== 'thumbnail')
.sort(
(left, right) =>
imageQualityRank(imageFileQuality(right.candidate)) -
imageQualityRank(imageFileQuality(left.candidate)) || right.size - left.size
)
return allowed[0]?.candidate || null
}
private async getLargestExistingPathAsync(
@@ -1858,17 +1866,21 @@ export class ImageDecryptService {
.sort((left, right) => right.size - left.size)
if (preferThumbnail) {
const thumbnail = sized.find((entry) => this.isThumbnailName(basename(entry.candidate)))
const thumbnail = sized.find((entry) => imageFileQuality(entry.candidate) === 'thumbnail')
if (thumbnail) return thumbnail.candidate
}
const nonThumbnail = sized.find((entry) => !this.isThumbnailName(basename(entry.candidate)))
if (nonThumbnail) return nonThumbnail.candidate
return allowThumbnail ? sized[0]?.candidate || null : null
const allowed = sized
.filter((entry) => allowThumbnail || imageFileQuality(entry.candidate) !== 'thumbnail')
.sort(
(left, right) =>
imageQualityRank(imageFileQuality(right.candidate)) -
imageQualityRank(imageFileQuality(left.candidate)) || right.size - left.size
)
return allowed[0]?.candidate || null
}
private isThumbnailName(fileName: string): boolean {
const lower = fileName.toLowerCase()
return /(?:_t(?:_m)?|_thumb|\.thumb|_b|_w|_c)\.dat$/i.test(lower)
return imageFileQuality(fileName) === 'thumbnail'
}
isThumbnailFile(filePath: string): boolean {