Files
upload-gltf/lib/prepare-git-assets.ts
T
2026-04-28 16:02:15 +02:00

142 lines
4.3 KiB
TypeScript

import { extname } from 'path'
import { compressTextureBuffer } from '@/lib/texture-compression'
import { classifyAssetCategory } from '@/lib/asset-classification'
import { normalizeTextureFilename } from '@/lib/asset-naming'
import { TEXTURE_EXTENSIONS } from '@/lib/constants'
import { getModelAssetPath } from '@/lib/model-paths'
import type { ParsedFile, PreparedAssetSummary, PushFile } from '@/lib/types'
interface PrepareGitAssetsParams {
folderName: string
parsedFiles: ParsedFile[]
}
interface PrepareGitAssetsResult {
filesToPush: PushFile[]
modelFilename: string
assetSummaries: PreparedAssetSummary[]
compressed: boolean
compressionError?: string
}
function getTextureFilenameMap(parsedFiles: ParsedFile[]) {
const filenameMap = new Map<string, string>()
const normalizedGroups = new Map<string, Array<{ original: string; normalized: string }>>()
for (const file of parsedFiles) {
const ext = extname(file.filename).toLowerCase()
if (!TEXTURE_EXTENSIONS.has(ext)) continue
const normalizedFilename = normalizeTextureFilename(file.filename)
if (!normalizedFilename) continue
const normalizedKey = normalizedFilename.toLowerCase()
const group = normalizedGroups.get(normalizedKey) || []
group.push({ original: file.filename, normalized: normalizedFilename })
normalizedGroups.set(normalizedKey, group)
}
for (const group of normalizedGroups.values()) {
if (group.length > 1) continue
const [{ original, normalized }] = group
filenameMap.set(original.toLowerCase(), normalized)
}
return filenameMap
}
function getReferencedFilename(uri: string) {
const cleanUri = decodeURIComponent(uri.split(/[?#]/)[0] || '')
return cleanUri.split(/[\\/]/).pop()?.toLowerCase()
}
function rewriteGltfUris(value: unknown, filenameMap: Map<string, string>): unknown {
if (Array.isArray(value)) {
return value.map((entry) => rewriteGltfUris(entry, filenameMap))
}
if (!value || typeof value !== 'object') return value
const rewritten: Record<string, unknown> = {}
for (const [key, entry] of Object.entries(value)) {
if (key === 'uri' && typeof entry === 'string') {
const filename = getReferencedFilename(entry)
rewritten[key] = filename ? filenameMap.get(filename) || entry : entry
continue
}
rewritten[key] = rewriteGltfUris(entry, filenameMap)
}
return rewritten
}
function prepareModelBuffer(buffer: Buffer, filenameMap: Map<string, string>) {
if (filenameMap.size === 0) return buffer
const parsed: unknown = JSON.parse(buffer.toString('utf-8'))
return Buffer.from(JSON.stringify(rewriteGltfUris(parsed, filenameMap), null, 2), 'utf-8')
}
export async function prepareGitAssets({
folderName,
parsedFiles,
}: PrepareGitAssetsParams): Promise<PrepareGitAssetsResult> {
const filesToPush: PushFile[] = []
const assetSummaries: PreparedAssetSummary[] = []
let modelFilename = ''
let compressed = false
let compressionError: string | undefined
const textureFilenameMap = getTextureFilenameMap(parsedFiles)
for (const pf of parsedFiles) {
let content = pf.buffer
let filename = pf.filename
if (pf.isModel) {
content = prepareModelBuffer(pf.buffer, textureFilenameMap)
modelFilename = pf.filename
assetSummaries.push({
filename,
kind: 'model',
compressed: false,
})
} else {
filename = textureFilenameMap.get(pf.filename.toLowerCase()) || pf.filename
const categoryFilename = textureFilenameMap.get(pf.filename.toLowerCase()) || normalizeTextureFilename(pf.filename) || pf.filename
const category = classifyAssetCategory(categoryFilename)
const textureResult = await compressTextureBuffer(filename, pf.buffer)
content = textureResult.buffer
compressed ||= textureResult.compressed
if (textureResult.error && !compressionError) {
compressionError = textureResult.error
}
assetSummaries.push({
filename,
kind: category === 'assets' ? 'asset' : 'texture',
category,
compressed: textureResult.compressed,
})
}
filesToPush.push({
path: getModelAssetPath(folderName, filename),
contentBase64: content.toString('base64'),
})
}
return {
filesToPush,
modelFilename,
assetSummaries,
compressed,
compressionError,
}
}