Update AudioExtractor.java
parent
ddcc25af73
commit
0cac93383f
|
@ -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]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue