Add support for GIFs and Music

This commit is contained in:
Martin Prokoph
2024-06-20 21:32:06 +02:00
parent 948c7f47d1
commit 64dfcd8a87
13 changed files with 205 additions and 64 deletions

View File

@@ -21,8 +21,10 @@ public class PictureSignConfig extends MidnightConfig {
@Entry(category = advanced) public static boolean debug = false;
@Entry(min = 1, max = 10, isSlider = true, category = advanced) public static int maxThreads = 4;
@Entry(min = 0, max = 2048, isSlider = true, category = general) public static int signRenderDistance = 64;
@Entry(category = general) public static boolean safeMode = true;
@Entry(category = general) public static List<String> safeProviders = Lists.newArrayList("https://i.imgur.com/", "https://i.ibb.co/", "https://pictshare.net/", "https://iili.io/", "https://vimeo.com/", "https://yewtu.be/");
@Entry(category = advanced) public static boolean safeMode = true;
@Entry(category = advanced) public static List<String> safeProviders = Lists.newArrayList("https://i.imgur.com/", "https://i.ibb.co/", "https://pictshare.net/", "https://iili.io/", "https://media1.tenor.com/");
@Entry(category = advanced) public static List<String> safeGifProviders = Lists.newArrayList("https://i.imgur.com/", "https://media1.tenor.com/");
@Entry(category = advanced) public static List<String> safeMultimediaProviders = Lists.newArrayList("https://vimeo.com/", "https://yewtu.be/");
@Entry(category = general) public static String invidiousInstance = "yt.oelrichsgarcia.de";
@Comment(category = general) public static Comment ebeWarning;
@Entry(category = advanced) public static MissingImageMode missingImageMode = MissingImageMode.BLACK;

View File

@@ -1,6 +1,6 @@
package eu.midnightdust.picturesign.mixin;
import eu.midnightdust.picturesign.util.VideoHandler;
import eu.midnightdust.picturesign.util.MediaHandler;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.block.entity.BlockEntityType;
@@ -10,7 +10,6 @@ import net.minecraft.util.math.BlockPos;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import static eu.midnightdust.picturesign.PictureSignClient.MOD_ID;
import static eu.midnightdust.picturesign.PictureSignClient.id;
@Mixin(value = SignBlockEntity.class, priority = 1100)
@@ -24,8 +23,8 @@ public abstract class MixinSignBlockEntity extends BlockEntity {
public void markRemoved() {
Identifier videoId = id(pos.getX() + "_" + pos.getY() + "_" + pos.getZ() + "_f");
Identifier videoId2 = id(pos.getX() + "_" + pos.getY() + "_" + pos.getZ() + "_b");
VideoHandler.closePlayer(videoId);
VideoHandler.closePlayer(videoId2);
MediaHandler.closePlayer(videoId);
MediaHandler.closePlayer(videoId2);
super.markRemoved();
}
}

View File

@@ -7,6 +7,7 @@ import net.minecraft.block.entity.SignBlockEntity;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.*;
import net.minecraft.client.render.block.entity.BlockEntityRenderer;
import net.minecraft.client.render.block.entity.BlockEntityRendererFactory;
import net.minecraft.client.render.block.entity.SignBlockEntityRenderer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.math.BlockPos;
@@ -20,11 +21,16 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(SignBlockEntityRenderer.class)
public abstract class MixinSignBlockEntityRenderer implements BlockEntityRenderer<SignBlockEntity> {
@Unique private static final MinecraftClient client = MinecraftClient.getInstance();
@Unique PictureSignRenderer psRenderer = new PictureSignRenderer();
@Unique private PictureSignRenderer psRenderer;
@Inject(at = @At("TAIL"), method = "<init>")
public void ps$onInit(BlockEntityRendererFactory.Context ctx, CallbackInfo ci) {
psRenderer = new PictureSignRenderer();
}
@Inject(at = @At("HEAD"), method = "render")
public void ps$onRender(SignBlockEntity sign, float f, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int light, int overlay, CallbackInfo ci) {
if (PictureSignConfig.enabled) {
if (PictureSignConfig.enabled && psRenderer != null) {
if (PictureSignType.isNotOfType(sign, PictureSignType.NONE, true)) psRenderer.render(sign, matrixStack, light, overlay, true);
if (PictureSignType.isNotOfType(sign, PictureSignType.NONE, false)) psRenderer.render(sign, matrixStack, light, overlay, false);
}

View File

@@ -21,10 +21,6 @@ import static eu.midnightdust.picturesign.PictureSignClient.id;
public class PictureSignRenderer {
private boolean isSafeUrl;
private boolean isDeactivated = false;
private boolean playbackStarted = false;
private Identifier videoId;
private VideoHandler videoHandler;
public void render(SignBlockEntity signBlockEntity, MatrixStack matrixStack, int light, int overlay, boolean front) {
PictureSignType type = PictureSignType.getType(signBlockEntity, front);
@@ -42,6 +38,7 @@ public class PictureSignRenderer {
url = "https://" + url;
}
if (type == PictureSignType.PICTURE && !url.contains(".png") && !url.contains(".jpg") && !url.contains(".jpeg")) return;
if (type == PictureSignType.GIF && !url.contains(".gif")) return;
if (PictureSignConfig.safeMode) {
isSafeUrl = false;
String finalUrl = url;
@@ -59,25 +56,30 @@ public class PictureSignRenderer {
World world = signBlockEntity.getWorld();
BlockPos pos = signBlockEntity.getPos();
String videoSuffix = front ? "_f" : "_b";
if (videoId == null) videoId = id(pos.getX() + "_" + pos.getY() + "_" + pos.getZ()+videoSuffix);
if (videoHandler == null) videoHandler = new VideoHandler(videoId);
Identifier videoId = id(pos.getX() + "_" + pos.getY() + "_" + pos.getZ() + videoSuffix);
MediaHandler mediaHandler = null;
GIFHandler gifHandler = null;
if (PictureSignClient.hasWaterMedia) {
if (type.isVideo || type.isAudio) mediaHandler = MediaHandler.getOrCreate(videoId);
if (type == PictureSignType.GIF) gifHandler = GIFHandler.getOrCreate(videoId);
}
if (world != null && ((world.getBlockState(pos.down()).getBlock().equals(Blocks.REDSTONE_TORCH) || world.getBlockState(pos.down()).getBlock().equals(Blocks.REDSTONE_WALL_TORCH))
&& world.getBlockState(pos.down()).get(Properties.LIT).equals(false)
|| (world.getBlockState(pos.up()).getBlock().equals(Blocks.REDSTONE_TORCH) || world.getBlockState(pos.up()).getBlock().equals(Blocks.REDSTONE_WALL_TORCH))
&& world.getBlockState(pos.up()).get(Properties.LIT).equals(false)))
{
if (PictureSignClient.hasWaterMedia && videoHandler.isWorking() && !videoHandler.isStopped()) {
videoHandler.stop();
if (mediaHandler != null && mediaHandler.isWorking() && !mediaHandler.isStopped()) {
mediaHandler.stop();
}
isDeactivated = true;
PictureURLUtils.cachedJsonData.remove(url);
return;
}
else if (isDeactivated) {
if (PictureSignClient.hasWaterMedia && videoHandler.isWorking() && videoHandler.isStopped())
videoHandler.restart();
isDeactivated = false;
else if (mediaHandler != null && mediaHandler.isDeactivated) {
if (mediaHandler.isWorking() && mediaHandler.isStopped())
mediaHandler.restart();
}
String lastLine = signBlockEntity.getText(front).getMessage(3, false).getString();
@@ -121,26 +123,34 @@ public class PictureSignRenderer {
data = PictureDownloader.getInstance().getPicture(url);
if (data == null || data.identifier == null) return;
}
else if (type.isVideo) {
else if (mediaHandler != null) {
try {
if (type.isLooped && !videoHandler.hasMedia() && !playbackStarted) {
videoHandler.play(url);
videoHandler.setRepeat(true);
}
else if (!videoHandler.hasMedia() && !playbackStarted) {
videoHandler.play(url);
if (!mediaHandler.hasMedia() && !mediaHandler.playbackStarted) {
mediaHandler.play(url, type.isVideo);
if (type.isLooped && !mediaHandler.hasMedia() && !mediaHandler.playbackStarted)
mediaHandler.setRepeat(true);
}
} catch (MalformedURLException e) {
PictureSignClient.LOGGER.error(e);
return;
}
if (info != null && info.start() > 0 && videoHandler.getTime() < info.start()) videoHandler.setTime(info.start());
if (info != null && info.end() > 0 && videoHandler.getTime() >= info.end() && !playbackStarted) videoHandler.stop();
if (info != null && info.start() > 0 && mediaHandler.getTime() < info.start()) mediaHandler.setTime(info.start());
if (info != null && info.end() > 0 && mediaHandler.getTime() >= info.end() && !mediaHandler.playbackStarted) mediaHandler.stop();
}
else if (type == PictureSignType.GIF) {
try {
if (!gifHandler.hasMedia() && !gifHandler.playbackStarted) {
gifHandler.play(url);
}
} catch (MalformedURLException e) {
PictureSignClient.LOGGER.error(e);
return;
}
}
else return;
if (videoId != null && !playbackStarted) playbackStarted = true;
if (type.isAudio) return;
float xOffset = 0.0F;
float zOffset = 0.0F;
@@ -184,14 +194,24 @@ public class PictureSignRenderer {
if (type == PictureSignType.PICTURE) {
texture = data.identifier;
}
else if (type.isVideo)
if (videoHandler.isWorking()) RenderSystem.setShaderTexture(0, videoHandler.getTexture());
else if (type.isVideo && mediaHandler != null) {
if (mediaHandler.isWorking()) RenderSystem.setShaderTexture(0, mediaHandler.getTexture());
else {
var id = VideoHandler.getMissingTexture();
var id = MediaHandler.getMissingTexture();
if (id == null) return;
texture = id;
}
}
else if (gifHandler != null) {
if (gifHandler.isWorking()) RenderSystem.setShaderTexture(0, gifHandler.getTexture());
else {
var id = MediaHandler.getMissingTexture();
if (id == null) return;
texture = id;
}
}
else return;
if (texture != null) RenderSystem.setShaderTexture(0, texture);
if (PictureSignConfig.translucency) RenderSystem.enableBlend();

View File

@@ -274,8 +274,14 @@ public class PictureSignHelperScreen extends Screen {
super.render(context, mouseX, mouseY, delta);
if (this.client == null) return;
DiffuseLighting.disableGuiDepthLighting();
context.drawTextWithShadow(textRenderer, Text.of("Link" +
(PictureSignConfig.safeMode ? (type.equals(PictureSignType.PICTURE) ? " (imgur.com/imgbb.com/iili.io/pictshare.net)" : " (youtube.com/youtu.be/vimeo.com)") : "")),
String enabledWebsites = "";
if (PictureSignConfig.safeMode) {
if (type.equals(PictureSignType.PICTURE)) enabledWebsites = " (imgur.com/imgbb.com/iili.io/pictshare.net)";
else if (type.equals(PictureSignType.GIF)) enabledWebsites = " (imgur.com/tenor.com)";
else if (type.isVideo) enabledWebsites = " (youtube.com/youtu.be/vimeo.com)";
else if (type.isAudio) enabledWebsites = " (youtube.com/youtu.be/vimeo.com/freesound.org)";
}
context.drawTextWithShadow(textRenderer, Text.of("Link" + enabledWebsites),
this.width / 2 - 175, this.height / 5 + 3, -8816268);
context.drawTextWithShadow(textRenderer, Text.of("Width"),this.width / 2 - 175, this.height / 5 + 60, -8816268);
context.drawTextWithShadow(textRenderer, Text.of("Height"),this.width / 2 - 140, this.height / 5 + 60, -8816268);

View File

@@ -0,0 +1,68 @@
package eu.midnightdust.picturesign.util;
import me.srrapero720.watermedia.api.image.ImageAPI;
import me.srrapero720.watermedia.api.image.ImageCache;
import me.srrapero720.watermedia.api.math.MathAPI;
import me.srrapero720.watermedia.api.url.UrlAPI;
import net.minecraft.client.MinecraftClient;
import net.minecraft.util.Identifier;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.util.Map;
public class GIFHandler {
public static Map<Identifier, GIFHandler> gifPlayers = new HashMap<>();
public final Identifier id;
public boolean playbackStarted;
private ImageCache player;
private static final MinecraftClient client = MinecraftClient.getInstance();
private long tick = 0L;
private GIFHandler(Identifier id) {
System.out.println("New GIF handler :" + id);
this.id = id;
gifPlayers.put(id, this);
}
public static GIFHandler getOrCreate(Identifier id) {
if (gifPlayers.containsKey(id)) return gifPlayers.get(id);
else return new GIFHandler(id);
}
public void tick() {
if (player != null && player.getRenderer() != null && tick < player.getRenderer().duration) tick += 1;
else tick = 0;
}
public void closePlayer() {
player.release();
player = null;
gifPlayers.remove(this.id);
}
public static void closePlayer(Identifier videoId) {
if (gifPlayers.containsKey(videoId)) gifPlayers.get(videoId).closePlayer();
}
public static void closeAll() {
gifPlayers.forEach((id, handler) -> handler.closePlayer());
gifPlayers.clear();
}
public void play(String url) throws MalformedURLException {
System.out.println(url);
System.out.println(UrlAPI.fixURL(url).assumeVideo);
this.player = ImageAPI.getCache(url, MinecraftClient.getInstance());
player.load();
this.playbackStarted = true;
}
public boolean hasMedia() {
return player != null && player.getStatus() == ImageCache.Status.READY;
}
public int getTexture() {
return player.getRenderer().texture(tick,
(MathAPI.tickToMs(GIFHandler.client.getRenderTickCounter().getTickDelta(true))), true);
}
public boolean isWorking() {
if (player != null && player.getException() != null) player.getException().fillInStackTrace();
return player != null && player.getStatus() == ImageCache.Status.READY && player.getRenderer() != null;
}
}

View File

@@ -1,6 +1,8 @@
package eu.midnightdust.picturesign.util;
import eu.midnightdust.picturesign.config.PictureSignConfig;
import me.srrapero720.watermedia.api.player.SyncBasePlayer;
import me.srrapero720.watermedia.api.player.SyncMusicPlayer;
import me.srrapero720.watermedia.api.player.SyncVideoPlayer;
import me.srrapero720.watermedia.api.url.UrlAPI;
import net.minecraft.client.MinecraftClient;
@@ -14,31 +16,38 @@ import java.util.Map;
import static eu.midnightdust.picturesign.PictureSignClient.id;
public class VideoHandler {
public static Map<Identifier, SyncVideoPlayer> videoPlayers = new HashMap<>();
public class MediaHandler {
public static Map<Identifier, MediaHandler> mediaPlayers = new HashMap<>();
private final Identifier id;
private SyncVideoPlayer player;
public final Identifier id;
public boolean playbackStarted = false;
public boolean isDeactivated;
private SyncBasePlayer player;
public VideoHandler(Identifier id) {
private MediaHandler(Identifier id) {
this.id = id;
mediaPlayers.put(id, this);
}
public static MediaHandler getOrCreate(Identifier id) {
if (mediaPlayers.containsKey(id)) return mediaPlayers.get(id);
else return new MediaHandler(id);
}
public void closePlayer() {
if (videoPlayers.containsKey(id)) videoPlayers.get(id).release();
videoPlayers.remove(id);
if (player != null) player.release();
mediaPlayers.remove(id);
player = null;
}
public static void closePlayer(Identifier videoId) {
if (videoPlayers.containsKey(videoId)) videoPlayers.get(videoId).release();
videoPlayers.remove(videoId);
if (mediaPlayers.containsKey(videoId)) mediaPlayers.get(videoId).closePlayer();
}
public static void closeAll() {
videoPlayers.forEach(((id, player) -> player.release()));
videoPlayers.clear();
mediaPlayers.forEach(((id, player) -> player.closePlayer()));
mediaPlayers.clear();
}
public void stop() {
player.stop();
isDeactivated = true;
}
public boolean isStopped() {
return player.isStopped();
@@ -53,13 +62,14 @@ public class VideoHandler {
player.play();
}
public void play(String url) throws MalformedURLException {
public void play(String url, boolean isVideo) throws MalformedURLException {
URL fixedUrl = UrlAPI.fixURL(url).url;
System.out.println("Fixed URL: " + fixedUrl);
this.player = new SyncVideoPlayer(MinecraftClient.getInstance());
videoPlayers.put(id, player);
this.player = isVideo ? new SyncVideoPlayer(MinecraftClient.getInstance()) : new SyncMusicPlayer();
mediaPlayers.put(id, this);
if (player.isBroken()) return;
player.start(fixedUrl.toString());
this.playbackStarted = true;
}
public boolean hasMedia() {
return player != null && player.isPlaying();
@@ -74,10 +84,11 @@ public class VideoHandler {
player.seekTo(value);
}
public int getTexture() {
return player.getGlTexture();
if (player instanceof SyncVideoPlayer videoPlayer) return videoPlayer.getGlTexture();
return -1;
}
public boolean isWorking() {
return videoPlayers.containsKey(id) && !videoPlayers.get(id).isBroken();
return mediaPlayers.containsKey(id) && !mediaPlayers.get(id).player.isBroken();
}
public static Identifier getMissingTexture() {
if (PictureSignConfig.missingImageMode.equals(PictureSignConfig.MissingImageMode.TRANSPARENT)) return null;

View File

@@ -4,21 +4,19 @@ import net.minecraft.block.entity.SignBlockEntity;
import net.minecraft.text.Text;
public enum PictureSignType {
NONE(Text.empty(), ""),
PICTURE(Text.of("Image"), "!PS:"),
NONE(Text.empty(), "", false, false, false),
PICTURE(Text.of("Image"), "!PS:", false, false, false),
GIF(Text.of("GIF"), "!GS:", true, false, false),
VIDEO(Text.of("Video"), "!VS:", false, true, false),
LOOPED_VIDEO(Text.of("Video Loop"), "!LS:", true, true, false),
AUDIO(Text.of("Audio"), "!AS:", false, false, true),
LOOPED_AUDIO(Text.of("Audio Loop"), "!LAS:", true, false, true);
LOOPED_AUDIO(Text.of("Audio Loop"), "!ALS:", true, false, true);
public final Text name;
public final String format;
public final boolean isLooped;
public final boolean isVideo;
public final boolean isAudio;
PictureSignType(Text name, String format) {
this(name, format, false, false, false);
}
PictureSignType(Text name, String format, boolean isLooped, boolean isVideo, boolean isAudio) {
this.name = name;
@@ -33,6 +31,7 @@ public enum PictureSignType {
}
public static PictureSignType getType(String lineOne) {
if (lineOne.startsWith("!PS:")) return PICTURE;
else if (lineOne.startsWith("!GS:")) return GIF;
else if (lineOne.startsWith("!VS:")) return VIDEO;
else if (lineOne.startsWith("!LS:")) return LOOPED_VIDEO;
else if (lineOne.startsWith("!AS:")) return AUDIO;
@@ -41,7 +40,8 @@ public enum PictureSignType {
}
public PictureSignType next() {
return switch (this) {
case PICTURE -> VIDEO;
case PICTURE -> GIF;
case GIF -> VIDEO;
case VIDEO -> LOOPED_VIDEO;
case LOOPED_VIDEO -> AUDIO;
case AUDIO -> LOOPED_AUDIO;

View File

@@ -65,16 +65,24 @@ public class PictureURLUtils {
String text = signBlockEntity.getText(front).getMessage(0, false).getString() +
signBlockEntity.getText(front).getMessage(1, false).getString();
if (!signBlockEntity.getText(front).getMessage(2, false).getString().matches("(.*\\d:.*\\d:.*\\d)")) text += signBlockEntity.getText(front).getMessage(2, false).getString();
String url = text.replaceAll("!PS:", "").replaceAll("!VS:", "").replaceAll("!LS:", "").replaceAll(" ","");
String url = text.replaceAll("!PS:", "")
.replaceAll("!GS:", "")
.replaceAll("!VS:", "")
.replaceAll("!LS:", "")
.replaceAll("!AS:", "")
.replaceAll("!ALS:", "")
.replaceAll(" ","");
if (url.startsWith("ps:")) url = url.replace("ps:", "https://pictshare.net/");
if (url.startsWith("imgur:")) url = url.replace("imgur:", "https://i.imgur.com/");
if (url.startsWith("imgbb:")) url = url.replace("imgbb:", "https://i.ibb.co/");
if (url.startsWith("iili:")) url = url.replace("iili:", "https://iili.io/");
if (url.startsWith("tenor:")) url = url.replace("tenor:", "https://media1.tenor.com/m/");
if (url.startsWith("yt:")) url = url.replace("yt:", "https://youtu.be/");
return url;
}
public static String shortenLink(String url) {
if (url.contains("pictshare.net/")) url = url.replace("pictshare.net/", "ps:");
if (url.contains("media1.tenor.com/m/")) url = url.replace("media1.tenor.com/m/", "tenor:");
if (url.contains("i.imgur.com/")) url = url.replace("i.imgur.com/", "imgur:");
if (url.contains("i.ibb.co/:")) url = url.replace("i.ibb.co/", "imgbb:");
if (url.contains("iili.io/")) url = url.replace("iili.io/", "iili:");

View File

@@ -1,7 +1,8 @@
package eu.midnightdust.picturesign.fabric;
import eu.midnightdust.picturesign.PictureSignClient;
import eu.midnightdust.picturesign.util.VideoHandler;
import eu.midnightdust.picturesign.util.GIFHandler;
import eu.midnightdust.picturesign.util.MediaHandler;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientBlockEntityEvents;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
@@ -21,18 +22,19 @@ public class PictureSignClientFabric implements ClientModInitializer {
KeyBindingHelper.registerKeyBinding(BINDING_COPY_SIGN);
ClientPlayConnectionEvents.DISCONNECT.register((handler, client) -> {
if (hasWaterMedia) VideoHandler.closeAll();
if (hasWaterMedia) MediaHandler.closeAll();
});
ClientBlockEntityEvents.BLOCK_ENTITY_UNLOAD.register((blockEntity, world) -> {
if (hasWaterMedia) {
BlockPos pos = blockEntity.getPos();
Identifier videoId = id(pos.getX() + "_" + pos.getY() + "_" + pos.getZ()+"_f");
VideoHandler.closePlayer(videoId);
MediaHandler.closePlayer(videoId);
Identifier videoId2 = id(pos.getX() + "_" + pos.getY() + "_" + pos.getZ()+"_b");
VideoHandler.closePlayer(videoId2);
MediaHandler.closePlayer(videoId2);
}
});
ClientTickEvents.END_CLIENT_TICK.register(client -> {
GIFHandler.gifPlayers.forEach(((identifier, handler) -> handler.tick()));
if (!BINDING_COPY_SIGN.isPressed()) return;
BINDING_COPY_SIGN.setPressed(false);
if (client.player == null || client.world == null || client.crosshairTarget == null || client.crosshairTarget.getType() != HitResult.Type.BLOCK) return;

View File

@@ -0,0 +1,19 @@
package eu.midnightdust.picturesign.neoforge;
import eu.midnightdust.picturesign.PictureSignClient;
import net.minecraft.resource.ResourcePackProfile;
import net.neoforged.neoforge.client.event.ClientTickEvent;
import org.apache.commons.compress.utils.Lists;
import java.util.List;
@SuppressWarnings("all")
public class PictureSignClientNeoForge {
public static List<ResourcePackProfile> defaultEnabledPacks = Lists.newArrayList();
public static void initClient() {
PictureSignClient.init();
}
public static void doClientTick(ClientTickEvent.Pre event) {
}
}

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB