mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-04-27 23:35:51 +00:00
improve audio support
This commit is contained in:
parent
d1acb6a5ce
commit
cb28b66dad
5587 changed files with 4538 additions and 2846 deletions
113
scripts/convert-wav.ts
Normal file
113
scripts/convert-wav.ts
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import fs from "node:fs/promises";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
const FFMPEG_PATH = process.env.FFMPEG_PATH || "ffmpeg";
|
||||
|
||||
/**
|
||||
* Find all .wav files in `docs/base` and convert them to Opus OGG.
|
||||
* Converted .ogg files are placed alongside the originals (analogous to
|
||||
* how convert-dts.ts places .glb files alongside .dts files). The manifest
|
||||
* ignores .ogg files; audioToUrl() swaps the extension at resolution time.
|
||||
*/
|
||||
async function run({
|
||||
onlyNew,
|
||||
bitrate,
|
||||
concurrency,
|
||||
}: {
|
||||
onlyNew: boolean;
|
||||
bitrate: string;
|
||||
concurrency: number;
|
||||
}) {
|
||||
const inputFiles: string[] = [];
|
||||
for await (const wavFile of fs.glob("docs/base/**/*.{wav,WAV}")) {
|
||||
const oggFile = wavFile.replace(/\.wav$/i, ".ogg");
|
||||
if (onlyNew) {
|
||||
try {
|
||||
await fs.stat(oggFile);
|
||||
continue; // .ogg already exists, skip
|
||||
} catch {}
|
||||
}
|
||||
inputFiles.push(wavFile);
|
||||
}
|
||||
|
||||
if (inputFiles.length === 0) {
|
||||
console.log("No .wav files to convert.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Converting ${inputFiles.length} .wav file(s) to Opus OGG (${bitrate})…`,
|
||||
);
|
||||
|
||||
let completed = 0;
|
||||
let failed = 0;
|
||||
|
||||
async function convert(wavFile: string) {
|
||||
const oggFile = wavFile.replace(/\.wav$/i, ".ogg");
|
||||
try {
|
||||
execFileSync(
|
||||
FFMPEG_PATH,
|
||||
[
|
||||
"-y",
|
||||
"-i",
|
||||
wavFile,
|
||||
"-c:a",
|
||||
"libopus",
|
||||
"-b:a",
|
||||
bitrate,
|
||||
"-vn",
|
||||
oggFile,
|
||||
],
|
||||
{ stdio: "pipe" },
|
||||
);
|
||||
completed++;
|
||||
} catch (err: any) {
|
||||
failed++;
|
||||
const stderr = err.stderr?.toString().trim();
|
||||
console.error(` FAILED: ${wavFile}`);
|
||||
if (stderr) {
|
||||
// Show just the last line of ffmpeg output (the actual error).
|
||||
const lines = stderr.split("\n");
|
||||
console.error(` ${lines[lines.length - 1]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process in batches for parallelism.
|
||||
for (let i = 0; i < inputFiles.length; i += concurrency) {
|
||||
const batch = inputFiles.slice(i, i + concurrency);
|
||||
await Promise.all(batch.map(convert));
|
||||
const total = Math.min(i + concurrency, inputFiles.length);
|
||||
process.stdout.write(`\r ${total}/${inputFiles.length}`);
|
||||
}
|
||||
process.stdout.write("\n");
|
||||
|
||||
console.log(`Done: ${completed} converted, ${failed} failed.`);
|
||||
}
|
||||
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
new: {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
short: "n",
|
||||
},
|
||||
bitrate: {
|
||||
type: "string",
|
||||
default: "64k",
|
||||
short: "b",
|
||||
},
|
||||
concurrency: {
|
||||
type: "string",
|
||||
default: "8",
|
||||
short: "j",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
run({
|
||||
onlyNew: values.new!,
|
||||
bitrate: values.bitrate!,
|
||||
concurrency: parseInt(values.concurrency!, 10) || 8,
|
||||
});
|
||||
|
|
@ -15,6 +15,7 @@ const baseDir = process.env.BASE_DIR || "docs/base";
|
|||
const ignoreList = ignore().add(`
|
||||
.DS_Store
|
||||
*.glb
|
||||
*.ogg
|
||||
`);
|
||||
|
||||
type SourceTuple =
|
||||
|
|
|
|||
31
scripts/sum-filesize.ts
Normal file
31
scripts/sum-filesize.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import fs from "node:fs/promises";
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
function formatSize(bytes: number): string {
|
||||
if (bytes >= 1e9) return `${(bytes / 1e9).toFixed(2)} GB`;
|
||||
if (bytes >= 1e6) return `${(bytes / 1e6).toFixed(2)} MB`;
|
||||
if (bytes >= 1e3) return `${(bytes / 1e3).toFixed(2)} KB`;
|
||||
return `${bytes} B`;
|
||||
}
|
||||
|
||||
const { positionals } = parseArgs({ allowPositionals: true });
|
||||
|
||||
if (positionals.length === 0) {
|
||||
console.error("Usage: tsx scripts/sum-filesize.ts <glob> [glob...]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
let count = 0;
|
||||
|
||||
for (const pattern of positionals) {
|
||||
for await (const file of fs.glob(pattern)) {
|
||||
const stat = await fs.stat(file);
|
||||
if (stat.isFile()) {
|
||||
total += stat.size;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`${count} files, ${formatSize(total)}`);
|
||||
Loading…
Add table
Add a link
Reference in a new issue