mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Add support for 3d models in assets and attachments
This commit is contained in:
@@ -232,6 +232,7 @@ public class SchemaMigration implements ApplicationRunner {
|
||||
WHEN media_type LIKE 'audio/%' THEN 'AUDIO'
|
||||
WHEN media_type LIKE 'video/%' THEN 'VIDEO'
|
||||
WHEN media_type LIKE 'image/%' THEN 'IMAGE'
|
||||
WHEN media_type LIKE 'model/%' THEN 'MODEL'
|
||||
WHEN media_type LIKE 'application/javascript%' THEN 'SCRIPT'
|
||||
WHEN media_type LIKE 'text/javascript%' THEN 'SCRIPT'
|
||||
ELSE COALESCE(asset_type, 'OTHER')
|
||||
@@ -248,7 +249,7 @@ public class SchemaMigration implements ApplicationRunner {
|
||||
SELECT id, name, preview, x, y, width, height, rotation, speed, muted, media_type,
|
||||
original_media_type, z_index, audio_volume, hidden
|
||||
FROM assets
|
||||
WHERE asset_type IN ('IMAGE', 'VIDEO', 'OTHER')
|
||||
WHERE asset_type IN ('IMAGE', 'VIDEO', 'MODEL', 'OTHER')
|
||||
"""
|
||||
);
|
||||
jdbcTemplate.execute(
|
||||
|
||||
@@ -6,6 +6,7 @@ public enum AssetType {
|
||||
IMAGE,
|
||||
VIDEO,
|
||||
AUDIO,
|
||||
MODEL,
|
||||
SCRIPT,
|
||||
OTHER;
|
||||
|
||||
@@ -24,6 +25,9 @@ public enum AssetType {
|
||||
if (normalized.startsWith("audio/")) {
|
||||
return AUDIO;
|
||||
}
|
||||
if (normalized.startsWith("model/")) {
|
||||
return MODEL;
|
||||
}
|
||||
if (normalized.startsWith("application/javascript") || normalized.startsWith("text/javascript")) {
|
||||
return SCRIPT;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,9 @@ public class AssetStorageService {
|
||||
Map.entry("audio/ogg", ".ogg"),
|
||||
Map.entry("audio/webm", ".webm"),
|
||||
Map.entry("audio/flac", ".flac"),
|
||||
Map.entry("model/gltf-binary", ".glb"),
|
||||
Map.entry("model/gltf+json", ".gltf"),
|
||||
Map.entry("model/obj", ".obj"),
|
||||
Map.entry("application/javascript", ".js"),
|
||||
Map.entry("text/javascript", ".js")
|
||||
);
|
||||
|
||||
@@ -155,6 +155,7 @@ public class ChannelDirectoryService {
|
||||
(asset) ->
|
||||
asset.getAssetType() == AssetType.IMAGE ||
|
||||
asset.getAssetType() == AssetType.VIDEO ||
|
||||
asset.getAssetType() == AssetType.MODEL ||
|
||||
asset.getAssetType() == AssetType.OTHER
|
||||
)
|
||||
.map(Asset::getId)
|
||||
@@ -635,7 +636,7 @@ public class ChannelDirectoryService {
|
||||
scriptAttachment.setFileId(attachmentFileId);
|
||||
scriptAttachment.setMediaType(attachmentContent.mediaType());
|
||||
scriptAttachment.setOriginalMediaType(attachmentContent.mediaType());
|
||||
scriptAttachment.setAssetType(AssetType.IMAGE);
|
||||
scriptAttachment.setAssetType(AssetType.fromMediaType(attachmentContent.mediaType(), attachmentContent.mediaType()));
|
||||
attachments.add(scriptAttachment);
|
||||
}
|
||||
if (!attachments.isEmpty()) {
|
||||
@@ -674,7 +675,8 @@ public class ChannelDirectoryService {
|
||||
}
|
||||
|
||||
private String storeScriptAttachmentFile(Asset asset, AssetContent attachmentContent) {
|
||||
ScriptAssetFile attachmentFile = new ScriptAssetFile(asset.getBroadcaster(), AssetType.IMAGE);
|
||||
AssetType assetType = AssetType.fromMediaType(attachmentContent.mediaType(), attachmentContent.mediaType());
|
||||
ScriptAssetFile attachmentFile = new ScriptAssetFile(asset.getBroadcaster(), assetType);
|
||||
attachmentFile.setMediaType(attachmentContent.mediaType());
|
||||
attachmentFile.setOriginalMediaType(attachmentContent.mediaType());
|
||||
try {
|
||||
@@ -981,8 +983,16 @@ public class ChannelDirectoryService {
|
||||
}
|
||||
|
||||
AssetType assetType = AssetType.fromMediaType(optimized.mediaType(), mediaType);
|
||||
if (assetType != AssetType.AUDIO && assetType != AssetType.IMAGE && assetType != AssetType.VIDEO) {
|
||||
throw new ResponseStatusException(BAD_REQUEST, "Only image, video, or audio attachments are supported.");
|
||||
if (
|
||||
assetType != AssetType.AUDIO &&
|
||||
assetType != AssetType.IMAGE &&
|
||||
assetType != AssetType.VIDEO &&
|
||||
assetType != AssetType.MODEL
|
||||
) {
|
||||
throw new ResponseStatusException(
|
||||
BAD_REQUEST,
|
||||
"Only image, video, audio, or 3D model attachments are supported."
|
||||
);
|
||||
}
|
||||
|
||||
String safeName = Optional.ofNullable(file.getOriginalFilename())
|
||||
@@ -1173,6 +1183,7 @@ public class ChannelDirectoryService {
|
||||
(asset) ->
|
||||
asset.getAssetType() == AssetType.IMAGE ||
|
||||
asset.getAssetType() == AssetType.VIDEO ||
|
||||
asset.getAssetType() == AssetType.MODEL ||
|
||||
asset.getAssetType() == AssetType.OTHER
|
||||
)
|
||||
.map(Asset::getId)
|
||||
@@ -1220,7 +1231,9 @@ public class ChannelDirectoryService {
|
||||
private int nextZIndex(String broadcaster) {
|
||||
return (
|
||||
visualAssetRepository
|
||||
.findByIdIn(assetsWithType(normalize(broadcaster), AssetType.IMAGE, AssetType.VIDEO, AssetType.OTHER))
|
||||
.findByIdIn(
|
||||
assetsWithType(normalize(broadcaster), AssetType.IMAGE, AssetType.VIDEO, AssetType.MODEL, AssetType.OTHER)
|
||||
)
|
||||
.stream()
|
||||
.mapToInt(VisualAsset::getZIndex)
|
||||
.max()
|
||||
@@ -1374,6 +1387,7 @@ public class ChannelDirectoryService {
|
||||
if (
|
||||
asset.getAssetType() != AssetType.VIDEO &&
|
||||
asset.getAssetType() != AssetType.IMAGE &&
|
||||
asset.getAssetType() != AssetType.MODEL &&
|
||||
asset.getAssetType() != AssetType.OTHER
|
||||
) {
|
||||
return Optional.empty();
|
||||
|
||||
@@ -28,6 +28,22 @@ public class MarketplaceScriptSeedLoader {
|
||||
private static final String ATTACHMENTS_DIR = "attachments";
|
||||
private static final String DEFAULT_SOURCE_MEDIA_TYPE = "application/javascript";
|
||||
private static final String DEFAULT_LOGO_MEDIA_TYPE = "image/png";
|
||||
private static final java.util.Map<String, String> ATTACHMENT_MEDIA_TYPES = java.util.Map.ofEntries(
|
||||
java.util.Map.entry("png", "image/png"),
|
||||
java.util.Map.entry("jpg", "image/jpeg"),
|
||||
java.util.Map.entry("jpeg", "image/jpeg"),
|
||||
java.util.Map.entry("gif", "image/gif"),
|
||||
java.util.Map.entry("webp", "image/webp"),
|
||||
java.util.Map.entry("mp4", "video/mp4"),
|
||||
java.util.Map.entry("webm", "video/webm"),
|
||||
java.util.Map.entry("mov", "video/quicktime"),
|
||||
java.util.Map.entry("mp3", "audio/mpeg"),
|
||||
java.util.Map.entry("wav", "audio/wav"),
|
||||
java.util.Map.entry("ogg", "audio/ogg"),
|
||||
java.util.Map.entry("glb", "model/gltf-binary"),
|
||||
java.util.Map.entry("gltf", "model/gltf+json"),
|
||||
java.util.Map.entry("obj", "model/obj")
|
||||
);
|
||||
|
||||
private final List<SeedScript> scripts;
|
||||
|
||||
@@ -175,7 +191,7 @@ public class MarketplaceScriptSeedLoader {
|
||||
logger.warn("Duplicate marketplace attachment name {}", name);
|
||||
return Optional.empty();
|
||||
}
|
||||
String mediaType = Files.probeContentType(attachment);
|
||||
String mediaType = detectAttachmentMediaType(attachment);
|
||||
attachments.add(
|
||||
new SeedAttachment(
|
||||
name,
|
||||
@@ -203,6 +219,32 @@ public class MarketplaceScriptSeedLoader {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String detectAttachmentMediaType(Path attachment) {
|
||||
try {
|
||||
String mediaType = Files.probeContentType(attachment);
|
||||
if (
|
||||
mediaType != null &&
|
||||
!mediaType.isBlank() &&
|
||||
!"application/octet-stream".equals(mediaType) &&
|
||||
!"text/plain".equals(mediaType)
|
||||
) {
|
||||
return mediaType;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
logger.warn("Failed to detect media type for {}", attachment, ex);
|
||||
}
|
||||
String filename = attachment.getFileName().toString().toLowerCase(Locale.ROOT);
|
||||
int dot = filename.lastIndexOf('.');
|
||||
if (dot > -1 && dot < filename.length() - 1) {
|
||||
String extension = filename.substring(dot + 1);
|
||||
String mapped = ATTACHMENT_MEDIA_TYPES.get(extension);
|
||||
if (mapped != null) {
|
||||
return mapped;
|
||||
}
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
private String normalizeBroadcaster(String broadcaster) {
|
||||
if (broadcaster == null || broadcaster.isBlank()) {
|
||||
return "System";
|
||||
|
||||
@@ -28,6 +28,9 @@ public class MediaDetectionService {
|
||||
Map.entry("mp3", "audio/mpeg"),
|
||||
Map.entry("wav", "audio/wav"),
|
||||
Map.entry("ogg", "audio/ogg"),
|
||||
Map.entry("glb", "model/gltf-binary"),
|
||||
Map.entry("gltf", "model/gltf+json"),
|
||||
Map.entry("obj", "model/obj"),
|
||||
Map.entry("js", "application/javascript"),
|
||||
Map.entry("mjs", "text/javascript")
|
||||
);
|
||||
|
||||
@@ -73,6 +73,10 @@ public class MediaOptimizationService {
|
||||
return new OptimizedAsset(bytes, mediaType, 0, 0, null);
|
||||
}
|
||||
|
||||
if (mediaType.startsWith("model/")) {
|
||||
return new OptimizedAsset(bytes, mediaType, 0, 0, null);
|
||||
}
|
||||
|
||||
if (mediaType.startsWith("application/javascript") || mediaType.startsWith("text/javascript")) {
|
||||
return new OptimizedAsset(bytes, mediaType, 0, 0, null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user