diff --git a/LICENSE b/LICENSE index 8fa4be5..3b04d77 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Imgfloat +Copyright (c) 2025 Andreas Krühlmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index dab640b..6528d7a 100644 --- a/Makefile +++ b/Makefile @@ -6,11 +6,23 @@ IMGFLOAT_DB_PATH ?= ./imgfloat.db IMGFLOAT_ASSETS_PATH ?= ./assets IMGFLOAT_PREVIEWS_PATH ?= ./previews +IMGFLOAT_MAX_SPEED ?= 4.0 +IMGFLOAT_MIN_AUDIO_SPEED ?= 0.1 +IMGFLOAT_MAX_AUDIO_SPEED ?= 4.0 +IMGFLOAT_MIN_AUDIO_PITCH ?= 0.5 +IMGFLOAT_MAX_AUDIO_PITCH ?= 2.0 +IMGFLOAT_MAX_AUDIO_VOLUME ?= 2.0 SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE ?= 10MB SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE ?= 10MB RUNTIME_ENV = IMGFLOAT_ASSETS_PATH=$(IMGFLOAT_ASSETS_PATH) \ IMGFLOAT_PREVIEWS_PATH=$(IMGFLOAT_PREVIEWS_PATH) \ IMGFLOAT_DB_PATH=$(IMGFLOAT_DB_PATH) \ + IMGFLOAT_MAX_SPEED=$(IMGFLOAT_MAX_SPEED) \ + IMGFLOAT_MIN_AUDIO_SPEED=$(IMGFLOAT_MIN_AUDIO_SPEED) \ + IMGFLOAT_MAX_AUDIO_SPEED=$(IMGFLOAT_MAX_AUDIO_SPEED) \ + IMGFLOAT_MIN_AUDIO_PITCH=$(IMGFLOAT_MIN_AUDIO_PITCH) \ + IMGFLOAT_MAX_AUDIO_PITCH=$(IMGFLOAT_MAX_AUDIO_PITCH) \ + IMGFLOAT_MAX_AUDIO_VOLUME=$(IMGFLOAT_MAX_AUDIO_VOLUME) \ SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE=$(SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE) \ SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE=$(SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE) WATCHDIR = ./src/main diff --git a/src/main/java/dev/kruhlmann/imgfloat/config/SystemEnvironmentValidator.java b/src/main/java/dev/kruhlmann/imgfloat/config/SystemEnvironmentValidator.java index 3a7dcff..317ed7e 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/config/SystemEnvironmentValidator.java +++ b/src/main/java/dev/kruhlmann/imgfloat/config/SystemEnvironmentValidator.java @@ -28,6 +28,18 @@ public class SystemEnvironmentValidator { private String previewsPath; @Value("${IMGFLOAT_DB_PATH}") private String dbPath; + @Value("${IMGFLOAT_MAX_SPEED}") + private double maxSpeed; + @Value("${IMGFLOAT_MIN_AUDIO_SPEED}") + private double minAudioSpeed; + @Value("${IMGFLOAT_MAX_AUDIO_SPEED}") + private double maxAudioSpeed; + @Value("${IMGFLOAT_MIN_AUDIO_PITCH}") + private double minAudioPitch; + @Value("${IMGFLOAT_MAX_AUDIO_PITCH}") + private double maxAudioPitch; + @Value("${IMGFLOAT_MAX_AUDIO_VOLUME}") + private double maxAudioVolume; private long maxUploadBytes; private long maxRequestBytes; @@ -38,8 +50,14 @@ public class SystemEnvironmentValidator { maxUploadBytes = DataSize.parse(springMaxFileSize).toBytes(); maxRequestBytes = DataSize.parse(springMaxRequestSize).toBytes(); - checkLong(maxUploadBytes, "SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE", missing); - checkLong(maxRequestBytes, "SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE", missing); + checkUnsignedNumeric(maxUploadBytes, "SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE", missing); + checkUnsignedNumeric(maxRequestBytes, "SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE", missing); + checkUnsignedNumeric(maxSpeed, "IMGFLOAT_MAX_SPEED", missing);; + checkUnsignedNumeric(minAudioSpeed, "IMGFLOAT_MIN_AUDIO_SPEED", missing);; + checkUnsignedNumeric(maxAudioSpeed, "IMGFLOAT_MAX_AUDIO_SPEED", missing);; + checkUnsignedNumeric(minAudioPitch, "IMGFLOAT_MIN_AUDIO_PITCH", missing);; + checkUnsignedNumeric(maxAudioPitch, "IMGFLOAT_MAX_AUDIO_PITCH", missing);; + checkUnsignedNumeric(maxAudioVolume, "IMGFLOAT_MAX_AUDIO_VOLUME", missing);; checkString(twitchClientId, "TWITCH_CLIENT_ID", missing); checkString(dbPath, "IMGFLOAT_DB_PATH", missing); checkString(twitchClientSecret, "TWITCH_CLIENT_SECRET", missing); @@ -57,15 +75,18 @@ public class SystemEnvironmentValidator { log.info(" - TWITCH_CLIENT_ID: {}", redact(twitchClientId)); log.info(" - TWITCH_CLIENT_SECRET: {}", redact(twitchClientSecret)); log.info(" - SPRING_SERVLET_MULTIPART_MAX_FILE_SIZE: {} ({} bytes)", - springMaxFileSize, - maxUploadBytes - ); + springMaxFileSize, maxUploadBytes); log.info(" - SPRING_SERVLET_MULTIPART_MAX_REQUEST_SIZE: {} ({} bytes)", - springMaxRequestSize, - maxRequestBytes - ); + springMaxRequestSize, maxRequestBytes); log.info(" - IMGFLOAT_ASSETS_PATH: {}", assetsPath); log.info(" - IMGFLOAT_PREVIEWS_PATH: {}", previewsPath); + log.info(" - IMGFLOAT_DB_PATH: {}", dbPath); + log.info(" - IMGFLOAT_MAX_SPEED: {}", maxSpeed); + log.info(" - IMGFLOAT_MIN_AUDIO_SPEED: {}", minAudioSpeed); + log.info(" - IMGFLOAT_MAX_AUDIO_SPEED: {}", maxAudioSpeed); + log.info(" - IMGFLOAT_MIN_AUDIO_PITCH: {}", minAudioPitch); + log.info(" - IMGFLOAT_MAX_AUDIO_PITCH: {}", maxAudioPitch); + log.info(" - IMGFLOAT_MAX_AUDIO_VOLUME: {}", maxAudioVolume); } private void checkString(String value, String name, StringBuilder missing) { @@ -74,44 +95,15 @@ public class SystemEnvironmentValidator { } } - private void checkLong(Long value, String name, StringBuilder missing) { - if (value == null || value <= 0) { - missing.append(" - ").append(name).append("\n"); + private void checkUnsignedNumeric(T value, String name, StringBuilder missing) { + if (value == null || value.doubleValue() <= 0) { + missing.append(" - ").append(name).append('\n'); } } - private String formatBytes(long bytes) { - if (bytes < 1024) return bytes + " B"; - - double kb = bytes / 1024.0; - if (kb < 1024) return String.format("%.2f KB", kb); - - double mb = kb / 1024.0; - if (mb < 1024) return String.format("%.2f MB", mb); - - double gb = mb / 1024.0; - return String.format("%.2f GB", gb); - } - private String redact(String value) { if (!StringUtils.hasText(value)) return "(missing)"; if (value.length() <= 6) return "******"; return value.substring(0, 2) + "****" + value.substring(value.length() - 2); } - - private long parseSizeToBytes(String value) { - if (value == null) return -1; - - String v = value.trim().toUpperCase(Locale.ROOT); - - try { - if (v.endsWith("GB")) return Long.parseLong(v.replace("GB", "")) * 1024 * 1024 * 1024; - if (v.endsWith("MB")) return Long.parseLong(v.replace("MB", "")) * 1024 * 1024; - if (v.endsWith("KB")) return Long.parseLong(v.replace("KB", "")) * 1024; - if (v.endsWith("B")) return Long.parseLong(v.replace("B", "")); - return Long.parseLong(v); - } catch (NumberFormatException e) { - return -1; - } - } } diff --git a/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java b/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java index 2364127..4d8af79 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java +++ b/src/main/java/dev/kruhlmann/imgfloat/controller/ViewController.java @@ -5,6 +5,7 @@ import dev.kruhlmann.imgfloat.service.VersionService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -22,9 +23,31 @@ public class ViewController { @Autowired private long uploadLimitBytes; - public ViewController(ChannelDirectoryService channelDirectoryService, VersionService versionService) { + private double maxSpeed; + private double minAudioSpeed; + private double maxAudioSpeed; + private double minAudioPitch; + private double maxAudioPitch; + private double maxAudioVolume; + + public ViewController( + ChannelDirectoryService channelDirectoryService, + VersionService versionService, + @Value("${IMGFLOAT_MAX_SPEED}") double maxSpeed, + @Value("${IMGFLOAT_MIN_AUDIO_SPEED}") double minAudioSpeed, + @Value("${IMGFLOAT_MAX_AUDIO_SPEED}") double maxAudioSpeed, + @Value("${IMGFLOAT_MIN_AUDIO_PITCH}") double minAudioPitch, + @Value("${IMGFLOAT_MAX_AUDIO_PITCH}") double maxAudioPitch, + @Value("${IMGFLOAT_MAX_AUDIO_VOLUME}") double maxAudioVolume + ) { this.channelDirectoryService = channelDirectoryService; this.versionService = versionService; + this.maxSpeed = maxSpeed; + this.minAudioSpeed = minAudioSpeed; + this.maxAudioSpeed = maxAudioSpeed; + this.minAudioPitch = minAudioPitch; + this.maxAudioPitch = maxAudioPitch; + this.maxAudioVolume = maxAudioVolume; } @org.springframework.web.bind.annotation.GetMapping("/") @@ -62,6 +85,13 @@ public class ViewController { model.addAttribute("username", login); model.addAttribute("uploadLimitBytes", uploadLimitBytes); + model.addAttribute("maxSpeed", maxSpeed); + model.addAttribute("minAudioSpeed", minAudioSpeed); + model.addAttribute("maxAudioSpeed", maxAudioSpeed); + model.addAttribute("minAudioPitch", minAudioPitch); + model.addAttribute("maxAudioPitch", maxAudioPitch); + model.addAttribute("maxAudioVolume", maxAudioVolume); + return "admin"; } diff --git a/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java b/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java index 5ec0816..91dcfc5 100644 --- a/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java +++ b/src/main/java/dev/kruhlmann/imgfloat/service/ChannelDirectoryService.java @@ -35,12 +35,6 @@ import static org.springframework.http.HttpStatus.PAYLOAD_TOO_LARGE; @Service public class ChannelDirectoryService { private static final Logger logger = LoggerFactory.getLogger(ChannelDirectoryService.class); - private static final double MAX_SPEED = 4.0; - private static final double MIN_AUDIO_SPEED = 0.1; - private static final double MAX_AUDIO_SPEED = 4.0; - private static final double MIN_AUDIO_PITCH = 0.5; - private static final double MAX_AUDIO_PITCH = 2.0; - private static final double MAX_AUDIO_VOLUME = 1.0; private static final Pattern SAFE_FILENAME = Pattern.compile("[^a-zA-Z0-9._ -]"); private final ChannelRepository channelRepository; @@ -53,13 +47,26 @@ public class ChannelDirectoryService { @Autowired private long uploadLimitBytes; + private double maxSpeed; + private double minAudioSpeed; + private double maxAudioSpeed; + private double minAudioPitch; + private double maxAudioPitch; + private double maxAudioVolume; + public ChannelDirectoryService( ChannelRepository channelRepository, AssetRepository assetRepository, SimpMessagingTemplate messagingTemplate, AssetStorageService assetStorageService, MediaDetectionService mediaDetectionService, - MediaOptimizationService mediaOptimizationService + MediaOptimizationService mediaOptimizationService, + @Value("${IMGFLOAT_MAX_SPEED}") double maxSpeed, + @Value("${IMGFLOAT_MIN_AUDIO_SPEED}") double minAudioSpeed, + @Value("${IMGFLOAT_MAX_AUDIO_SPEED}") double maxAudioSpeed, + @Value("${IMGFLOAT_MIN_AUDIO_PITCH}") double minAudioPitch, + @Value("${IMGFLOAT_MAX_AUDIO_PITCH}") double maxAudioPitch, + @Value("${IMGFLOAT_MAX_AUDIO_VOLUME}") double maxAudioVolume ) { this.channelRepository = channelRepository; this.assetRepository = assetRepository; @@ -67,6 +74,12 @@ public class ChannelDirectoryService { this.assetStorageService = assetStorageService; this.mediaDetectionService = mediaDetectionService; this.mediaOptimizationService = mediaOptimizationService; + this.maxSpeed = maxSpeed; + this.minAudioSpeed = minAudioSpeed; + this.maxAudioSpeed = maxAudioSpeed; + this.minAudioPitch = minAudioPitch; + this.maxAudioPitch = maxAudioPitch; + this.maxAudioVolume = maxAudioVolume; } @@ -244,17 +257,17 @@ public class ChannelDirectoryService { private void validateTransform(TransformRequest req) { if (req.getWidth() <= 0) throw new ResponseStatusException(BAD_REQUEST, "Width must be > 0"); if (req.getHeight() <= 0) throw new ResponseStatusException(BAD_REQUEST, "Height must be > 0"); - if (req.getSpeed() != null && (req.getSpeed() < 0 || req.getSpeed() > MAX_SPEED)) - throw new ResponseStatusException(BAD_REQUEST, "Speed must be between 0 and " + MAX_SPEED); + if (req.getSpeed() != null && (req.getSpeed() < 0 || req.getSpeed() > maxSpeed)) + throw new ResponseStatusException(BAD_REQUEST, "Speed must be between 0 and " + maxSpeed); if (req.getZIndex() != null && req.getZIndex() < 1) throw new ResponseStatusException(BAD_REQUEST, "zIndex must be >= 1"); if (req.getAudioDelayMillis() != null && req.getAudioDelayMillis() < 0) throw new ResponseStatusException(BAD_REQUEST, "Audio delay >= 0"); - if (req.getAudioSpeed() != null && (req.getAudioSpeed() < MIN_AUDIO_SPEED || req.getAudioSpeed() > MAX_AUDIO_SPEED)) + if (req.getAudioSpeed() != null && (req.getAudioSpeed() < minAudioSpeed || req.getAudioSpeed() > maxAudioSpeed)) throw new ResponseStatusException(BAD_REQUEST, "Audio speed out of range"); - if (req.getAudioPitch() != null && (req.getAudioPitch() < MIN_AUDIO_PITCH || req.getAudioPitch() > MAX_AUDIO_PITCH)) + if (req.getAudioPitch() != null && (req.getAudioPitch() < minAudioPitch || req.getAudioPitch() > maxAudioPitch)) throw new ResponseStatusException(BAD_REQUEST, "Audio pitch out of range"); - if (req.getAudioVolume() != null && (req.getAudioVolume() < 0 || req.getAudioVolume() > MAX_AUDIO_VOLUME)) + if (req.getAudioVolume() != null && (req.getAudioVolume() < 0 || req.getAudioVolume() > maxAudioVolume)) throw new ResponseStatusException(BAD_REQUEST, "Audio volume out of range"); } diff --git a/src/main/resources/static/js/admin.js b/src/main/resources/static/js/admin.js index 6410586..540e030 100644 --- a/src/main/resources/static/js/admin.js +++ b/src/main/resources/static/js/admin.js @@ -22,8 +22,8 @@ let interactionState = null; let lastSizeInputChanged = null; const HANDLE_SIZE = 10; const ROTATE_HANDLE_OFFSET = 32; -const MAX_VOLUME = 2; -const VOLUME_SLIDER_MAX = 200; +const MAX_VOLUME = adminInputRestrictions.MAX_AUDIO_VOLUME; +const VOLUME_SLIDER_MAX = adminInputRestrictions.MAX_AUDIO_VOLUME * 100; const VOLUME_CURVE_STRENGTH = -0.6; const pendingTransformSaves = new Map(); const KEYBOARD_NUDGE_STEP = 5; @@ -2023,8 +2023,8 @@ function uploadAsset(file = null) { showToast('Choose an image, GIF, video, or audio file to upload.', 'info'); return; } - if (selectedFile.size > upload_limit_bytes) { - showToast(`File is too large. Maximum upload size is ${upload_limit_bytes / 1024 / 1024} MB.`, 'error'); + if (selectedFile.size > adminInputRestrictions.UPLOAD_MAX_BYTES) { + showToast(`File is too large. Maximum upload size is ${adminInputRestrictions.UPLOAD_MAX_BYTES / 1024 / 1024} MB.`, 'error'); return; } diff --git a/src/main/resources/templates/admin.html b/src/main/resources/templates/admin.html index 5620b7d..ae4aa27 100644 --- a/src/main/resources/templates/admin.html +++ b/src/main/resources/templates/admin.html @@ -5,7 +5,6 @@ Imgfloat Admin - @@ -14,7 +13,7 @@
-

Admin

+

CHANNEL ADMIN

@@ -208,7 +207,15 @@ diff --git a/src/main/resources/templates/dashboard.html b/src/main/resources/templates/dashboard.html index f94d1d4..85211e2 100644 --- a/src/main/resources/templates/dashboard.html +++ b/src/main/resources/templates/dashboard.html @@ -69,7 +69,7 @@
-

Admins

+

Channel Admins

Users who can currently modify your overlay.

    @@ -97,7 +97,7 @@
  • channel

    -

    Admin access

    +

    Channel admin access

    Open