Skip to content

Commit

Permalink
feat: Add mp3 and flac audio exporting options, add automatic cleanup…
Browse files Browse the repository at this point in the history
… of processing files
  • Loading branch information
yoroshikun committed Oct 12, 2020
1 parent 1679fb9 commit 7883302
Show file tree
Hide file tree
Showing 29 changed files with 178 additions and 45 deletions.
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package-lock.json
node_modules
Game Files/*
Tools/Decoding
input/*
output/*
processing/*
25 changes: 19 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
GenshinImpact_Data/StreamingAssets/Audio/GeneratedSoundBanks/Windows
```

Move the .pck files you want to extract into the `Game Files` folder. The files *must* be directly inside the folder - no subdirectories.
Move the .pck files you want to extract into the `input` folder. The files *must* be directly inside the folder - no subdirectories.

2. install dependencies

Expand All @@ -22,18 +22,31 @@ npm install
node decode.js
```

The files in `./Game Files` will be converted to .wav files inside `./WAV`.
The files in `./input` will be converted to .wav files inside `./output`.

## Cleanup
## Options

Leftover files are placed inside `./Tools/Decoding` - Remember to delete these after you're done, as they use a lot of storage.
You can pass an optional argument to export the audio in different formats.

Valid arguments are `flac`, `mp3` and `flacandmp3`

```bash
node decode.js flac
```

Encoding details

```
flac: lossless, 16bit, 44100 sample rate
mp3: 320kbit/s, 44100 sample rate
```

### Todo:

- [ ] Clean up dependencies

- [ ] Multi-export support (FLAC, MP3, etc)
- [x] Multi-export support (FLAC, MP3, etc)

- [ ] Automatically remove processed files once complete
- [x] Automatically remove processed files once complete

- [ ] More?
164 changes: 129 additions & 35 deletions decode.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,52 +10,146 @@ const path = require("path");
const mkdirp = require("mkdirp");
const util = require("util");
const exec = util.promisify(require("child_process").execFile);
const { rmraf } = require("./helpers/rmraf");

const toolsDir = path.join(".", "Tools");
const quickBMS = path.join(toolsDir, "quickbms.exe");
const waveScanBMS = path.join(toolsDir, "wavescan.bms");
const vgmstream = path.join(toolsDir, "vgmstream-cli.exe");
const libsDir = path.join(".", "libs");
const quickBMS = path.join(libsDir, "quickbms.exe");
const waveScanBMS = path.join(libsDir, "wavescan.bms");
const vgmstream = path.join(libsDir, "vgmstream-cli.exe");
const ffmpeg = path.join(libsDir, "ffmpeg.exe");

const inputDir = path.join(".", "input");
const baseProcessingDir = path.join(".", "processing");
const outputDir = path.join(".", "output");
const wavOutputDir = path.join(outputDir, "WAV");
const flacOutputDir = path.join(outputDir, "FLAC");
const mp3OutputDir = path.join(outputDir, "MP3");

const extraExportArg = process.argv[2] || 0;

const convertWav = async (subWavOutputDir, processingDir, createdFile) => {
const outputFile = path.join(
subWavOutputDir,
createdFile.split(".")[0] + ".wav"
);

const createdFilePath = path.join(processingDir, createdFile);
await exec(vgmstream, ["-o", outputFile, createdFilePath]);
};

const convertFlac = async (subOutputDir, wavDir, createdFile) => {
const outputFile = path.join(
subOutputDir,
createdFile.split(".")[0] + ".flac"
);

const wavFilePath = path.join(wavDir, createdFile.split(".")[0] + ".wav");
await exec(ffmpeg, [
"-i",
wavFilePath,
"-y",
"-af",
"aformat=s16:44100",
outputFile,
]);
};

const convertMp3 = async (subOutputDir, wavDir, createdFile) => {
const outputFile = path.join(
subOutputDir,
createdFile.split(".")[0] + ".mp3"
);

const wavFilePath = path.join(wavDir, createdFile.split(".")[0] + ".wav");
await exec(ffmpeg, [
"-i",
wavFilePath,
"-y",
"-ar",
"44100",
"-b:a",
"320k",
outputFile,
]);
};

const main = async () => {
const gameFiles = fs
.readdirSync("Game Files")
const pckFiles = fs
.readdirSync(inputDir)
.filter((f) => f.toLowerCase().endsWith(".pck"));

const baseProcessingFolder = path.join(".", "Tools", "Decoding");
console.log(`Found ${gameFiles.length} Game Files`);
for (gameFile of gameFiles) {
const inputFolder = path.join(".", "Game Files");
const inputFile = path.join(inputFolder, gameFile);
const processingFolder = path.join(
baseProcessingFolder,
gameFile.split(".")[0]
);
await mkdirp(processingFolder);

const { stdout, stderr } = await exec(quickBMS, [
waveScanBMS,
inputFile,
processingFolder,
]);

const createdFiles = fs.readdirSync(processingFolder);
const outputFolder = path.join(".", "WAV", gameFile.split(".")[0]);
await mkdirp(outputFolder);
console.info(`Found ${pckFiles.length} pck files`);

for (pckFile of pckFiles) {
const inputFile = path.join(inputDir, pckFile);
const processingDir = path.join(baseProcessingDir, pckFile.split(".")[0]);

await mkdirp(processingDir);

await exec(quickBMS, [waveScanBMS, inputFile, processingDir]);

const createdFiles = fs.readdirSync(processingDir);

const subWavOutputDir = path.join(wavOutputDir, pckFile.split(".")[0]);
const subFlacOutputDir = path.join(flacOutputDir, pckFile.split(".")[0]);
const subMp3OutputDir = path.join(mp3OutputDir, pckFile.split(".")[0]);

await mkdirp(subWavOutputDir);

if (extraExportArg === "flac" || extraExportArg === "flacandmp3") {
await mkdirp(subFlacOutputDir);
}

if (extraExportArg === "mp3" || extraExportArg === "flacandmp3") {
await mkdirp(subMp3OutputDir);
}

for (createdFile of createdFiles) {
const outputFile = path.join(
outputFolder,
createdFile.split(".")[0] + ".wav"
);
await convertWav(subWavOutputDir, processingDir, createdFile);

const createdDirFile = path.join(processingFolder, createdFile);
await exec(vgmstream, ["-o", outputFile, createdDirFile]);
switch (extraExportArg) {
case "flac":
await convertFlac(subFlacOutputDir, subWavOutputDir, createdFile);

console.log(
`${gameFile} -> ${createdFile} -> ${createdFile.split(".")[0] + ".wav"}`
);
console.log(
`${pckFile} -> ${createdFile} -> ${
createdFile.split(".")[0]
}.wav -> ${createdFile.split(".")[0]}.flac`
);
break;
case "mp3":
await convertMp3(subMp3OutputDir, subWavOutputDir, createdFile);

console.log(
`${pckFile} -> ${createdFile} -> ${
createdFile.split(".")[0]
}.wav -> ${createdFile.split(".")[0]}.mp3`
);
break;
case "flacandmp3":
await Promise.all([
convertFlac(subFlacOutputDir, subWavOutputDir, createdFile),
convertMp3(subMp3OutputDir, subWavOutputDir, createdFile),
]);

console.log(
`${pckFile} -> ${createdFile} -> ${
createdFile.split(".")[0]
}.wav -> ${createdFile.split(".")[0]}.flac -> ${
createdFile.split(".")[0]
}.mp3`
);
break;
default:
console.log(
`${pckFile} -> ${createdFile} -> ${createdFile.split(".")[0]}.wav`
);
break;
}
}
}

await rmraf(baseProcessingDir);
};

main();
17 changes: 17 additions & 0 deletions helpers/rmraf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');
const readdir = promisify(fs.readdir);
const rmdir = promisify(fs.rmdir);
const unlink = promisify(fs.unlink);

exports.rmraf = rmraf = async (dir) => {
let entries = await readdir(dir, { withFileTypes: true });

await Promise.all(entries.map(entry => {
let fullPath = path.join(dir, entry.name);
return entry.isDirectory() ? rmraf(fullPath) : unlink(fullPath);
}));

await rmdir(dir);
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "genshin-audio-extractor",
"version": "0.0.2",
"description": "Convert Genshin Impact .pck files into .wav",
"version": "0.1.0",
"description": "Convert Genshin Impact .pck files into various workable files",
"main": "decode.js",
"dependencies": {
"mkdirp": "^1.0.4"
Expand Down
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==

0 comments on commit 7883302

Please sign in to comment.