@archhaze24/korg 0.0.0-stage → 0.1.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -2
  3. package/bin/korg.js +99 -0
  4. package/package.json +39 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kirill Belolipetsky
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,100 @@
1
- # Temporary Holding Version
1
+ # korg
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Kirby's music organizer. Turns a FLAC library tagged with MusicBrainz Picard into a small Opus library.
4
+
5
+ One `korg run` does four steps:
6
+
7
+ 1. **covers**: resize and clean `cover.jpg`
8
+ 2. **lyrics**: download synced lyrics from [LRCLIB](https://lrclib.net) as `.lrc` files
9
+ 3. **encode**: FLAC to Opus (160 kbit/s VBR), MP3 is copied, covers, `.lrc` and booklets are copied along
10
+ 4. **gain**: ReplayGain for both libraries
11
+
12
+ Works on Linux, macOS and Windows.
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ brew install archhaze24/tap/korg # macOS, Linux
18
+ scoop bucket add archhaze24 https://github.com/archhaze24/scoop-bucket && scoop install korg # Windows
19
+ npx @archhaze24/korg --help # any, via npm
20
+ cargo install korg-cli # from source, Rust 1.89+
21
+ ```
22
+
23
+ Prebuilt archives are on the [releases page](https://github.com/archhaze24/korg/releases).
24
+
25
+ korg calls two external tools, so install them too (Homebrew installs them with the formula):
26
+
27
+ - [`opusenc`](https://opus-codec.org/downloads/) (opus-tools)
28
+ - [`rsgain`](https://github.com/complexlogic/rsgain) with a preset named `opus`
29
+
30
+ The gain step runs `rsgain easy -p opus`, so create `opus.ini` in rsgain's presets folder:
31
+ `~/.config/rsgain/presets/` (Linux), `~/Library/rsgain/presets/` (macOS), `%USERPROFILE%\.rsgain\presets\` (Windows).
32
+ This is enough to get R128 tags in Opus files:
33
+
34
+ ```ini
35
+ [Opus]
36
+ OpusMode=r
37
+ ```
38
+
39
+ rsgain ignores `[Opus]` in a folder that mixes Opus and MP3 and writes plain ReplayGain tags there. Use
40
+ `[Global]` instead of `[Opus]` if you want R128 everywhere.
41
+
42
+ `korg doctor` checks the tools and the preset.
43
+
44
+ ## Usage
45
+
46
+ ```sh
47
+ korg run # ./toencode -> ./encoded
48
+ korg run -s ~/Music/toencode -d ~/Music/encoded
49
+ korg run --skip lyrics,gain # leave steps out
50
+ korg run --dry-run # show what would happen
51
+ korg covers | lyrics | encode | gain # one step only
52
+ korg restore-names # undo renames after a crash
53
+ ```
54
+
55
+ Source and destination can also come from `KORG_SOURCE` and `KORG_DEST`. `-j` sets parallel encodes per album
56
+ (default: CPU cores minus 2), `-v` prints every checked file.
57
+
58
+ Don't run two instances on the same folders, and don't edit the source in Picard meanwhile.
59
+
60
+ ## What the steps do
61
+
62
+ **covers.** `cover.jpg`, `cover.jpeg`, `front.jpg` and `folder.jpg` (any case) become `cover.jpg`. A side over 2000 px
63
+ or an EXIF rotation means resize and re-encode (quality 90, progressive); a heavy file is re-encoded only if that
64
+ saves 10% or more. Everything else just loses its metadata. Several candidates in one folder: reported, skipped.
65
+
66
+ **lyrics.** For each FLAC/MP3, LRCLIB is asked by title, artist, album and duration, then by title and artist only.
67
+ A match needs the same title and artist (ignoring case and punctuation) and a duration within 2 s; the closest
68
+ duration wins, the album only breaks ties. Only lyrics with timestamps are saved. An existing `.lrc` with
69
+ timestamps is kept, one without is deleted and searched again. To search again, delete the `.lrc`.
70
+
71
+ **encode.** Albums run one after another, tracks inside an album in parallel. Tags and artwork are kept; ReplayGain
72
+ and encoder tags are dropped, and the FLAC is never modified. A duration that differs by more than 0.1 s is an
73
+ error (`--allow-duration-mismatch` makes it a warning). An album is skipped if its folder already exists in the
74
+ destination, nothing else is checked: delete the folder to redo it. Covers, `.lrc` and `booklet.pdf` are copied for
75
+ every album, skipped ones too.
76
+
77
+ **gain.** `rsgain easy -m MAX <source>`, then `rsgain easy -m MAX -p opus <dest>`. Both always run.
78
+
79
+ ## Long paths
80
+
81
+ If `opusenc` says a path was not found, the track is copied to a short temporary path and encoded there. Such
82
+ albums are renamed to short names for the rsgain run and renamed back. The renames are journaled; Ctrl+C and
83
+ errors restore them. After a crash the next run refuses to start until `korg restore-names`.
84
+
85
+ ## Exit codes
86
+
87
+ `0` ok, `1` some files failed (see `<dest>/korg-report.json`), `2` bad arguments or setup, `130` interrupted.
88
+ Output goes to stderr; `NO_COLOR` is respected.
89
+
90
+ ## Development
91
+
92
+ ```sh
93
+ cargo fmt --check && cargo clippy --all-targets -- -D warnings && cargo test
94
+ ```
95
+
96
+ `tests/e2e.rs` needs `opusenc`, `rsgain`, `flac` and `lame` in `PATH` and skips itself without them.
97
+
98
+ ## License
99
+
100
+ MIT
package/bin/korg.js ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ // Launcher: runs the native binary shipped by the matching platform package.
3
+ // build.mjs copies this script to bin/korg.js of the launcher package.
4
+ "use strict";
5
+
6
+ const { spawn } = require("node:child_process");
7
+ const os = require("node:os");
8
+ const path = require("node:path");
9
+
10
+ const NAME = "korg";
11
+
12
+ // Candidate packages per platform, in order of preference. The musl build is fully static,
13
+ // so on Linux (x64 and arm64) it is also a valid fallback when the glibc package was not installed.
14
+ const PLATFORMS = {
15
+ "linux-x64": ["@archhaze24/korg-linux-x64", "@archhaze24/korg-linux-x64-musl"],
16
+ "linux-arm64": ["@archhaze24/korg-linux-arm64", "@archhaze24/korg-linux-arm64-musl"],
17
+ "darwin-x64": ["@archhaze24/korg-darwin-x64"],
18
+ "darwin-arm64": ["@archhaze24/korg-darwin-arm64"],
19
+ "win32-x64": ["@archhaze24/korg-win32-x64"],
20
+ };
21
+
22
+ // True on a musl-based Linux (Alpine etc.): glibc reports its version, musl does not.
23
+ function isMusl() {
24
+ if (process.platform !== "linux") return false;
25
+ try {
26
+ const report = process.report.getReport();
27
+ const header = typeof report === "string" ? JSON.parse(report).header : report.header;
28
+ if (header && "glibcVersionRuntime" in header) return !header.glibcVersionRuntime;
29
+ } catch {
30
+ // fall through to the file check
31
+ }
32
+ try {
33
+ return require("node:fs").readdirSync("/lib").some((name) => name.startsWith("ld-musl-"));
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ function binaryPath() {
40
+ const key = `${process.platform}-${process.arch}`;
41
+ const candidates = PLATFORMS[key];
42
+ if (!candidates) {
43
+ throw new Error(
44
+ `${NAME} has no prebuilt binary for ${key}. Supported: ${Object.keys(PLATFORMS).join(", ")}. ` +
45
+ "Build from source instead: cargo install korg-cli",
46
+ );
47
+ }
48
+ const ordered = key.startsWith("linux-") && isMusl() ? [...candidates].reverse() : candidates;
49
+ const exe = process.platform === "win32" ? `${NAME}.exe` : NAME;
50
+ for (const pkg of ordered) {
51
+ try {
52
+ const manifest = require.resolve(`${pkg}/package.json`);
53
+ return path.join(path.dirname(manifest), "bin", exe);
54
+ } catch {
55
+ // not installed, try the next candidate
56
+ }
57
+ }
58
+ throw new Error(
59
+ `The platform package ${ordered[0]} is not installed (npm skipped the optional dependency). ` +
60
+ "Reinstall without --omit=optional, or run: cargo install korg-cli",
61
+ );
62
+ }
63
+
64
+ let binary;
65
+ try {
66
+ binary = binaryPath();
67
+ } catch (error) {
68
+ console.error(`${NAME}: ${error.message}`);
69
+ process.exit(1);
70
+ }
71
+
72
+ const child = spawn(binary, process.argv.slice(2), { stdio: "inherit" });
73
+
74
+ // Forward termination signals so the native process does not outlive the launcher: korg
75
+ // restores renamed album folders when it is interrupted, so it must get the chance to run
76
+ // its cleanup. Windows only supports SIGINT/SIGBREAK; the others are skipped.
77
+ const FORWARDED = process.platform === "win32" ? ["SIGINT", "SIGBREAK"] : ["SIGINT", "SIGTERM", "SIGHUP"];
78
+ // On a POSIX terminal Ctrl-C already reaches the child through the tty (same foreground process
79
+ // group), so SIGINT is only swallowed here (the handler keeps the launcher alive until the child
80
+ // exits) and not forwarded a second time.
81
+ const TTY_SIGINT = process.platform !== "win32" && process.stdin.isTTY === true;
82
+ for (const signal of FORWARDED) {
83
+ process.on(signal, () => {
84
+ if (signal === "SIGINT" && TTY_SIGINT) return;
85
+ if (child.exitCode === null && child.signalCode === null) child.kill(signal);
86
+ });
87
+ }
88
+
89
+ child.on("error", (error) => {
90
+ console.error(`${NAME}: could not run ${binary}: ${error.message}`);
91
+ process.exit(1);
92
+ });
93
+
94
+ child.on("exit", (code, signal) => {
95
+ if (signal) {
96
+ process.exit(128 + (os.constants.signals[signal] ?? 0));
97
+ }
98
+ process.exit(code ?? 1);
99
+ });
package/package.json CHANGED
@@ -1,6 +1,41 @@
1
1
  {
2
2
  "name": "@archhaze24/korg",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Kirby's music organizer: covers, synced lyrics, FLAC to Opus, ReplayGain",
5
+ "keywords": [
6
+ "music",
7
+ "flac",
8
+ "opus",
9
+ "replaygain",
10
+ "lrc",
11
+ "lrclib",
12
+ "picard"
13
+ ],
14
+ "license": "MIT",
15
+ "author": "archhaze24",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/archhaze24/korg.git"
19
+ },
20
+ "homepage": "https://github.com/archhaze24/korg#readme",
21
+ "bin": {
22
+ "korg": "bin/korg.js"
23
+ },
24
+ "files": [
25
+ "bin",
26
+ "README.md",
27
+ "LICENSE"
28
+ ],
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "optionalDependencies": {
33
+ "@archhaze24/korg-linux-x64": "0.1.0",
34
+ "@archhaze24/korg-linux-x64-musl": "0.1.0",
35
+ "@archhaze24/korg-linux-arm64": "0.1.0",
36
+ "@archhaze24/korg-linux-arm64-musl": "0.1.0",
37
+ "@archhaze24/korg-darwin-x64": "0.1.0",
38
+ "@archhaze24/korg-darwin-arm64": "0.1.0",
39
+ "@archhaze24/korg-win32-x64": "0.1.0"
40
+ }
41
+ }