@ -16,6 +16,11 @@ function getExtensionFromMime(mimeType: string): string {
"video/webm" : "webm" ,
"video/quicktime" : "mov" ,
"model/gltf-binary" : "glb" ,
"model/gltf+json" : "gltf" ,
"model/obj" : "obj" ,
"model/vnd.usdz+zip" : "usdz" ,
"model/fbx" : "fbx" ,
"model/stl" : "stl" ,
} ;
// Check explicit mapping first
@ -43,6 +48,25 @@ function isHttpUrl(str: string): boolean {
return str . startsWith ( "http://" ) || str . startsWith ( "https://" ) ;
}
// Known file extensions for 3D models and common media
const KNOWN_3D_EXTENSIONS = new Set ( [ "glb" , "gltf" , "obj" , "fbx" , "usdz" , "stl" , "ply" , "zip" ] ) ;
const KNOWN_MEDIA_EXTENSIONS = new Set ( [ "png" , "jpg" , "jpeg" , "gif" , "webp" , "svg" , "mp4" , "webm" , "mov" ] ) ;
// Helper to extract a recognized file extension from a URL pathname
function getExtensionFromUrl ( url : string ) : string | null {
try {
const urlObj = new URL ( url ) ;
const pathname = urlObj . pathname ;
const lastDot = pathname . lastIndexOf ( "." ) ;
if ( lastDot === - 1 || lastDot === pathname . length - 1 ) return null ;
const ext = pathname . substring ( lastDot + 1 ) . toLowerCase ( ) ;
if ( KNOWN_3D_EXTENSIONS . has ( ext ) || KNOWN_MEDIA_EXTENSIONS . has ( ext ) ) return ext ;
return null ;
} catch {
return null ;
}
}
// Helper to compute MD5 hash of buffer content
function computeContentHash ( buffer : Buffer ) : string {
return crypto . createHash ( "md5" ) . update ( buffer ) . digest ( "hex" ) ;
@ -173,10 +197,18 @@ export async function POST(request: NextRequest) {
}
const rawSaveContentType = response . headers . get ( "content-type" ) ;
const contentType = ( rawSaveContentType && ( rawSaveContentType . startsWith ( "video/" ) || rawSaveContentType . startsWith ( "image/" ) || rawSaveContentType . startsWith ( "model/" ) ) )
? rawSaveContentType
: ( isModel ? "model/gltf-binary" : isVideo ? "video/mp4" : "image/png" ) ;
extension = getExtensionFromMime ( contentType ) ;
// For 3D models, try extracting extension from URL first (most reliable with CDN URLs)
const urlExtension = isModel ? getExtensionFromUrl ( content ) : null ;
if ( urlExtension ) {
extension = urlExtension ;
} else {
const contentType = ( rawSaveContentType && ( rawSaveContentType . startsWith ( "video/" ) || rawSaveContentType . startsWith ( "image/" ) || rawSaveContentType . startsWith ( "model/" ) ) )
? rawSaveContentType
: ( isModel ? "model/gltf-binary" : isVideo ? "video/mp4" : "image/png" ) ;
extension = getExtensionFromMime ( contentType ) ;
}
const arrayBuffer = await response . arrayBuffer ( ) ;
@ -210,7 +242,11 @@ export async function POST(request: NextRequest) {
// Safety net: if extension resolved to "bin" but we know the media type, use correct extension
if ( extension === "bin" ) {
extension = isModel ? "glb" : isVideo ? "mp4" : "png" ;
if ( isModel && isHttpUrl ( content ) ) {
extension = getExtensionFromUrl ( content ) || "glb" ;
} else {
extension = isModel ? "glb" : isVideo ? "mp4" : "png" ;
}
}
// Compute content hash for deduplication