From 80760bc534cc6914348713299c8fbdb457c0f00e Mon Sep 17 00:00:00 2001 From: Nes370 Date: Thu, 25 Jul 2024 00:08:49 -0700 Subject: [PATCH] Update AudioExtractor.java Finished Extract function rewrite. --- src/goblincave/gitea/nes/AudioExtractor.java | 530 +++++++++++++------ 1 file changed, 358 insertions(+), 172 deletions(-) diff --git a/src/goblincave/gitea/nes/AudioExtractor.java b/src/goblincave/gitea/nes/AudioExtractor.java index 04d6397..a3d236f 100644 --- a/src/goblincave/gitea/nes/AudioExtractor.java +++ b/src/goblincave/gitea/nes/AudioExtractor.java @@ -161,24 +161,37 @@ public class AudioExtractor { if(args.length == 0) { // no arguments provided switch(operation) { - case "identify": + + case "identify": { File unknownFile = retrieveFile(reader, "Please enter the path of a file: " + ANSI_BEIGE); identify(unknownFile); 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; - case "package": // pack + } + + case "convert": { + + } + + case "package": {// pack break; + } case "patch": // patch File waveFile = retrieveWave(reader); patch(waveFile); @@ -195,6 +208,9 @@ public class AudioExtractor { } } + + System.out.println("Press Enter to continue..."); + try { reader.readLine(); } catch (Exception e) {} } @@ -280,7 +296,281 @@ 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 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 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 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 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. * @@ -350,15 +640,15 @@ public class AudioExtractor { /** * 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 - String name = unknownFile.getName(); + String name = givenFile.getName(); 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"); // file extension @@ -368,14 +658,13 @@ public class AudioExtractor { extension = name.substring(extIndex + 1); try { - RandomAccessFile file = new RandomAccessFile(unknownFile, "r"); + RandomAccessFile file = new RandomAccessFile(givenFile, "r"); switch(extension.toLowerCase()) { case "bin": { - - break; - } - case "flac": { - + LinkedHashMap tracklist = findAudioTracks(file); + System.out.println(tracklist.size() + " Tracks (" + convertBytes(size / tracklist.size()) + " avg size)\n"); + System.out.println("Format Info"); + readFormatChunk(file, 0x0C); break; } case "wav": @@ -402,15 +691,15 @@ public class AudioExtractor { break; } case ASCII_ALIG: { - readAlignmentChunk(file, (int) i); + i = readAlignmentChunk(file, (int) i) - 4; break; } case ASCII_seek: { - readx2stChunk(file, (int) i); + i = readx2stChunk(file, (int) i) - 4; break; } case ASCII_x2st: { - readx2stChunk(file, (int) i); + i = readx2stChunk(file, (int) i) - 4; break; } } @@ -572,8 +861,7 @@ public class AudioExtractor { System.out.println(formatAddress(offset + 0x04, file.length()) + ":\t" + size + " Bytes" + (size > 1024 ? " (" + convertBytes(size) + ")" : "")); } - // 0x08 4B manufacturer code -// parseField(file, offset + 0x08, 4, " manufacturer code"); + // 0x08 4 bytes manufacturer code byte[] bytes = new byte[4]; file.seek(offset + 0x08); file.read(bytes); @@ -582,9 +870,7 @@ public class AudioExtractor { if(relevantBytes == 3) code = String.format("%02XH %02XH %02XH", bytes[1], bytes[2], bytes[3]); else code = String.format("%02XH", bytes[3]); - System.out.print(formatAddress(offset + 0x08, file.length()) + ":\t"); - boolean found = false; InputStream filestream = AudioExtractor.class.getResourceAsStream("/manufacturer.csv"); InputStreamReader streamReader = new InputStreamReader(filestream); @@ -603,8 +889,7 @@ public class AudioExtractor { if(!found) System.out.print("Unknown (" + code + ")"); System.out.println(" manufacturer"); - - // 0x0C 4B product code + parseField(file, offset + 0x0C, 4, " product code"); parseField(file, offset + 0x10, 4, "-nanosecond sample period"); 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); 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(); @@ -752,145 +1044,6 @@ public class AudioExtractor { // else return String.format("%04X", address); } - - /** - * 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 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 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('.'); - } - } - } - - } - - } - - } /** @@ -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. *