Update AudioExtractor.java

Finished Extract function rewrite.
main
Nes370 2024-07-25 00:08:49 -07:00
parent 98a235e0f4
commit 80760bc534
1 changed files with 358 additions and 172 deletions

View File

@ -161,24 +161,37 @@ public class AudioExtractor {
if(args.length == 0) { // no arguments provided if(args.length == 0) { // no arguments provided
switch(operation) { switch(operation) {
case "identify":
case "identify": {
File unknownFile = retrieveFile(reader, "Please enter the path of a file: " + ANSI_BEIGE); File unknownFile = retrieveFile(reader, "Please enter the path of a file: " + ANSI_BEIGE);
identify(unknownFile); identify(unknownFile);
break; break;
case "extract": // extract }
// Determine package directory
File currentDir = new File(new File(AudioExtractor.class.getProtectionDomain().getCodeSource().getLocation().toURI())
.getParent());
boolean packDirFound = false;
do {
System.out.println(Ansi.colorize("\nPackage Directory:", Attribute.BOLD(), Attribute.CYAN_TEXT()));
System.out.println("- " + Ansi.colorize("Current", Attribute.BRIGHT_RED_TEXT()) + " directory:\t"
+ Ansi.colorize(currentDir.getPath(), Attribute.BRIGHT_CYAN_TEXT()));
} while(!packDirFound); case "extract": {
File packageFile = retrievePackage(reader);
if(packageFile.isFile())
extract(packageFile);
else { // packageFile is a folder
File[] packages = packageFile.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".bin");
}
});
for(File pack : packages) {
extract(pack);
}
}
break; break;
case "package": // pack }
case "convert": {
}
case "package": {// pack
break; break;
}
case "patch": // patch case "patch": // patch
File waveFile = retrieveWave(reader); File waveFile = retrieveWave(reader);
patch(waveFile); patch(waveFile);
@ -196,6 +209,9 @@ public class AudioExtractor {
} }
System.out.println("Press Enter to continue...");
try { reader.readLine(); } catch (Exception e) {}
} }
/* /*
@ -280,6 +296,280 @@ public class AudioExtractor {
} }
/**
* Extracts audio tracks from the given package file.
*
* @param packageFile
* @throws FileNotFoundException
*/
private static void extract(File packageFile) {
System.out.println("Name:\t" + packageFile.getName());
System.out.println("Size:\t" + packageFile.length() + " (" + convertBytes(packageFile.length()) + ")\n");
try {
RandomAccessFile file = new RandomAccessFile(packageFile, "r");
LinkedHashMap<Integer, Integer> tracklist = findAudioTracks(file);
File dir = new File(packageFile.getAbsoluteFile().getParentFile().toPath() + "/" + FilenameUtils.removeExtension(packageFile.getName()));
if(!dir.exists())
dir.mkdir();
System.out.println("Extracting " + tracklist.size() + " tracks...");
int digits = (int) Math.ceil(Math.log10(tracklist.size() + 1));
int extracted = 0;
Set<Integer> keys = tracklist.keySet();
for(Integer address : keys) {
int length = tracklist.get(address);
if(CMD_MODE)
printProgressBar(extracted, keys.size());
String extension;
byte[] bytes = new byte[2];
file.seek(address + 0x14);
file.read(bytes);
int encoding = littleEndianToInt(bytes);
switch(encoding) {
case 0x0165: extension = "xma"; break;
case 0x0001: extension = "wav"; break;
default: extension = "bin"; break;
}
File trackFile = new File(dir.toPath() + "/track" + String.format("%0" + digits + "d", extracted + 1) + "." + extension);
try (
FileInputStream in = new FileInputStream(packageFile);
FileOutputStream out = new FileOutputStream(trackFile);
) {
in.skipNBytes(address.intValue());
bytes = new byte[length];
int readBytes = in.read(bytes);
out.write(bytes, 0, readBytes);
} catch (IOException e) {
e.printStackTrace();
}
extracted++;
}
if(CMD_MODE)
printProgressBar(extracted, keys.size());
} catch(Exception e) {
e.printStackTrace();
}
System.out.println("Finished.\n");
}
public static void printProgressBar(int current, int total) {
int done = 50 * current / total;
int todo = 50 - done;
String doneStr = ""; // String.format("%" + done + "s", '█');
for(int i = 0; i < done; i++)
doneStr += '█';
String todoStr = ""; // String.format("%" + todo + "s", '_');
for(int i = 0; i < todo; i++)
doneStr += '_';
System.out.println(ANSI_BACKLINE + "Progress: |" + doneStr + todoStr + "| (" + current + "/" + total + ")");
}
/**
* Extracts audio files to the extract directory from package files located at the package directory.
* Also supports FLAC conversion for BGM files.
*
* @param packDirectory
* @param extractDirectory
* @param compressBGM
*/
public static void extract(File packDirectory, File extractDirectory, boolean compressBGM) {
// TODO Delete
System.out.println("packDirectory:\t" + packDirectory);
System.out.println("extractDirectory:\t" + extractDirectory);
// Identify target binary files
File[] packages = new File(packDirectory.toString()).listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".bin");
}
});
// Identify and extract audio within binary files
for(int i = 0; i < packages.length; i++) {
RandomAccessFile source;
LinkedHashMap<Integer, Integer> tracklist = null;
try {
source = new RandomAccessFile(packages[i], "r");
tracklist = findAudioTracks(source);
} catch (FileNotFoundException e) {
System.out.println("Binary file:\t" + packages[i] + "\n was not found. File skipped.");
continue;
} catch (IOException e) {
System.out.println("Binary file:\t" + packages[i] + "\n could not be read. File skipped.");
continue;
}
//TODO Delete
System.out.println("packages[" + i + "]:\t" + packages[i]);
File dir = new File(extractDirectory.getPath() + "/" + FilenameUtils.removeExtension(packages[i].getName()));
if(!dir.exists())
dir.mkdir();
// Extract and write WAV files in directory
Set<Integer> keys = tracklist.keySet();
int track = 1;
for(Integer key : keys) {
String name = String.format("track%04d", track);
{ // Extract WAV files
File wav = null;
try {
// create file for storing WAV data
wav = new File(dir.getPath() + "/" + name + ".wav");
if(!wav.exists())
wav.createNewFile();
//TODO Delete
System.out.println("Saving track " + String.format("%d (@0x%08X)", track, key) + " to " + wav + String.format(" (%d bytes)", tracklist.get(key)));
// write selection to file
try (
FileInputStream inStream = new FileInputStream(packages[i]);
FileOutputStream outStream = new FileOutputStream(wav)
) {
inStream.skipNBytes(key.intValue());
byte[] trackBytes = new byte[tracklist.get(key)];
int readBytes = inStream.read(trackBytes);
outStream.write(trackBytes, 0, readBytes);
} catch (IOException e) {
e.printStackTrace();
System.exit(3);
}
} catch (IOException e) {
System.out.println("An error occurred when attempting to write " + name + " to file:\t" + wav);
e.printStackTrace();
System.exit(3);
}
}
track++;
}
if(compressBGM && packages[i].getName().equals("pack_bgm.bin")) {
// find WAV files
File[] WAV_Files = dir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".wav");
}
});
// convert WAV files to FLAC
FLAC_FileEncoder ffe = new FLAC_FileEncoder();
for(File wav : WAV_Files) {
try {
File flac = new File(dir.getPath() + "/" + FilenameUtils.removeExtension(wav.getName()) + ".flac");
if(!flac.exists())
flac.createNewFile();
//TODO Delete
System.out.println("Compressing WAV to FLAC:\t" + flac);
ffe.encode(wav, flac);
} catch (IOException e) {
System.out.println("An error occurred when attempting to write file.");
e.printStackTrace();
System.exit(3);
}
}
System.out.print("Deleting WAV files");
// delete WAV files
for(File wav : WAV_Files) {
boolean deleted = false;
while(!deleted) {
try {
Files.delete(wav.toPath());
deleted = true;
} catch(Exception e) {
System.out.print('.');
}
}
}
}
}
}
/**
* Helper method to prompt user to select a package file, or folder containing one or more packages.
*
* @param reader
* @return file
*/
private static File retrievePackage(BufferedReader reader) {
String prompt = "Please enter the path of a package: " + ANSI_BEIGE;
File packageFile = null;
boolean valid = false;
do {
// Retrieve an audio file
packageFile = retrieveFileOrFolder(reader, prompt);
if(packageFile.isFile()) {
// File must have .bin extension
String name = packageFile.getName();
int extIndex = name.lastIndexOf('.');
String extension = null;
if(extIndex > 0 && extIndex < name.length() - 1)
extension = name.substring(extIndex + 1);
if(extension != null && extension.equalsIgnoreCase("bin"))
valid = true;
} else { // packageFile is folder
; // Folder should have a .bin file
File[] contents = packageFile.listFiles();
for(File file : contents) {
String name = file.getName();
int extIndex = name.lastIndexOf('.');
String extension = null;
if(extIndex > 0 && extIndex < name.length() - 1)
extension = name.substring(extIndex + 1);
if(extension != null && extension.equalsIgnoreCase("bin")) {
valid = true;
break;
}
}
}
if(!valid) {
if(packageFile.isFile())
System.out.println("File does not have a .BIN file extension.");
else System.out.println("Directory does not contain a .BIN file.");
}
} while(!valid);
return packageFile;
}
/** /**
* Helper function to prompt user to select a program task. * Helper function to prompt user to select a program task.
@ -350,15 +640,15 @@ public class AudioExtractor {
/** /**
* Examines a given file and reads some of its properties. * Examines a given file and reads some of its properties.
* *
* @param unknownFile * @param givenFile
*/ */
public static void identify(File unknownFile) { public static void identify(File givenFile) {
// TODO check for characteristics of BIN or WAV files // TODO check for characteristics of BIN or WAV files
String name = unknownFile.getName(); String name = givenFile.getName();
System.out.println("Name:\t" + name); System.out.println("Name:\t" + name);
long size = unknownFile.length(); long size = givenFile.length();
System.out.println("Size:\t" + size + " Bytes" + (size > 1024 ? " (" + convertBytes(size) + ")" : "") + "\n"); System.out.println("Size:\t" + size + " Bytes" + (size > 1024 ? " (" + convertBytes(size) + ")" : "") + "\n");
// file extension // file extension
@ -368,14 +658,13 @@ public class AudioExtractor {
extension = name.substring(extIndex + 1); extension = name.substring(extIndex + 1);
try { try {
RandomAccessFile file = new RandomAccessFile(unknownFile, "r"); RandomAccessFile file = new RandomAccessFile(givenFile, "r");
switch(extension.toLowerCase()) { switch(extension.toLowerCase()) {
case "bin": { case "bin": {
LinkedHashMap<Integer, Integer> tracklist = findAudioTracks(file);
break; System.out.println(tracklist.size() + " Tracks (" + convertBytes(size / tracklist.size()) + " avg size)\n");
} System.out.println("Format Info");
case "flac": { readFormatChunk(file, 0x0C);
break; break;
} }
case "wav": case "wav":
@ -402,15 +691,15 @@ public class AudioExtractor {
break; break;
} }
case ASCII_ALIG: { case ASCII_ALIG: {
readAlignmentChunk(file, (int) i); i = readAlignmentChunk(file, (int) i) - 4;
break; break;
} }
case ASCII_seek: { case ASCII_seek: {
readx2stChunk(file, (int) i); i = readx2stChunk(file, (int) i) - 4;
break; break;
} }
case ASCII_x2st: { case ASCII_x2st: {
readx2stChunk(file, (int) i); i = readx2stChunk(file, (int) i) - 4;
break; break;
} }
} }
@ -572,8 +861,7 @@ public class AudioExtractor {
System.out.println(formatAddress(offset + 0x04, file.length()) + ":\t" + size + " Bytes" + (size > 1024 ? " (" + convertBytes(size) + ")" : "")); System.out.println(formatAddress(offset + 0x04, file.length()) + ":\t" + size + " Bytes" + (size > 1024 ? " (" + convertBytes(size) + ")" : ""));
} }
// 0x08 4B manufacturer code // 0x08 4 bytes manufacturer code
// parseField(file, offset + 0x08, 4, " manufacturer code");
byte[] bytes = new byte[4]; byte[] bytes = new byte[4];
file.seek(offset + 0x08); file.seek(offset + 0x08);
file.read(bytes); file.read(bytes);
@ -582,9 +870,7 @@ public class AudioExtractor {
if(relevantBytes == 3) if(relevantBytes == 3)
code = String.format("%02XH %02XH %02XH", bytes[1], bytes[2], bytes[3]); code = String.format("%02XH %02XH %02XH", bytes[1], bytes[2], bytes[3]);
else code = String.format("%02XH", bytes[3]); else code = String.format("%02XH", bytes[3]);
System.out.print(formatAddress(offset + 0x08, file.length()) + ":\t"); System.out.print(formatAddress(offset + 0x08, file.length()) + ":\t");
boolean found = false; boolean found = false;
InputStream filestream = AudioExtractor.class.getResourceAsStream("/manufacturer.csv"); InputStream filestream = AudioExtractor.class.getResourceAsStream("/manufacturer.csv");
InputStreamReader streamReader = new InputStreamReader(filestream); InputStreamReader streamReader = new InputStreamReader(filestream);
@ -604,7 +890,6 @@ public class AudioExtractor {
System.out.print("Unknown (" + code + ")"); System.out.print("Unknown (" + code + ")");
System.out.println(" manufacturer"); System.out.println(" manufacturer");
// 0x0C 4B product code
parseField(file, offset + 0x0C, 4, " product code"); parseField(file, offset + 0x0C, 4, " product code");
parseField(file, offset + 0x10, 4, "-nanosecond sample period"); parseField(file, offset + 0x10, 4, "-nanosecond sample period");
parseField(file, offset + 0x14, 4, " MIDI unity note"); parseField(file, offset + 0x14, 4, " MIDI unity note");
@ -652,7 +937,14 @@ public class AudioExtractor {
System.out.println(formatAddress(loopOffset + 0x08, file.length()) + ":\tLoop end @ sample " + loopEnd); System.out.println(formatAddress(loopOffset + 0x08, file.length()) + ":\tLoop end @ sample " + loopEnd);
parseField(file, loopOffset + 0x10, 4, " loop fraction"); parseField(file, loopOffset + 0x10, 4, " loop fraction");
parseField(file, loopOffset + 0x14, 4, " repeat count");
file.seek(loopOffset + 0x14);
file.read(bytes);
int repeat = littleEndianToInt(bytes);
if(repeat == 0)
System.out.println(formatAddress(loopOffset + 0x14, file.length()) + ":\tRepeat indefinitely");
else System.out.println(formatAddress(loopOffset + 0x14, file.length()) + ":\t" + repeat + "repeat count");
} }
System.out.println(); System.out.println();
@ -753,145 +1045,6 @@ public class AudioExtractor {
} }
/**
* Extracts audio files to the extract directory from package files located at the package directory.
* Also supports FLAC conversion for BGM files.
*
* @param packDirectory
* @param extractDirectory
* @param compressBGM
*/
public static void extract(File packDirectory, File extractDirectory, boolean compressBGM) {
// TODO Delete
System.out.println("packDirectory:\t" + packDirectory);
System.out.println("extractDirectory:\t" + extractDirectory);
// Identify target binary files
File[] packages = new File(packDirectory.toString()).listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".bin");
}
});
// Identify and extract audio within binary files
for(int i = 0; i < packages.length; i++) {
RandomAccessFile source;
LinkedHashMap<Integer, Integer> tracklist = null;
try {
source = new RandomAccessFile(packages[i], "r");
tracklist = findAudioTracks(source);
} catch (FileNotFoundException e) {
System.out.println("Binary file:\t" + packages[i] + "\n was not found. File skipped.");
continue;
} catch (IOException e) {
System.out.println("Binary file:\t" + packages[i] + "\n could not be read. File skipped.");
continue;
}
//TODO Delete
System.out.println("packages[" + i + "]:\t" + packages[i]);
File dir = new File(extractDirectory.getPath() + "/" + FilenameUtils.removeExtension(packages[i].getName()));
if(!dir.exists())
dir.mkdir();
// Extract and write WAV files in directory
Set<Integer> keys = tracklist.keySet();
int track = 1;
for(Integer key : keys) {
String name = String.format("track%04d", track);
{ // Extract WAV files
File wav = null;
try {
// create file for storing WAV data
wav = new File(dir.getPath() + "/" + name + ".wav");
if(!wav.exists())
wav.createNewFile();
//TODO Delete
System.out.println("Saving track " + String.format("%d (@0x%08X)", track, key) + " to " + wav + String.format(" (%d bytes)", tracklist.get(key)));
// write selection to file
try (
FileInputStream inStream = new FileInputStream(packages[i]);
FileOutputStream outStream = new FileOutputStream(wav)
) {
inStream.skipNBytes(key.intValue());
byte[] trackBytes = new byte[tracklist.get(key)];
int readBytes = inStream.read(trackBytes);
outStream.write(trackBytes, 0, readBytes);
} catch (IOException e) {
e.printStackTrace();
System.exit(3);
}
} catch (IOException e) {
System.out.println("An error occurred when attempting to write " + name + " to file:\t" + wav);
e.printStackTrace();
System.exit(3);
}
}
track++;
}
if(compressBGM && packages[i].getName().equals("pack_bgm.bin")) {
// find WAV files
File[] WAV_Files = dir.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".wav");
}
});
// convert WAV files to FLAC
FLAC_FileEncoder ffe = new FLAC_FileEncoder();
for(File wav : WAV_Files) {
try {
File flac = new File(dir.getPath() + "/" + FilenameUtils.removeExtension(wav.getName()) + ".flac");
if(!flac.exists())
flac.createNewFile();
//TODO Delete
System.out.println("Compressing WAV to FLAC:\t" + flac);
ffe.encode(wav, flac);
} catch (IOException e) {
System.out.println("An error occurred when attempting to write file.");
e.printStackTrace();
System.exit(3);
}
}
System.out.print("Deleting WAV files");
// delete WAV files
for(File wav : WAV_Files) {
boolean deleted = false;
while(!deleted) {
try {
Files.delete(wav.toPath());
deleted = true;
} catch(Exception e) {
System.out.print('.');
}
}
}
}
}
}
/** /**
* Packs WAV files located at the extract directory into BIN files at the package directory. * Packs WAV files located at the extract directory into BIN files at the package directory.
@ -1076,6 +1229,39 @@ public class AudioExtractor {
} }
/**
* Helper function to prompt user to select a file or folder.
*
* @param reader
* @return file or folder
*/
private static File retrieveFileOrFolder(BufferedReader reader, String prompt) {
File file = null;
String input = null;
boolean valid = false;
do {
System.out.print(prompt);
try {
input = reader.readLine();
if(input == null)
throw new IOException("Invalid file.");
file = new File(input);
if(file.exists()) {
valid = true;
} else System.out.println(Ansi.colorize("File does not exist."));
} catch (IOException e) {
System.out.println(Ansi.colorize("Invalid file."));
}
} while(!valid);
System.out.print(Ansi.colorize("\n"));
return file;
}
/** /**
* Helper function to prompt user to select a file. * Helper function to prompt user to select a file.
* *