mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
Use filesystem over uris
This commit is contained in:
@@ -57,6 +57,7 @@ import org.w3c.dom.NodeList;
|
|||||||
public class ChannelDirectoryService {
|
public class ChannelDirectoryService {
|
||||||
private static final int MIN_GIF_DELAY_MS = 20;
|
private static final int MIN_GIF_DELAY_MS = 20;
|
||||||
private static final String PREVIEW_MEDIA_TYPE = "image/png";
|
private static final String PREVIEW_MEDIA_TYPE = "image/png";
|
||||||
|
private static final Path ASSET_ROOT = Paths.get("assets");
|
||||||
private static final Path PREVIEW_ROOT = Paths.get("previews");
|
private static final Path PREVIEW_ROOT = Paths.get("previews");
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ChannelDirectoryService.class);
|
private static final Logger logger = LoggerFactory.getLogger(ChannelDirectoryService.class);
|
||||||
private final ChannelRepository channelRepository;
|
private final ChannelRepository channelRepository;
|
||||||
@@ -147,12 +148,12 @@ public class ChannelDirectoryService {
|
|||||||
.filter(s -> !s.isBlank())
|
.filter(s -> !s.isBlank())
|
||||||
.orElse("Asset " + System.currentTimeMillis());
|
.orElse("Asset " + System.currentTimeMillis());
|
||||||
|
|
||||||
String dataUrl = "data:" + optimized.mediaType() + ";base64," + Base64.getEncoder().encodeToString(optimized.bytes());
|
|
||||||
double width = optimized.width() > 0 ? optimized.width() : (optimized.mediaType().startsWith("audio/") ? 400 : 640);
|
double width = optimized.width() > 0 ? optimized.width() : (optimized.mediaType().startsWith("audio/") ? 400 : 640);
|
||||||
double height = optimized.height() > 0 ? optimized.height() : (optimized.mediaType().startsWith("audio/") ? 80 : 360);
|
double height = optimized.height() > 0 ? optimized.height() : (optimized.mediaType().startsWith("audio/") ? 80 : 360);
|
||||||
Asset asset = new Asset(channel.getBroadcaster(), name, dataUrl, width, height);
|
Asset asset = new Asset(channel.getBroadcaster(), name, "", width, height);
|
||||||
asset.setOriginalMediaType(mediaType);
|
asset.setOriginalMediaType(mediaType);
|
||||||
asset.setMediaType(optimized.mediaType());
|
asset.setMediaType(optimized.mediaType());
|
||||||
|
asset.setUrl(storeAsset(channel.getBroadcaster(), asset.getId(), optimized.bytes(), optimized.mediaType()));
|
||||||
asset.setPreview(storePreview(channel.getBroadcaster(), asset.getId(), optimized.previewBytes()));
|
asset.setPreview(storePreview(channel.getBroadcaster(), asset.getId(), optimized.previewBytes()));
|
||||||
asset.setSpeed(1.0);
|
asset.setSpeed(1.0);
|
||||||
asset.setMuted(optimized.mediaType().startsWith("video/"));
|
asset.setMuted(optimized.mediaType().startsWith("video/"));
|
||||||
@@ -243,6 +244,7 @@ public class ChannelDirectoryService {
|
|||||||
return assetRepository.findById(assetId)
|
return assetRepository.findById(assetId)
|
||||||
.filter(asset -> normalized.equals(asset.getBroadcaster()))
|
.filter(asset -> normalized.equals(asset.getBroadcaster()))
|
||||||
.map(asset -> {
|
.map(asset -> {
|
||||||
|
deleteAssetFile(asset.getUrl());
|
||||||
deletePreviewFile(asset.getPreview());
|
deletePreviewFile(asset.getPreview());
|
||||||
assetRepository.delete(asset);
|
assetRepository.delete(asset);
|
||||||
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.deleted(broadcaster, assetId));
|
messagingTemplate.convertAndSend(topicFor(broadcaster), AssetEvent.deleted(broadcaster, assetId));
|
||||||
@@ -324,7 +326,8 @@ public class ChannelDirectoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Optional<AssetContent> decodeAssetData(Asset asset) {
|
private Optional<AssetContent> decodeAssetData(Asset asset) {
|
||||||
return decodeDataUrl(asset.getUrl())
|
return loadAssetFile(asset.getUrl(), asset.getMediaType())
|
||||||
|
.or(() -> decodeDataUrl(asset.getUrl()))
|
||||||
.or(() -> {
|
.or(() -> {
|
||||||
logger.warn("Unable to decode asset data for {}", asset.getId());
|
logger.warn("Unable to decode asset data for {}", asset.getId());
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@@ -373,6 +376,86 @@ public class ChannelDirectoryService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<AssetContent> loadAssetFile(String assetPath, String mediaType) {
|
||||||
|
if (assetPath == null || assetPath.isBlank()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Path path = Paths.get(assetPath);
|
||||||
|
if (!Files.exists(path)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String resolvedMediaType = mediaType;
|
||||||
|
if (resolvedMediaType == null || resolvedMediaType.isBlank()) {
|
||||||
|
resolvedMediaType = Files.probeContentType(path);
|
||||||
|
}
|
||||||
|
if (resolvedMediaType == null || resolvedMediaType.isBlank()) {
|
||||||
|
resolvedMediaType = "application/octet-stream";
|
||||||
|
}
|
||||||
|
return Optional.of(new AssetContent(Files.readAllBytes(path), resolvedMediaType));
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Unable to read asset from {}", assetPath, e);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
} catch (InvalidPathException e) {
|
||||||
|
logger.debug("Asset path {} is not a file path; skipping", assetPath);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String storeAsset(String broadcaster, String assetId, byte[] assetBytes, String mediaType) throws IOException {
|
||||||
|
if (assetBytes == null || assetBytes.length == 0) {
|
||||||
|
throw new IOException("Asset content is empty");
|
||||||
|
}
|
||||||
|
Path directory = ASSET_ROOT.resolve(normalize(broadcaster));
|
||||||
|
Files.createDirectories(directory);
|
||||||
|
String extension = extensionForMediaType(mediaType);
|
||||||
|
Path assetFile = directory.resolve(assetId + extension);
|
||||||
|
Files.write(assetFile, assetBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
|
||||||
|
return assetFile.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteAssetFile(String assetPath) {
|
||||||
|
if (assetPath == null || assetPath.isBlank()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Path path = Paths.get(assetPath);
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(path);
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Unable to delete asset file {}", assetPath, e);
|
||||||
|
}
|
||||||
|
} catch (InvalidPathException e) {
|
||||||
|
logger.debug("Asset value {} is not a file path; nothing to delete", assetPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extensionForMediaType(String mediaType) {
|
||||||
|
if (mediaType == null || mediaType.isBlank()) {
|
||||||
|
return ".bin";
|
||||||
|
}
|
||||||
|
return switch (mediaType.toLowerCase(Locale.ROOT)) {
|
||||||
|
case "image/png" -> ".png";
|
||||||
|
case "image/jpeg", "image/jpg" -> ".jpg";
|
||||||
|
case "image/gif" -> ".gif";
|
||||||
|
case "video/mp4" -> ".mp4";
|
||||||
|
case "video/webm" -> ".webm";
|
||||||
|
case "video/quicktime" -> ".mov";
|
||||||
|
case "audio/mpeg" -> ".mp3";
|
||||||
|
case "audio/wav" -> ".wav";
|
||||||
|
case "audio/ogg" -> ".ogg";
|
||||||
|
default -> {
|
||||||
|
int slash = mediaType.indexOf('/');
|
||||||
|
if (slash > -1 && slash < mediaType.length() - 1) {
|
||||||
|
yield "." + mediaType.substring(slash + 1).replaceAll("[^a-z0-9.+-]", "");
|
||||||
|
}
|
||||||
|
yield ".bin";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private String storePreview(String broadcaster, String assetId, byte[] previewBytes) throws IOException {
|
private String storePreview(String broadcaster, String assetId, byte[] previewBytes) throws IOException {
|
||||||
if (previewBytes == null || previewBytes.length == 0) {
|
if (previewBytes == null || previewBytes.length == 0) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user