feat: support ascii85 in *music playitem
This commit is contained in:
parent
b61d7ee2fa
commit
e37d3b1a6e
3 changed files with 204 additions and 6 deletions
|
@ -1 +1 @@
|
||||||
2125
|
2129
|
|
@ -7,6 +7,7 @@ import me.chayapak1.chomens_bot.song.Instrument;
|
||||||
import me.chayapak1.chomens_bot.song.Loop;
|
import me.chayapak1.chomens_bot.song.Loop;
|
||||||
import me.chayapak1.chomens_bot.song.Note;
|
import me.chayapak1.chomens_bot.song.Note;
|
||||||
import me.chayapak1.chomens_bot.song.Song;
|
import me.chayapak1.chomens_bot.song.Song;
|
||||||
|
import me.chayapak1.chomens_bot.util.Ascii85;
|
||||||
import me.chayapak1.chomens_bot.util.ColorUtilities;
|
import me.chayapak1.chomens_bot.util.ColorUtilities;
|
||||||
import me.chayapak1.chomens_bot.util.PathUtilities;
|
import me.chayapak1.chomens_bot.util.PathUtilities;
|
||||||
import me.chayapak1.chomens_bot.util.TimestampUtilities;
|
import me.chayapak1.chomens_bot.util.TimestampUtilities;
|
||||||
|
@ -91,11 +92,11 @@ public class MusicCommand extends Command {
|
||||||
|
|
||||||
if (player.loaderThread != null) throw new CommandException(Component.text("Already loading a song"));
|
if (player.loaderThread != null) throw new CommandException(Component.text("Already loading a song"));
|
||||||
|
|
||||||
String stringPath;
|
final String stringPath = context.getString(true, true);
|
||||||
Path path;
|
|
||||||
try {
|
|
||||||
stringPath = context.getString(true, true);
|
|
||||||
|
|
||||||
|
Path path;
|
||||||
|
|
||||||
|
try {
|
||||||
path = Path.of(ROOT.toString(), stringPath);
|
path = Path.of(ROOT.toString(), stringPath);
|
||||||
|
|
||||||
if (path.toString().contains("http")) player.loadSong(new URI(stringPath).toURL(), context.sender);
|
if (path.toString().contains("http")) player.loadSong(new URI(stringPath).toURL(), context.sender);
|
||||||
|
@ -196,7 +197,14 @@ public class MusicCommand extends Command {
|
||||||
context.sender
|
context.sender
|
||||||
);
|
);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
context.sendOutput(Component.text("Invalid base64 in the selected item").color(NamedTextColor.RED));
|
try {
|
||||||
|
bot.music.loadSong(
|
||||||
|
Ascii85.decode(output),
|
||||||
|
context.sender
|
||||||
|
);
|
||||||
|
} catch (IllegalArgumentException e2) {
|
||||||
|
context.sendOutput(Component.text("Invalid Base64 or Ascii85 in the selected item").color(NamedTextColor.RED));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
|
|
190
src/main/java/me/chayapak1/chomens_bot/util/Ascii85.java
Normal file
190
src/main/java/me/chayapak1/chomens_bot/util/Ascii85.java
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
package me.chayapak1.chomens_bot.util;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A very simple class that helps encode/decode for Ascii85 / base85
|
||||||
|
* The version that is likely most similar that is implemented here would be the Adobe version.
|
||||||
|
* <p>
|
||||||
|
* This code is from <a href="https://github.com/fzakaria/ascii85/blob/master/src/main/java/com/github/fzakaria/ascii85/Ascii85.java">https://github.com/fzakaria/ascii85/blob/master/src/main/java/com/github/fzakaria/ascii85/Ascii85.java</a>. Thank you!
|
||||||
|
*
|
||||||
|
* @see <a href="https://en.wikipedia.org/wiki/Ascii85">Ascii85</a>
|
||||||
|
*/
|
||||||
|
public class Ascii85 {
|
||||||
|
|
||||||
|
private final static int ASCII_SHIFT = 33;
|
||||||
|
|
||||||
|
private static final int[] BASE85_POW = {
|
||||||
|
1,
|
||||||
|
85,
|
||||||
|
85 * 85,
|
||||||
|
85 * 85 * 85,
|
||||||
|
85 * 85 * 85 *85
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final Pattern REMOVE_WHITESPACE = Pattern.compile("\\s+");
|
||||||
|
|
||||||
|
private Ascii85 () {}
|
||||||
|
|
||||||
|
public static String encode(byte[] payload) {
|
||||||
|
if (payload == null) {
|
||||||
|
throw new IllegalArgumentException("You must provide a non-null input");
|
||||||
|
}
|
||||||
|
// By using five ASCII characters to represent four bytes of binary data the encoded size ¹⁄₄ is larger than the original
|
||||||
|
StringBuilder stringBuff = new StringBuilder(payload.length * 5/4);
|
||||||
|
// We break the payload into int (4 bytes)
|
||||||
|
byte[] chunk = new byte[4];
|
||||||
|
int chunkIndex = 0;
|
||||||
|
for (byte currByte : payload) {
|
||||||
|
chunk[chunkIndex++] = currByte;
|
||||||
|
|
||||||
|
if (chunkIndex == 4) {
|
||||||
|
int value = byteToInt(chunk);
|
||||||
|
//Because all-zero data is quite common, an exception is made for the sake of data compression,
|
||||||
|
//and an all-zero group is encoded as a single character "z" instead of "!!!!!".
|
||||||
|
if (value == 0) {
|
||||||
|
stringBuff.append('z');
|
||||||
|
} else {
|
||||||
|
stringBuff.append(encodeChunk(value));
|
||||||
|
}
|
||||||
|
Arrays.fill(chunk, (byte) 0);
|
||||||
|
chunkIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//If we didn't end on 0, then we need some padding
|
||||||
|
if (chunkIndex > 0) {
|
||||||
|
int numPadded = chunk.length - chunkIndex;
|
||||||
|
Arrays.fill(chunk, chunkIndex, chunk.length, (byte)0);
|
||||||
|
int value = byteToInt(chunk);
|
||||||
|
char[] encodedChunk = encodeChunk(value);
|
||||||
|
for(int i = 0 ; i < encodedChunk.length - numPadded; i++) {
|
||||||
|
stringBuff.append(encodedChunk[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuff.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char[] encodeChunk(int value) {
|
||||||
|
//transform value to unsigned long
|
||||||
|
long longValue = value & 0x00000000ffffffffL;
|
||||||
|
char[] encodedChunk = new char[5];
|
||||||
|
for(int i = 0 ; i < encodedChunk.length; i++) {
|
||||||
|
encodedChunk[i] = (char) ((longValue / BASE85_POW[4 - i]) + ASCII_SHIFT);
|
||||||
|
longValue = longValue % BASE85_POW[4 - i];
|
||||||
|
}
|
||||||
|
return encodedChunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a very simple base85 decoder. It respects the 'z' optimization for empty chunks, and
|
||||||
|
* strips whitespace between characters to respect line limits.
|
||||||
|
* @see <a href="https://en.wikipedia.org/wiki/Ascii85">Ascii85</a>
|
||||||
|
* @param chars The input characters that are base85 encoded.
|
||||||
|
* @return The binary data decoded from the input
|
||||||
|
*/
|
||||||
|
public static byte[] decode(String chars) {
|
||||||
|
if (chars == null) {
|
||||||
|
throw new IllegalArgumentException("You must provide a non-null input");
|
||||||
|
}
|
||||||
|
// Because we perform compression when encoding four bytes of zeros to a single 'z', we need
|
||||||
|
// to scan through the input to compute the target length, instead of just subtracting 20% of
|
||||||
|
// the encoded text length.
|
||||||
|
final int inputLength = chars.length();
|
||||||
|
|
||||||
|
// lets first count the occurrences of 'z'
|
||||||
|
long zCount = chars.chars().filter(c -> c == 'z').count();
|
||||||
|
|
||||||
|
// Typically by using five ASCII characters to represent four bytes of binary data
|
||||||
|
// the encoded size ¹⁄₄ is larger than the original.
|
||||||
|
// We however have to account for the 'z' which were compressed
|
||||||
|
BigDecimal uncompressedZLength = BigDecimal.valueOf(zCount).multiply(BigDecimal.valueOf(4));
|
||||||
|
|
||||||
|
BigDecimal uncompressedNonZLength = BigDecimal.valueOf(inputLength - zCount)
|
||||||
|
.multiply(BigDecimal.valueOf(4))
|
||||||
|
.divide(BigDecimal.valueOf(5));
|
||||||
|
|
||||||
|
BigDecimal uncompressedLength = uncompressedZLength.add(uncompressedNonZLength);
|
||||||
|
|
||||||
|
ByteBuffer bytebuff = ByteBuffer.allocate(uncompressedLength.intValue());
|
||||||
|
//1. Whitespace characters may occur anywhere to accommodate line length limitations. So lets strip it.
|
||||||
|
chars = REMOVE_WHITESPACE.matcher(chars).replaceAll("");
|
||||||
|
//Since Base85 is an ascii encoder, we don't need to get the bytes as UTF-8.
|
||||||
|
byte[] payload = chars.getBytes(StandardCharsets.US_ASCII);
|
||||||
|
byte[] chunk = new byte[5];
|
||||||
|
int chunkIndex = 0;
|
||||||
|
|
||||||
|
for (byte currByte : payload) {
|
||||||
|
// Because all-zero data is quite common, an exception is made for the sake of data compression,
|
||||||
|
// and an all-zero group is encoded as a single character "z" instead of "!!!!!".
|
||||||
|
if (currByte == 'z') {
|
||||||
|
if (chunkIndex > 0) {
|
||||||
|
throw new IllegalArgumentException("The payload is not base 85 encoded.");
|
||||||
|
}
|
||||||
|
chunk[chunkIndex++] = '!';
|
||||||
|
chunk[chunkIndex++] = '!';
|
||||||
|
chunk[chunkIndex++] = '!';
|
||||||
|
chunk[chunkIndex++] = '!';
|
||||||
|
chunk[chunkIndex++] = '!';
|
||||||
|
} else {
|
||||||
|
chunk[chunkIndex++] = currByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunkIndex == 5) {
|
||||||
|
bytebuff.put(decodeChunk(chunk));
|
||||||
|
Arrays.fill(chunk, (byte) 0);
|
||||||
|
chunkIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we didn't end on 0, then we need some padding
|
||||||
|
if (chunkIndex > 0) {
|
||||||
|
int numPadded = chunk.length - chunkIndex;
|
||||||
|
Arrays.fill(chunk, chunkIndex, chunk.length, (byte)'u');
|
||||||
|
byte[] paddedDecode = decodeChunk(chunk);
|
||||||
|
for(int i = 0 ; i < paddedDecode.length - numPadded; i++) {
|
||||||
|
bytebuff.put(paddedDecode[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytebuff.flip();
|
||||||
|
return Arrays.copyOf(bytebuff.array(),bytebuff.limit());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] decodeChunk(byte[] chunk) {
|
||||||
|
if (chunk.length != 5) {
|
||||||
|
throw new IllegalArgumentException("You can only decode chunks of size 5.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int value = 0;
|
||||||
|
|
||||||
|
value += (chunk[0] - ASCII_SHIFT) * BASE85_POW[4];
|
||||||
|
value += (chunk[1] - ASCII_SHIFT) * BASE85_POW[3];
|
||||||
|
value += (chunk[2] - ASCII_SHIFT) * BASE85_POW[2];
|
||||||
|
value += (chunk[3] - ASCII_SHIFT) * BASE85_POW[1];
|
||||||
|
value += (chunk[4] - ASCII_SHIFT) * BASE85_POW[0];
|
||||||
|
|
||||||
|
return intToByte(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int byteToInt(byte[] value) {
|
||||||
|
if (value == null || value.length != 4) {
|
||||||
|
throw new IllegalArgumentException("You cannot create an int without exactly 4 bytes.");
|
||||||
|
}
|
||||||
|
return ByteBuffer.wrap(value).getInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] intToByte(int value) {
|
||||||
|
return new byte[] {
|
||||||
|
(byte) (value >>> 24),
|
||||||
|
(byte) (value >>> 16),
|
||||||
|
(byte) (value >>> 8),
|
||||||
|
(byte) (value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue