Update AudioExtractor.java

main
Nes370 2024-07-23 23:46:18 -07:00
parent ddcc25af73
commit 0cac93383f
1 changed files with 180 additions and 90 deletions

View File

@ -33,7 +33,7 @@ public class AudioExtractor {
* Activate alternate behavior for command line terminal. * Activate alternate behavior for command line terminal.
* Specifically prints things in a narrower width, and overwrites previous lines to add color. * Specifically prints things in a narrower width, and overwrites previous lines to add color.
*/ */
private final static boolean CMD_MODE = true; private final static boolean CMD_MODE = false;
/** /**
* ANSI code to go up one line, followed by a carriage return. * ANSI code to go up one line, followed by a carriage return.
@ -51,6 +51,11 @@ public class AudioExtractor {
* Indicates the start of a RIFF header in WAV files. * Indicates the start of a RIFF header in WAV files.
*/ */
private final static int ASCII_RIFF = 0x5249_4646; private final static int ASCII_RIFF = 0x5249_4646;
/**
* Hex value for the ASCII sequence "RIFF".
* Indicates the start of a RIFF header in WAV files.
*/
private final static int ASCII_WAVE = 0x5741_5645;
/** /**
* Hex value for the ASCII sequence "fmt ". * Hex value for the ASCII sequence "fmt ".
* Indicates the start of a format chunk in WAV files. * Indicates the start of a format chunk in WAV files.
@ -76,6 +81,12 @@ public class AudioExtractor {
* Indicates the start of an XMA stream chunk in WAV files. * Indicates the start of an XMA stream chunk in WAV files.
*/ */
private final static int ASCII_x2st = 0x7832_7374; private final static int ASCII_x2st = 0x7832_7374;
/**
* Hex value for the ASCII sequence "fLaC".
* Indicates the start of an FLAC file.
*/
private final static int ASCII_fLaC = 0x664C_6143;
/** /**
* BIN files must first be extracted from game disk to use this program. * BIN files must first be extracted from game disk to use this program.
* By default uses program directory for I/O and saves audio as WAV.<br> * By default uses program directory for I/O and saves audio as WAV.<br>
@ -252,28 +263,14 @@ public class AudioExtractor {
} }
/** public static void identify(File unknownFile) {
* Helper method to convert a number of bytes to a more readable form.
*
* @param bytes
* @return legible form
*/
private static String convertBytes(long bytes) {
final String[] units = new String[] {"B", "KB", "MB", "GB", "TB"};
int i = (int) (Math.log10(bytes) / Math.log10(1024));
double value = bytes / Math.pow(1024, i);
return String.format("%.2f %s", value, units[i]);
}
public static void identify(File file) {
// TODO check for characteristics of BIN or WAV files // TODO check for characteristics of BIN or WAV files
String name = file.getName(); String name = unknownFile.getName();
System.out.println("Name:\t" + name); System.out.println("Name:\t" + name);
long size = file.length(); long size = unknownFile.length();
System.out.println("Size:\t" + size + " Bytes" + (size > 1024 ? " (" + convertBytes(size) + ")" : "")); System.out.println("Size:\t" + size + " Bytes" + (size > 1024 ? " (" + convertBytes(size) + ")" : "") + "\n");
// file extension // file extension
int extIndex = name.lastIndexOf('.'); int extIndex = name.lastIndexOf('.');
@ -281,10 +278,88 @@ public class AudioExtractor {
if(extIndex > 0 && extIndex < name.length() - 1) if(extIndex > 0 && extIndex < name.length() - 1)
extension = name.substring(extIndex + 1); extension = name.substring(extIndex + 1);
try {
RandomAccessFile file = new RandomAccessFile(unknownFile, "r");
switch(extension.toLowerCase()) {
case "wav":
case "xma": {
byte[] buffer = new byte[4];
for(long i = 0; i < size - 4; i += 4) {
file.seek(i);
int value = file.readInt();
switch(value) {
case ASCII_RIFF: {
System.out.println(formatAddress(i) + ":\tRIFF (Resource Interchange File Format) chunk");
if(i + 8 < size) {
int riffSize = readChunkSize(file, (int) i);
System.out.println(formatAddress(i + 4) + ":\t" + riffSize + " Bytes" + (riffSize > 1024 ? " (" + convertBytes(riffSize) + ")" : ""));
}
if(i + 12 < size) {
file.seek(i + 8);
value = file.readInt();
if(value == ASCII_WAVE)
System.out.println(formatAddress(i + 8) + ":\tWAVE (Waveform Audio File Format) type");
i += 12;
}
System.out.println();
break;
}
case ASCII_fmt: {
System.out.println(formatAddress(i) + ":\tfmt (Format) chunk");
int fmtSize = 8;
if(i + 8 < size) {
fmtSize = readChunkSize(file, (int) i);
System.out.println(formatAddress(i + 4) + ":\t" + fmtSize + " Bytes" + (fmtSize > 1024 ? " (" + convertBytes(fmtSize) + ")" : ""));
}
if(i + fmtSize < size) {
file.seek(i + 8);
byte[] compression = new byte[2];
file.read(compression);
i += fmtSize;
}
System.out.println();
break;
}
}
}
break;
}
case "bin": {
break;
}
case "flac": {
break;
}
default: {
break;
}
}
} catch (IOException e) {
System.out.println("Could not read file.");
}
// check if RIFF header // check if RIFF header
} }
/**
* Formats a file address with leading zeroes.
*
* @param address
* @return padded address
*/
private static String formatAddress(long address) {
if(address > 0xFFFF)
return String.format("%08x", address);
else return String.format("%04x", address);
}
/** /**
* Extracts audio files to the extract directory from package files located at the package directory. * Extracts audio files to the extract directory from package files located at the package directory.
* Also supports FLAC conversion for BGM files. * Also supports FLAC conversion for BGM files.
@ -558,6 +633,72 @@ public class AudioExtractor {
} }
/**
* Helper function to prompt user to select a program task.
*
* @param reader
* @param input
* @return operation
*/
private static String retrieveOperation(BufferedReader reader, String input) {
String operation = null;
String[] values = {"identify", "extract", "pack", "patch", "print", "exit"};
if(input != null)
for(int i = 0; i < values.length; i++)
if(values[i].equalsIgnoreCase(input))
operation = values[i];
if(operation == null) {
String prompt = "\n" + Ansi.colorize("Operations", Attribute.BRIGHT_YELLOW_TEXT(), Attribute.BOLD()) + ":\n"
+ "- " + Ansi.colorize("Identify", Attribute.BRIGHT_RED_TEXT()) + " a package or audio file\n"
+ "- " + Ansi.colorize("Extract", Attribute.TEXT_COLOR(0xFF, 0x68, 0x1F)) + " audio tracks from a package (BIN -> WAV/XMA)\n"
+ "- " + Ansi.colorize("Convert", Attribute.TEXT_COLOR(0xFF, 0xEA, 0x00)) + " audio tracks to another format (XMA <-> WAV <-> FLAC)\n"
+ "- " + Ansi.colorize("Patch", Attribute.BRIGHT_GREEN_TEXT()) + " an audio track to repeat (WAV)\n"
+ "- " + Ansi.colorize("Pack", Attribute.BRIGHT_BLUE_TEXT()) + " audio tracks into a package (WAV/XMA -> BIN)\n"
+ "- " + Ansi.colorize("Print", Attribute.TEXT_COLOR(0x6B, 0x4C, 0xE3)) + " a Ridge Racer 6 style ASCII logo\n"
+ "- " + Ansi.colorize("Exit", Attribute.BRIGHT_MAGENTA_TEXT()) + " program\n"
+ "\nPlease select an operation: " + ANSI_BEIGE;
operation = retrieveInput(reader, prompt, values);
if(CMD_MODE) {
Attribute color = null;
switch(operation.toLowerCase()) {
case "identify":
color = Attribute.BRIGHT_RED_TEXT();
break;
case "extract":
color = Attribute.TEXT_COLOR(0xFF, 0x68, 0x1F);
break;
case "convert":
color = Attribute.TEXT_COLOR(0xFF, 0xEA, 0x00);
case "patch":
color = Attribute.BRIGHT_GREEN_TEXT();
break;
case "pack":
color = Attribute.BRIGHT_BLUE_TEXT();
break;
case "print":
color = Attribute.TEXT_COLOR(0x6B, 0x4C, 0xE3);
break;
case "exit":
color = Attribute.BRIGHT_MAGENTA_TEXT();
break;
}
System.out.println(ANSI_BACKLINE + Ansi.colorize("Please select an operation: ") + Ansi.colorize(operation, color));
} else System.out.print(Ansi.colorize(""));
}
return operation.toLowerCase();
}
/** /**
* Helper function to prompt user to select a WAV file. * Helper function to prompt user to select a WAV file.
* *
@ -741,72 +882,6 @@ public class AudioExtractor {
} }
/**
* Helper function to prompt user to select a program task.
*
* @param reader
* @param input
* @return operation
*/
private static String retrieveOperation(BufferedReader reader, String input) {
String operation = null;
String[] values = {"identify", "extract", "pack", "patch", "print", "exit"};
if(input != null)
for(int i = 0; i < values.length; i++)
if(values[i].equalsIgnoreCase(input))
operation = values[i];
if(operation == null) {
String prompt = "\n" + Ansi.colorize("Operations", Attribute.BRIGHT_YELLOW_TEXT(), Attribute.BOLD()) + ":\n"
+ "- " + Ansi.colorize("Identify", Attribute.BRIGHT_RED_TEXT()) + " a package or audio file\n"
+ "- " + Ansi.colorize("Extract", Attribute.TEXT_COLOR(0xFF, 0x68, 0x1F)) + " audio tracks from a package (BIN to WAV)\n"
+ "- " + Ansi.colorize("Convert", Attribute.TEXT_COLOR(0xFF, 0xEA, 0x00)) + " audio tracks to another format (XMA to PCM / WAV to FLAC)\n"
+ "- " + Ansi.colorize("Patch", Attribute.BRIGHT_GREEN_TEXT()) + " an audio track to repeat (PCM-encoded WAV)\n"
+ "- " + Ansi.colorize("Pack", Attribute.BRIGHT_BLUE_TEXT()) + " audio tracks into a package (WAV to BIN)\n"
+ "- " + Ansi.colorize("Print", Attribute.TEXT_COLOR(0x6B, 0x4C, 0xE3)) + " a Ridge Racer 6 style ASCII logo\n"
+ "- " + Ansi.colorize("Exit", Attribute.BRIGHT_MAGENTA_TEXT()) + " program\n"
+ "\nPlease select an operation: " + ANSI_BEIGE;
operation = retrieveInput(reader, prompt, values);
if(CMD_MODE) {
Attribute color = null;
switch(operation.toLowerCase()) {
case "identify":
color = Attribute.BRIGHT_RED_TEXT();
break;
case "extract":
color = Attribute.TEXT_COLOR(0xFF, 0x68, 0x1F);
break;
case "convert":
color = Attribute.TEXT_COLOR(0xFF, 0xEA, 0x00);
case "patch":
color = Attribute.BRIGHT_GREEN_TEXT();
break;
case "pack":
color = Attribute.BRIGHT_BLUE_TEXT();
break;
case "print":
color = Attribute.TEXT_COLOR(0x6B, 0x4C, 0xE3);
break;
case "exit":
color = Attribute.BRIGHT_MAGENTA_TEXT();
break;
}
System.out.println(ANSI_BACKLINE + Ansi.colorize("Please select an operation: ") + Ansi.colorize(operation, color));
} else System.out.print(Ansi.colorize(""));
}
return operation.toLowerCase();
}
/** /**
* Prompts the user for input without strict validation. * Prompts the user for input without strict validation.
* *
@ -895,12 +970,27 @@ public class AudioExtractor {
file.seek(chunkAddress + 4); file.seek(chunkAddress + 4);
file.read(buffer); file.read(buffer);
// System.out.println("DEBUG: " + (buffer[0] & 0xFF) + " " + (buffer[1] & 0xFF) + " " + (buffer[2] & 0xFF) + " " + (buffer[3] & 0xFF) + "");
return 8 // size offset return 8 // size offset
+ buffer[3] // 16^0 + (buffer[0] & 0xFF) // 256^0
+ buffer[2] * 256 // 16^2 + (buffer[1] & 0xFF) * 256 // 256^1
+ buffer[1] * 65_536 // 16^4 + (buffer[2] & 0xFF) * 65_536 // 256^2
+ buffer[0] * 16_777_216; // 16^6 + (buffer[3] & 0xFF) * 16_777_216; // 256^3
}
/**
* Helper method to convert a number of bytes to a more readable form.
*
* @param bytes
* @return legible form
*/
private static String convertBytes(long bytes) {
final String[] units = new String[] {"B", "KB", "MB", "GB", "TB"};
int i = (int) (Math.log10(bytes) / Math.log10(1024));
double value = bytes / Math.pow(1024, i);
return String.format("%.2f %s", value, units[i]);
} }