fix desktop smzdm webp image upload
Frontend CI / Frontend (push) Successful in 3m13s
Desktop Client Build / Resolve Build Metadata (push) Successful in 25s
Desktop Client Build / Build Desktop Client (push) Successful in 21m47s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 25s
Frontend CI / Frontend (push) Successful in 3m13s
Desktop Client Build / Resolve Build Metadata (push) Successful in 25s
Desktop Client Build / Build Desktop Client (push) Successful in 21m47s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 25s
This commit is contained in:
@@ -3,6 +3,7 @@ import { Buffer } from 'node:buffer'
|
||||
import { nativeImage } from 'electron'
|
||||
|
||||
import { fetchDesktopApiURL, resolveDesktopApiURL } from '../transport/api-client'
|
||||
import { STANDARD_ACCEPT_LANGUAGES, STANDARD_USER_AGENT } from '../user-agent'
|
||||
import { adapterFetch, mergeAbortSignals, timeoutSignal, type AdapterFetchOptions } from './common'
|
||||
|
||||
export interface ImageAssetBlob {
|
||||
@@ -12,6 +13,11 @@ export interface ImageAssetBlob {
|
||||
fileName: string
|
||||
}
|
||||
|
||||
export interface RawImageAssetBlob {
|
||||
blob: Blob
|
||||
fileName: string
|
||||
}
|
||||
|
||||
export interface CropRect {
|
||||
x: number
|
||||
y: number
|
||||
@@ -24,6 +30,7 @@ export type AssetImageFormat = 'png' | 'jpg' | 'jpeg'
|
||||
export interface FetchImageAssetBlobOptions extends AdapterFetchOptions {
|
||||
preferredFormat?: AssetImageFormat
|
||||
passthroughTypes?: string[]
|
||||
headers?: HeadersInit
|
||||
}
|
||||
|
||||
const defaultPassthroughImageTypes = new Set(['image/png', 'image/jpeg', 'image/jpg', 'image/gif'])
|
||||
@@ -66,6 +73,10 @@ export function buildAssetURLCandidates(
|
||||
return [trimmed]
|
||||
}
|
||||
|
||||
if (/^\/\//.test(trimmed)) {
|
||||
return [`https:${trimmed}`]
|
||||
}
|
||||
|
||||
const candidates: string[] = []
|
||||
const pushCandidate = (value: string) => {
|
||||
if (!candidates.includes(value)) {
|
||||
@@ -113,30 +124,43 @@ function fileNameForImageType(sourceType: string): string {
|
||||
if (sourceType === 'image/jpeg' || sourceType === 'image/jpg') {
|
||||
return 'image.jpg'
|
||||
}
|
||||
if (sourceType === 'image/webp') {
|
||||
return 'image.webp'
|
||||
}
|
||||
return 'image.png'
|
||||
}
|
||||
|
||||
export async function fetchImageAssetBlob(
|
||||
function imageFetchHeaders(options: Pick<FetchImageAssetBlobOptions, 'headers'>): Headers {
|
||||
const headers = new Headers(options.headers)
|
||||
if (!headers.has('accept')) {
|
||||
headers.set('accept', 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8')
|
||||
}
|
||||
if (!headers.has('accept-language')) {
|
||||
headers.set('accept-language', STANDARD_ACCEPT_LANGUAGES)
|
||||
}
|
||||
if (!headers.has('user-agent')) {
|
||||
headers.set('user-agent', STANDARD_USER_AGENT)
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
export async function fetchRawImageAssetBlob(
|
||||
sourceUrl: string,
|
||||
options: FetchImageAssetBlobOptions = {},
|
||||
): Promise<ImageAssetBlob | null> {
|
||||
const passthroughImageTypes = new Set(
|
||||
(options.passthroughTypes ?? [...defaultPassthroughImageTypes]).map((type) =>
|
||||
type.toLowerCase(),
|
||||
),
|
||||
)
|
||||
|
||||
): Promise<RawImageAssetBlob | null> {
|
||||
for (const candidate of buildAssetURLCandidates(sourceUrl, {
|
||||
preferredFormat: options.preferredFormat,
|
||||
})) {
|
||||
const headers = imageFetchHeaders(options)
|
||||
const response = await (
|
||||
shouldUseDesktopAssetFetch(candidate)
|
||||
? fetchDesktopApiURL(candidate, {
|
||||
signal: mergeAbortSignals([options.signal, timeoutSignal(options.timeoutMs ?? 10_000)]),
|
||||
headers,
|
||||
})
|
||||
: adapterFetch(
|
||||
candidate,
|
||||
{},
|
||||
{ headers },
|
||||
{ timeoutMs: options.timeoutMs ?? 10_000, signal: options.signal },
|
||||
)
|
||||
).catch(() => null)
|
||||
@@ -149,33 +173,60 @@ export async function fetchImageAssetBlob(
|
||||
continue
|
||||
}
|
||||
|
||||
const image = nativeImage.createFromBuffer(Buffer.from(await sourceBlob.arrayBuffer()))
|
||||
if (image.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
|
||||
const size = image.getSize()
|
||||
const sourceType = sourceBlob.type.toLowerCase()
|
||||
if (passthroughImageTypes.has(sourceType)) {
|
||||
return {
|
||||
blob: sourceBlob,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
fileName: fileNameForImageType(sourceType),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
blob: new Blob([new Uint8Array(image.toPNG())], { type: 'image/png' }),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
fileName: 'image.png',
|
||||
blob: sourceBlob,
|
||||
fileName: fileNameForImageType(sourceBlob.type.toLowerCase()),
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export async function normalizeImageAssetBlob(
|
||||
sourceBlob: Blob,
|
||||
options: Pick<FetchImageAssetBlobOptions, 'passthroughTypes'> = {},
|
||||
): Promise<ImageAssetBlob | null> {
|
||||
if (!sourceBlob.type.startsWith('image/')) {
|
||||
return null
|
||||
}
|
||||
|
||||
const image = nativeImage.createFromBuffer(Buffer.from(await sourceBlob.arrayBuffer()))
|
||||
if (image.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
const size = image.getSize()
|
||||
const passthroughImageTypes = new Set(
|
||||
(options.passthroughTypes ?? [...defaultPassthroughImageTypes]).map((type) =>
|
||||
type.toLowerCase(),
|
||||
),
|
||||
)
|
||||
const sourceType = sourceBlob.type.toLowerCase()
|
||||
if (passthroughImageTypes.has(sourceType)) {
|
||||
return {
|
||||
blob: sourceBlob,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
fileName: fileNameForImageType(sourceType),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
blob: new Blob([new Uint8Array(image.toPNG())], { type: 'image/png' }),
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
fileName: 'image.png',
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchImageAssetBlob(
|
||||
sourceUrl: string,
|
||||
options: FetchImageAssetBlobOptions = {},
|
||||
): Promise<ImageAssetBlob | null> {
|
||||
const raw = await fetchRawImageAssetBlob(sourceUrl, options)
|
||||
return raw ? await normalizeImageAssetBlob(raw.blob, options) : null
|
||||
}
|
||||
|
||||
export function getCenteredCropRect(width: number, height: number, ratio: number): CropRect {
|
||||
let cropWidth = width
|
||||
let cropHeight = cropWidth / ratio
|
||||
|
||||
Reference in New Issue
Block a user