klaudio 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli.js +32 -4
- package/src/scanner.js +40 -0
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ import { render, Box, Text, useInput, useApp } from "ink";
|
|
|
3
3
|
import Spinner from "ink-spinner";
|
|
4
4
|
import { PRESETS, EVENTS } from "./presets.js";
|
|
5
5
|
import { playSoundWithCancel, getWavDuration } from "./player.js";
|
|
6
|
-
import { getAvailableGames } from "./scanner.js";
|
|
6
|
+
import { getAvailableGames, getSystemSounds } from "./scanner.js";
|
|
7
7
|
import { install, uninstall, getExistingSounds } from "./installer.js";
|
|
8
8
|
import { getVgmstreamPath, findPackedAudioFiles, extractToWav } from "./extractor.js";
|
|
9
9
|
import { extractUnityResource } from "./unity.js";
|
|
@@ -160,6 +160,7 @@ const PresetScreen = ({ onNext, onBack }) => {
|
|
|
160
160
|
label: `${p.icon} ${p.name} — ${p.description}`,
|
|
161
161
|
value: id,
|
|
162
162
|
})),
|
|
163
|
+
{ label: "🔔 System sounds — use built-in OS notification sounds", value: "_system" },
|
|
163
164
|
{ label: "🕹️ Scan local games — find sounds from your Steam & Epic Games library", value: "_scan" },
|
|
164
165
|
{ label: "📁 Custom files — provide your own sound files", value: "_custom" },
|
|
165
166
|
];
|
|
@@ -757,6 +758,16 @@ const GameSoundsScreen = ({ game, sounds, onSelectSound, onDone, onBack }) => {
|
|
|
757
758
|
|
|
758
759
|
// Phase 1: Browse and pick files (auto-preview plays on highlight)
|
|
759
760
|
const filterLower = filter.toLowerCase();
|
|
761
|
+
|
|
762
|
+
// Parse duration filter: "10s", "<10s", "< 10s", ">5s", "> 5s", "<=10s", ">=5s"
|
|
763
|
+
const durationFilter = useMemo(() => {
|
|
764
|
+
const m = filter.match(/^\s*(<|>|<=|>=)?\s*(\d+(?:\.\d+)?)\s*s\s*$/);
|
|
765
|
+
if (!m) return null;
|
|
766
|
+
const op = m[1] || "<=";
|
|
767
|
+
const val = parseFloat(m[2]);
|
|
768
|
+
return { op, val };
|
|
769
|
+
}, [filter]);
|
|
770
|
+
|
|
760
771
|
const allFileItems = categoryFiles.map((f) => {
|
|
761
772
|
const dur = fileDurations[f.path];
|
|
762
773
|
const durStr = dur != null ? ` (${dur}s${dur > MAX_PLAY_SECONDS ? `, preview ${MAX_PLAY_SECONDS}s` : ""})` : "";
|
|
@@ -768,11 +779,22 @@ const GameSoundsScreen = ({ game, sounds, onSelectSound, onDone, onBack }) => {
|
|
|
768
779
|
label: `${catTag}${name}${durStr}`,
|
|
769
780
|
usedTag: usedFor ? ` ← ${usedFor.join(", ")}` : null,
|
|
770
781
|
value: f.path,
|
|
782
|
+
_dur: dur,
|
|
771
783
|
};
|
|
772
784
|
});
|
|
773
785
|
|
|
774
786
|
const filteredFiles = filter
|
|
775
|
-
?
|
|
787
|
+
? durationFilter
|
|
788
|
+
? allFileItems.filter((i) => {
|
|
789
|
+
if (i._dur == null) return false;
|
|
790
|
+
const { op, val } = durationFilter;
|
|
791
|
+
if (op === "<") return i._dur < val;
|
|
792
|
+
if (op === ">") return i._dur > val;
|
|
793
|
+
if (op === "<=") return i._dur <= val;
|
|
794
|
+
if (op === ">=") return i._dur >= val;
|
|
795
|
+
return true;
|
|
796
|
+
})
|
|
797
|
+
: allFileItems.filter((i) => i.label.toLowerCase().includes(filterLower))
|
|
776
798
|
: allFileItems;
|
|
777
799
|
|
|
778
800
|
const fileItems = [
|
|
@@ -797,12 +819,12 @@ const GameSoundsScreen = ({ game, sounds, onSelectSound, onDone, onBack }) => {
|
|
|
797
819
|
),
|
|
798
820
|
filter
|
|
799
821
|
? h(Box, { marginLeft: 4 },
|
|
800
|
-
h(Text, { color: "yellow" }, "Filter: "),
|
|
822
|
+
h(Text, { color: "yellow" }, durationFilter ? "Duration: " : "Filter: "),
|
|
801
823
|
h(Text, { bold: true }, filter),
|
|
802
824
|
h(Text, { dimColor: true }, ` (${filteredFiles.length} match${filteredFiles.length !== 1 ? "es" : ""})`),
|
|
803
825
|
)
|
|
804
826
|
: categoryFiles.length > 15
|
|
805
|
-
? h(Text, { dimColor: true, marginLeft: 4 }, "Type to filter...")
|
|
827
|
+
? h(Text, { dimColor: true, marginLeft: 4 }, "Type to filter... (e.g. <10s, >5s)")
|
|
806
828
|
: null,
|
|
807
829
|
fileItems.length > 0
|
|
808
830
|
? h(Box, { marginLeft: 2 },
|
|
@@ -1417,6 +1439,12 @@ const InstallApp = () => {
|
|
|
1417
1439
|
onNext: (id) => {
|
|
1418
1440
|
if (id === "_music") {
|
|
1419
1441
|
setScreen(SCREEN.MUSIC_MODE);
|
|
1442
|
+
} else if (id === "_system") {
|
|
1443
|
+
getSystemSounds().then((files) => {
|
|
1444
|
+
const catFiles = categorizeLooseFiles(files);
|
|
1445
|
+
setSelectedGame({ name: "System Sounds", path: "", files: catFiles, fileCount: catFiles.length, hasAudio: catFiles.length > 0 });
|
|
1446
|
+
setScreen(SCREEN.GAME_SOUNDS);
|
|
1447
|
+
});
|
|
1420
1448
|
} else if (id === "_scan") {
|
|
1421
1449
|
setScreen(SCREEN.GAME_PICK);
|
|
1422
1450
|
} else if (id === "_custom") {
|
package/src/scanner.js
CHANGED
|
@@ -379,6 +379,46 @@ export async function scanForGames(onProgress, onGameFound) {
|
|
|
379
379
|
return games;
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Discover system sounds (Windows Media, macOS system sounds, Linux sound themes).
|
|
384
|
+
* Returns an array of { path, name, dir } matching the game file format.
|
|
385
|
+
*/
|
|
386
|
+
export async function getSystemSounds() {
|
|
387
|
+
const os = platform();
|
|
388
|
+
const results = [];
|
|
389
|
+
|
|
390
|
+
const dirs = [];
|
|
391
|
+
if (os === "win32") {
|
|
392
|
+
dirs.push("C:/Windows/Media", "C:/Windows/Media/dm");
|
|
393
|
+
} else if (os === "darwin") {
|
|
394
|
+
dirs.push("/System/Library/Sounds");
|
|
395
|
+
} else {
|
|
396
|
+
// Linux: common sound theme locations
|
|
397
|
+
dirs.push(
|
|
398
|
+
"/usr/share/sounds/freedesktop/stereo",
|
|
399
|
+
"/usr/share/sounds/gnome/default/alerts",
|
|
400
|
+
"/usr/share/sounds/ubuntu/stereo",
|
|
401
|
+
"/usr/share/sounds",
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
for (const dir of dirs) {
|
|
406
|
+
if (!(await dirExists(dir))) continue;
|
|
407
|
+
try {
|
|
408
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
409
|
+
for (const entry of entries) {
|
|
410
|
+
if (!entry.isFile()) continue;
|
|
411
|
+
const ext = extname(entry.name).toLowerCase();
|
|
412
|
+
if (AUDIO_EXTENSIONS.has(ext)) {
|
|
413
|
+
results.push({ path: join(dir, entry.name), name: entry.name, dir });
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
} catch { /* skip */ }
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return results;
|
|
420
|
+
}
|
|
421
|
+
|
|
382
422
|
/**
|
|
383
423
|
* Get a flat list of all found game directories.
|
|
384
424
|
*/
|