feat: convert heic uploads to jpeg
This commit is contained in:
parent
aa63415b1c
commit
7fd3feb77c
@ -15,8 +15,12 @@ const ALLOWED_UPLOAD_IMAGE_TYPES = new Set([
|
|||||||
"image/png",
|
"image/png",
|
||||||
"image/webp",
|
"image/webp",
|
||||||
"image/svg+xml",
|
"image/svg+xml",
|
||||||
|
"image/heic",
|
||||||
|
"image/heif",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const HEIC_UPLOAD_TYPES = new Set(["image/heic", "image/heif"]);
|
||||||
|
|
||||||
function normalizeUploadContentType(contentType: string, filename: string) {
|
function normalizeUploadContentType(contentType: string, filename: string) {
|
||||||
const normalized = contentType.trim().toLowerCase();
|
const normalized = contentType.trim().toLowerCase();
|
||||||
if (normalized && normalized !== "application/octet-stream") {
|
if (normalized && normalized !== "application/octet-stream") {
|
||||||
@ -29,11 +33,44 @@ function normalizeUploadContentType(contentType: string, filename: string) {
|
|||||||
function createUnsupportedImageTypeMessage(contentType: string, filename: string) {
|
function createUnsupportedImageTypeMessage(contentType: string, filename: string) {
|
||||||
const extension = filename.toLowerCase().split(".").pop() || "unknown";
|
const extension = filename.toLowerCase().split(".").pop() || "unknown";
|
||||||
if (contentType === "image/heic" || contentType === "image/heif" || extension === "heic" || extension === "heif") {
|
if (contentType === "image/heic" || contentType === "image/heif" || extension === "heic" || extension === "heif") {
|
||||||
return "暂不支持 HEIC/HEIF 图片上传,请在手机相册中导出为 JPG/PNG 后再上传。";
|
return "HEIC/HEIF 图片转码失败,请在手机相册中导出为 JPG/PNG 后再上传。";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "File type should be JPEG, PNG, WebP or SVG";
|
return "File type should be JPEG, PNG, WebP or SVG";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isHeicUpload(contentType: string, filename: string) {
|
||||||
|
const extension = filename.toLowerCase().split(".").pop() || "";
|
||||||
|
return (
|
||||||
|
HEIC_UPLOAD_TYPES.has(contentType) ||
|
||||||
|
extension === "heic" ||
|
||||||
|
extension === "heif"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toJpegFilename(filename: string) {
|
||||||
|
const trimmed = filename.trim() || `upload-${Date.now()}`;
|
||||||
|
if (/\.(heic|heif)$/i.test(trimmed)) {
|
||||||
|
return trimmed.replace(/\.(heic|heif)$/i, ".jpg");
|
||||||
|
}
|
||||||
|
if (/\.[a-z0-9]+$/i.test(trimmed)) {
|
||||||
|
return trimmed.replace(/\.[a-z0-9]+$/i, ".jpg");
|
||||||
|
}
|
||||||
|
return `${trimmed}.jpg`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function convertHeicToJpeg(buffer: Buffer): Promise<Buffer> {
|
||||||
|
const convert = (await import("heic-convert")).default;
|
||||||
|
const output = await convert({
|
||||||
|
buffer,
|
||||||
|
format: "JPEG",
|
||||||
|
quality: 0.92,
|
||||||
|
});
|
||||||
|
if (output instanceof ArrayBuffer) {
|
||||||
|
return Buffer.from(new Uint8Array(output));
|
||||||
|
}
|
||||||
|
return Buffer.from(output);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 使用Sharp智能优化图片(保持原格式)
|
* 使用Sharp智能优化图片(保持原格式)
|
||||||
* 策略:避免双重压缩,只在需要时优化
|
* 策略:避免双重压缩,只在需要时优化
|
||||||
@ -152,8 +189,13 @@ export async function POST(request: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get filename from formData since Blob doesn't have name property
|
// Get filename from formData since Blob doesn't have name property
|
||||||
const filename = (formData.get("file") as File).name;
|
const originalFilename = (formData.get("file") as File).name;
|
||||||
const contentType = normalizeUploadContentType(file.type, filename);
|
const originalContentType = normalizeUploadContentType(
|
||||||
|
file.type,
|
||||||
|
originalFilename
|
||||||
|
);
|
||||||
|
let filename = originalFilename;
|
||||||
|
let contentType = originalContentType;
|
||||||
const uploadPurpose = String(formData.get("purpose") || "");
|
const uploadPurpose = String(formData.get("purpose") || "");
|
||||||
const clientUploadId =
|
const clientUploadId =
|
||||||
String(formData.get("clientUploadId") || "").trim() || null;
|
String(formData.get("clientUploadId") || "").trim() || null;
|
||||||
@ -203,8 +245,42 @@ export async function POST(request: Request) {
|
|||||||
logger.debug("[Upload API] File buffer size:", fileBuffer.byteLength);
|
logger.debug("[Upload API] File buffer size:", fileBuffer.byteLength);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let uploadBuffer: Buffer = Buffer.from(fileBuffer);
|
||||||
|
let convertedFrom: string | null = null;
|
||||||
|
|
||||||
|
if (isHeicUpload(contentType, filename)) {
|
||||||
|
try {
|
||||||
|
const convertedBuffer = await convertHeicToJpeg(uploadBuffer);
|
||||||
|
logger.info("[Upload API] HEIC image converted to JPEG", {
|
||||||
|
clientUploadId,
|
||||||
|
originalContentType: contentType,
|
||||||
|
originalSize: uploadBuffer.length,
|
||||||
|
convertedSize: convertedBuffer.length,
|
||||||
|
});
|
||||||
|
uploadBuffer = convertedBuffer;
|
||||||
|
filename = toJpegFilename(filename);
|
||||||
|
convertedFrom = contentType;
|
||||||
|
contentType = "image/jpeg";
|
||||||
|
} catch (heicError) {
|
||||||
|
logger.warn("[Upload API] HEIC image conversion failed", {
|
||||||
|
clientUploadId,
|
||||||
|
contentType,
|
||||||
|
size: uploadBuffer.length,
|
||||||
|
error:
|
||||||
|
heicError instanceof Error ? heicError.message : String(heicError),
|
||||||
|
});
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: createUnsupportedImageTypeMessage(contentType, filename),
|
||||||
|
clientUploadId,
|
||||||
|
},
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (uploadPurpose === "agent-workflow-preview" && contentType !== "image/svg+xml") {
|
if (uploadPurpose === "agent-workflow-preview" && contentType !== "image/svg+xml") {
|
||||||
const preview = await createAgentWorkflowPreviewImageBuffer(Buffer.from(fileBuffer));
|
const preview = await createAgentWorkflowPreviewImageBuffer(uploadBuffer);
|
||||||
const previewResult = await uploadFile(
|
const previewResult = await uploadFile(
|
||||||
`${filename.replace(/\.[^.]+$/, "") || "agent-workflow-preview"}.webp`,
|
`${filename.replace(/\.[^.]+$/, "") || "agent-workflow-preview"}.webp`,
|
||||||
toArrayBuffer(preview.buffer)
|
toArrayBuffer(preview.buffer)
|
||||||
@ -231,6 +307,7 @@ export async function POST(request: Request) {
|
|||||||
previewSize: preview.buffer.length,
|
previewSize: preview.buffer.length,
|
||||||
purpose: uploadPurpose,
|
purpose: uploadPurpose,
|
||||||
clientUploadId,
|
clientUploadId,
|
||||||
|
convertedFrom,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
assetId = asset?.id ?? null;
|
assetId = asset?.id ?? null;
|
||||||
@ -253,7 +330,7 @@ export async function POST(request: Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 使用Sharp优化图片(保持原格式)
|
// ✅ 使用Sharp优化图片(保持原格式)
|
||||||
let optimizedBuffer: Buffer = Buffer.from(fileBuffer);
|
let optimizedBuffer: Buffer = uploadBuffer;
|
||||||
|
|
||||||
// ✅ 优化原图(保持JPG/PNG格式)
|
// ✅ 优化原图(保持JPG/PNG格式)
|
||||||
// WebP格式不优化(用于预览图,保持原样)
|
// WebP格式不优化(用于预览图,保持原样)
|
||||||
@ -297,6 +374,7 @@ export async function POST(request: Request) {
|
|||||||
pathname: data.pathname,
|
pathname: data.pathname,
|
||||||
size: optimizedBuffer.length,
|
size: optimizedBuffer.length,
|
||||||
clientUploadId,
|
clientUploadId,
|
||||||
|
convertedFrom,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
assetId = asset?.id ?? null;
|
assetId = asset?.id ?? null;
|
||||||
@ -309,6 +387,7 @@ export async function POST(request: Request) {
|
|||||||
...data,
|
...data,
|
||||||
assetId,
|
assetId,
|
||||||
clientUploadId,
|
clientUploadId,
|
||||||
|
convertedFrom,
|
||||||
});
|
});
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
logger.error("[Upload API] Upload failed:", _error);
|
logger.error("[Upload API] Upload failed:", _error);
|
||||||
|
|||||||
@ -3202,7 +3202,7 @@ export default function CanvasModel3DPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Input
|
<Input
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setWorldImageFile(e.target.files?.[0] || null)
|
setWorldImageFile(e.target.files?.[0] || null)
|
||||||
@ -3236,7 +3236,7 @@ export default function CanvasModel3DPage() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<Input
|
<Input
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
handleWorldImageSlotChange(
|
handleWorldImageSlotChange(
|
||||||
|
|||||||
@ -4799,7 +4799,7 @@ export function AgentPsdTemplateEditorDialog({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
if (!selectedImageLayer) return;
|
if (!selectedImageLayer) return;
|
||||||
|
|||||||
@ -759,7 +759,7 @@ export function AgentWorkflowMaterialPicker({
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
disabled={isReplacing || isUploadingNewImage || isLoading}
|
disabled={isReplacing || isUploadingNewImage || isLoading}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
@ -806,7 +806,7 @@ export function AgentWorkflowMaterialPicker({
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
disabled={isUploadingNewImage || Boolean(uploadingImageId) || isLoading}
|
disabled={isUploadingNewImage || Boolean(uploadingImageId) || isLoading}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
|
|||||||
@ -7452,7 +7452,7 @@ export function ImageNode({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<input
|
<input
|
||||||
accept="image/jpeg,image/png,image/webp"
|
accept="image/jpeg,image/png,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
data-canvas-no-drag="true"
|
data-canvas-no-drag="true"
|
||||||
data-local-edit-control="true"
|
data-local-edit-control="true"
|
||||||
|
|||||||
@ -1985,7 +1985,7 @@ export function ImageAnnotator({
|
|||||||
</p>
|
</p>
|
||||||
<Button variant="default">选择图片</Button>
|
<Button variant="default">选择图片</Button>
|
||||||
<input
|
<input
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={handleImageUpload}
|
onChange={handleImageUpload}
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
|
|||||||
@ -2184,7 +2184,7 @@ export function SceneFusionView({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={handleProductUpload}
|
onChange={handleProductUpload}
|
||||||
ref={productInputRef}
|
ref={productInputRef}
|
||||||
@ -2320,7 +2320,7 @@ export function SceneFusionView({
|
|||||||
<Plus className="h-3 w-3" /> 批量添加
|
<Plus className="h-3 w-3" /> 批量添加
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
multiple
|
multiple
|
||||||
onChange={handleSceneUpload}
|
onChange={handleSceneUpload}
|
||||||
@ -2484,7 +2484,7 @@ export function SceneFusionView({
|
|||||||
<Plus className="h-3 w-3" /> 批量添加
|
<Plus className="h-3 w-3" /> 批量添加
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
multiple
|
multiple
|
||||||
onChange={handleMultiProductUpload}
|
onChange={handleMultiProductUpload}
|
||||||
@ -2640,7 +2640,7 @@ export function SceneFusionView({
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={handleReferenceSceneUpload}
|
onChange={handleReferenceSceneUpload}
|
||||||
ref={referenceSceneInputRef}
|
ref={referenceSceneInputRef}
|
||||||
@ -2745,7 +2745,7 @@ export function SceneFusionView({
|
|||||||
<Plus className="h-3 w-3" /> 批量添加
|
<Plus className="h-3 w-3" /> 批量添加
|
||||||
</Button>
|
</Button>
|
||||||
<input
|
<input
|
||||||
accept="image/png,image/jpeg,image/webp"
|
accept="image/png,image/jpeg,image/webp,image/heic,image/heif,.heic,.heif"
|
||||||
className="hidden"
|
className="hidden"
|
||||||
multiple
|
multiple
|
||||||
onChange={handleBatchUpload}
|
onChange={handleBatchUpload}
|
||||||
|
|||||||
@ -183,7 +183,7 @@ DIRECT_UPLOAD_ABORT_INCOMPLETE_DAYS=2 pnpm storage:configure-upload-lifecycle
|
|||||||
|
|
||||||
默认只清理 `uploads/direct/` 前缀下 2 天未完成的分片;如使用自定义前缀,可设置 `DIRECT_UPLOAD_LIFECYCLE_PREFIX`。
|
默认只清理 `uploads/direct/` 前缀下 2 天未完成的分片;如使用自定义前缀,可设置 `DIRECT_UPLOAD_LIFECYCLE_PREFIX`。
|
||||||
|
|
||||||
当前上传格式支持 JPEG、PNG、WebP,管理员图标额外允许 SVG。HEIC/HEIF 不是可靠的 Web 展示格式,当前会明确提示用户转为 JPG/PNG;若未来要原生支持,应作为独立专项引入客户端或服务端转码,并评估前端包体、Sharp/libvips HEIF 支持和隐私成本。
|
当前上传格式支持 JPEG、PNG、WebP,管理员图标额外允许 SVG。HEIC/HEIF 上传会在服务端自动转为高质量 JPEG,避免把转码 WASM 放进前端主包;如果转码失败,会明确提示用户从手机相册导出 JPG/PNG 后再上传。
|
||||||
|
|
||||||
## 路由回归烟测
|
## 路由回归烟测
|
||||||
|
|
||||||
|
|||||||
@ -115,6 +115,7 @@
|
|||||||
"form-data": "^4.0.5",
|
"form-data": "^4.0.5",
|
||||||
"framer-motion": "^11.18.2",
|
"framer-motion": "^11.18.2",
|
||||||
"geist": "^1.3.1",
|
"geist": "^1.3.1",
|
||||||
|
"heic-convert": "^2.1.0",
|
||||||
"immer": "^11.1.3",
|
"immer": "^11.1.3",
|
||||||
"is-hotkey": "^0.2.0",
|
"is-hotkey": "^0.2.0",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
|
|||||||
38
pnpm-lock.yaml
generated
38
pnpm-lock.yaml
generated
@ -220,6 +220,9 @@ importers:
|
|||||||
geist:
|
geist:
|
||||||
specifier: ^1.3.1
|
specifier: ^1.3.1
|
||||||
version: 1.3.1(next@16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021))
|
version: 1.3.1(next@16.2.6(@opentelemetry/api@1.9.0)(@playwright/test@1.57.0)(react-dom@19.0.0-rc-45804af1-20241021(react@19.0.0-rc-45804af1-20241021))(react@19.0.0-rc-45804af1-20241021))
|
||||||
|
heic-convert:
|
||||||
|
specifier: ^2.1.0
|
||||||
|
version: 2.1.0
|
||||||
immer:
|
immer:
|
||||||
specifier: ^11.1.3
|
specifier: ^11.1.3
|
||||||
version: 11.1.3
|
version: 11.1.3
|
||||||
@ -4691,6 +4694,14 @@ packages:
|
|||||||
hastscript@9.0.1:
|
hastscript@9.0.1:
|
||||||
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
|
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
|
||||||
|
|
||||||
|
heic-convert@2.1.0:
|
||||||
|
resolution: {integrity: sha512-1qDuRvEHifTVAj3pFIgkqGgJIr0M3X7cxEPjEp0oG4mo8GFjq99DpCo8Eg3kg17Cy0MTjxpFdoBHOatj7ZVKtg==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
heic-decode@2.1.0:
|
||||||
|
resolution: {integrity: sha512-0fB3O3WMk38+PScbHLVp66jcNhsZ/ErtQ6u2lMYu/YxXgbBtl+oKOhGQHa4RpvE68k8IzbWkABzHnyAIjR758A==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
highlight.js@10.7.3:
|
highlight.js@10.7.3:
|
||||||
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
|
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
|
||||||
|
|
||||||
@ -4843,6 +4854,9 @@ packages:
|
|||||||
jose@6.1.3:
|
jose@6.1.3:
|
||||||
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
|
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
|
||||||
|
|
||||||
|
jpeg-js@0.4.4:
|
||||||
|
resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==}
|
||||||
|
|
||||||
js-cookie@3.0.7:
|
js-cookie@3.0.7:
|
||||||
resolution: {integrity: sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==}
|
resolution: {integrity: sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
@ -4929,6 +4943,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
|
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
|
||||||
engines: {node: '>= 0.6.3'}
|
engines: {node: '>= 0.6.3'}
|
||||||
|
|
||||||
|
libheif-js@1.19.8:
|
||||||
|
resolution: {integrity: sha512-vQJWusIxO7wavpON1dusciL8Go9jsIQ+EUrckauFYAiSTjcmLAsuJh3SszLpvkwPci3JcL41ek2n+LUZGFpPIQ==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
lie@3.3.0:
|
lie@3.3.0:
|
||||||
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
|
resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
|
||||||
|
|
||||||
@ -5658,6 +5676,10 @@ packages:
|
|||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
pngjs@6.0.0:
|
||||||
|
resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==}
|
||||||
|
engines: {node: '>=12.13.0'}
|
||||||
|
|
||||||
points-on-curve@0.2.0:
|
points-on-curve@0.2.0:
|
||||||
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
|
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
|
||||||
|
|
||||||
@ -11298,6 +11320,16 @@ snapshots:
|
|||||||
property-information: 7.0.0
|
property-information: 7.0.0
|
||||||
space-separated-tokens: 2.0.2
|
space-separated-tokens: 2.0.2
|
||||||
|
|
||||||
|
heic-convert@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
heic-decode: 2.1.0
|
||||||
|
jpeg-js: 0.4.4
|
||||||
|
pngjs: 6.0.0
|
||||||
|
|
||||||
|
heic-decode@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
libheif-js: 1.19.8
|
||||||
|
|
||||||
highlight.js@10.7.3: {}
|
highlight.js@10.7.3: {}
|
||||||
|
|
||||||
highlightjs-vue@1.0.0: {}
|
highlightjs-vue@1.0.0: {}
|
||||||
@ -11425,6 +11457,8 @@ snapshots:
|
|||||||
|
|
||||||
jose@6.1.3: {}
|
jose@6.1.3: {}
|
||||||
|
|
||||||
|
jpeg-js@0.4.4: {}
|
||||||
|
|
||||||
js-cookie@3.0.7: {}
|
js-cookie@3.0.7: {}
|
||||||
|
|
||||||
js-md4@0.3.2: {}
|
js-md4@0.3.2: {}
|
||||||
@ -11508,6 +11542,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
readable-stream: 2.3.8
|
readable-stream: 2.3.8
|
||||||
|
|
||||||
|
libheif-js@1.19.8: {}
|
||||||
|
|
||||||
lie@3.3.0:
|
lie@3.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
immediate: 3.0.6
|
immediate: 3.0.6
|
||||||
@ -12541,6 +12577,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
|
|
||||||
|
pngjs@6.0.0: {}
|
||||||
|
|
||||||
points-on-curve@0.2.0: {}
|
points-on-curve@0.2.0: {}
|
||||||
|
|
||||||
points-on-curve@1.0.1: {}
|
points-on-curve@1.0.1: {}
|
||||||
|
|||||||
23
types/heic-convert.d.ts
vendored
Normal file
23
types/heic-convert.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
declare module "heic-convert" {
|
||||||
|
type ConvertOptions = {
|
||||||
|
buffer: Buffer | ArrayBuffer | Uint8Array;
|
||||||
|
format: "JPEG" | "PNG";
|
||||||
|
quality?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ConvertAllOptions = ConvertOptions;
|
||||||
|
|
||||||
|
type ConvertibleImage = {
|
||||||
|
convert: () => Promise<ArrayBuffer | Uint8Array | Buffer>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function convert(
|
||||||
|
options: ConvertOptions
|
||||||
|
): Promise<ArrayBuffer | Uint8Array | Buffer>;
|
||||||
|
|
||||||
|
namespace convert {
|
||||||
|
function all(options: ConvertAllOptions): Promise<ConvertibleImage[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export = convert;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user