增强预览机制

This commit is contained in:
2026-03-13 23:58:52 +08:00
parent 33d2231bed
commit e76a1dff71
2 changed files with 81 additions and 16 deletions

View File

@@ -179,7 +179,15 @@ Future<Response> _getPhotoPreviewHandler(Request req) async {
return Response.notFound('Photo not found'); return Response.notFound('Photo not found');
} }
final file = await vips.generatePreview(photo.filePath); // 从查询参数获取宽度和高度
final w = int.tryParse(req.url.queryParameters['w'] ?? '');
final h = int.tryParse(req.url.queryParameters['h'] ?? '');
final file = await vips.generatePreview(
photo.filePath,
w: w,
h: h,
);
if (!await file.exists()) { if (!await file.exists()) {
return Response.notFound('File not found'); return Response.notFound('File not found');
@@ -206,7 +214,7 @@ Future<Response> _getPhotoPreviewHandler(Request req) async {
return Response.ok( return Response.ok(
streamController.stream, streamController.stream,
headers: { headers: {
'Content-Type': photo.mimeType, 'Content-Type': 'image/webp',
'Content-Length': file.statSync().size.toString(), 'Content-Length': file.statSync().size.toString(),
}, },
); );

View File

@@ -7,25 +7,82 @@ class Vips {
Vips({this.vipsExecuteFile}); Vips({this.vipsExecuteFile});
Future<File> generatePreview(String imgPath) async { /// 生成图片预览WebP 格式)
final imgOut = ///
"cache/preview/" + basename(imgPath).hashCode.toString() + ".webp"; /// [imgPath] 原图路径
/// [w] 预览图宽度(可选)
/// [h] 预览图高度(可选)
///
/// 如果同时指定 [w] 和 [h],图片将按比例缩放以适应指定尺寸
Future<File> generatePreview(
String imgPath, {
int? w,
int? h,
}) async {
// 生成缓存文件名:包含原图 hash 和尺寸信息
final baseName = basename(imgPath).hashCode.toString();
final sizeSuffix = _buildSizeSuffix(w, h);
final imgOut = "cache/preview/${baseName}${sizeSuffix}.webp";
final outFile = File(imgOut); final outFile = File(imgOut);
// 缓存命中:直接返回已存在的预览图
if (outFile.existsSync()) { if (outFile.existsSync()) {
return outFile; return outFile;
} }
final result = await Process.run("vips", [
// 确保缓存目录存在
await outFile.parent.create(recursive: true);
// 构建 vips 命令参数
if (w != null || h != null) {
final thumbnailArgs = <String>[
"thumbnail",
imgPath,
imgOut,
w?.toString() ?? "0",
"--height",
h?.toString() ?? "0",
];
final thumbnailResult = await Process.run("vips", thumbnailArgs);
if (thumbnailResult.exitCode != 0) {
print(thumbnailResult.stderr);
throw Exception("vips thumbnail failed!");
}
return outFile;
} else {
// 不缩放,直接转换为 WebP 格式
final args = <String>[
"webpsave", "webpsave",
imgPath, imgPath,
imgOut, imgOut,
"--Q", "--Q",
"80", "80",
"--effort",
"6",
"--smart-subsample", "--smart-subsample",
"--strip", ];
]);
final result = await Process.run("vips", args);
if (result.exitCode == 0) { if (result.exitCode == 0) {
return outFile; return outFile;
} }
print(result.stderr);
throw Exception("vips image transcode failed!"); throw Exception("vips image transcode failed!");
} }
} }
/// 构建尺寸后缀
///
/// 例如:
/// - w=200, h=150 => "_200x150"
/// - w=200, h=null => "_200x0"
/// - w=null, h=150 => "_0x150"
/// - w=null, h=null => ""
String _buildSizeSuffix(int? w, int? h) {
if (w == null && h == null) {
return "";
}
return "_${w ?? 0}x${h ?? 0}";
}
}