tserato 0.1.0 → 0.1.10
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/.github/workflows/release.yml +53 -0
- package/dist/builder.d.ts +2 -1
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +57 -55
- package/dist/encoders/v2/v2Mp3Encoder.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/model/crate.d.ts +2 -4
- package/dist/model/crate.d.ts.map +1 -1
- package/dist/model/crate.js +7 -37
- package/dist/model/track.d.ts.map +1 -1
- package/dist/model/track.js +3 -1
- package/dist/util.d.ts +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +10 -28
- package/eslint.config.js +50 -0
- package/package.json +7 -2
- package/src/builder.ts +198 -0
- package/src/encoders/baseEncoder.ts +8 -0
- package/src/encoders/serato_tags.ts +4 -0
- package/src/encoders/utils.ts +21 -0
- package/src/encoders/v2/v2Mp3Encoder.ts +230 -0
- package/src/index.ts +6 -0
- package/src/model/crate.ts +55 -0
- package/src/model/hotCue.ts +206 -0
- package/src/model/hotCueType.ts +6 -0
- package/src/model/seratoColor.ts +20 -0
- package/src/model/tempo.ts +4 -0
- package/src/model/track.ts +73 -0
- package/src/util.ts +79 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [created]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
id-token: write # Required for OIDC
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- name: Checkout repository
|
|
17
|
+
uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Setup Node.js
|
|
20
|
+
uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: 24
|
|
23
|
+
registry-url: https://registry.npmjs.org/
|
|
24
|
+
|
|
25
|
+
# Ensure npm 11.5.1 or later for trusted publishing
|
|
26
|
+
- run: npm install -g npm@latest
|
|
27
|
+
|
|
28
|
+
- run: node --version && npm --version
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: npm ci
|
|
32
|
+
|
|
33
|
+
- name: Extract version from git tag
|
|
34
|
+
id: version
|
|
35
|
+
run: |
|
|
36
|
+
TAG="${{ github.event.release.tag_name }}"
|
|
37
|
+
VERSION="${TAG#v}"
|
|
38
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
39
|
+
|
|
40
|
+
- name: Set package.json version
|
|
41
|
+
run: |
|
|
42
|
+
npm version ${{ steps.version.outputs.version }} --no-git-tag-version
|
|
43
|
+
|
|
44
|
+
- name: Lint
|
|
45
|
+
run: npm run lint
|
|
46
|
+
|
|
47
|
+
- name: Build
|
|
48
|
+
run: npm run build
|
|
49
|
+
|
|
50
|
+
- name: Publish to npm
|
|
51
|
+
run: npm publish
|
|
52
|
+
env:
|
|
53
|
+
NPM_CONFIG_PROVENANCE: true
|
package/dist/builder.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { BaseEncoder } from './encoders/baseEncoder';
|
|
2
2
|
import { Crate } from './model/crate';
|
|
3
|
+
export declare const DEFAULT_SERATO_FOLDER: string;
|
|
3
4
|
export declare class Builder {
|
|
4
5
|
private _encoder?;
|
|
5
6
|
constructor(encoder?: BaseEncoder);
|
|
6
7
|
private static _resolvePath;
|
|
7
8
|
private static _parseCrateNames;
|
|
8
9
|
private _buildCrateFilepath;
|
|
9
|
-
parseCratesFromRootPath(subcratePath: string):
|
|
10
|
+
parseCratesFromRootPath(subcratePath: string): Map<string, Crate>;
|
|
10
11
|
private _buildCratesFromFilepath;
|
|
11
12
|
private _parseCrateTracks;
|
|
12
13
|
private _construct;
|
package/dist/builder.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAItC,eAAO,MAAM,qBAAqB,QAIjC,CAAC;AAEF,qBAAa,OAAO;IAClB,OAAO,CAAC,QAAQ,CAAC,CAAc;gBACnB,OAAO,CAAC,EAAE,WAAW;IAEjC,OAAO,CAAC,MAAM,CAAE,YAAY;IAc5B,OAAO,CAAC,MAAM,CAAE,gBAAgB;IAKhC,OAAO,CAAE,mBAAmB;IAQ5B,uBAAuB,CAAC,YAAY,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC;IAoBjE,OAAO,CAAC,wBAAwB;IAuChC,OAAO,CAAE,iBAAiB;IA+B1B,OAAO,CAAC,UAAU;IAwDlB,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,GAAE,MAA8B,EAAE,SAAS,UAAQ,GAAG,IAAI;CAOrF"}
|
package/dist/builder.js
CHANGED
|
@@ -33,13 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.Builder = void 0;
|
|
36
|
+
exports.Builder = exports.DEFAULT_SERATO_FOLDER = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const crate_1 = require("./model/crate");
|
|
40
40
|
const track_1 = require("./model/track");
|
|
41
41
|
const util_1 = require("./util");
|
|
42
|
-
|
|
42
|
+
exports.DEFAULT_SERATO_FOLDER = path.join(process.env.HOME ?? process.cwd(), 'Music', '_Serato_');
|
|
43
43
|
class Builder {
|
|
44
44
|
constructor(encoder) { this._encoder = encoder; }
|
|
45
45
|
static *_resolvePath(root) {
|
|
@@ -49,9 +49,8 @@ class Builder {
|
|
|
49
49
|
const [crate, p] = stack.pop();
|
|
50
50
|
let newPath = p + `${crate.name}%%`;
|
|
51
51
|
const children = crate.children;
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
stack.push([child, newPath]);
|
|
52
|
+
for (const child of children.values()) {
|
|
53
|
+
stack.push([child, newPath]);
|
|
55
54
|
}
|
|
56
55
|
yield [crate, newPath.replace(/%%$/, '') + '.crate'];
|
|
57
56
|
}
|
|
@@ -70,63 +69,68 @@ class Builder {
|
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
71
|
parseCratesFromRootPath(subcratePath) {
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
for (const
|
|
76
|
-
if (!name.endsWith(
|
|
72
|
+
// map from top-level crate name to crate
|
|
73
|
+
const topLevelCrateMap = new Map();
|
|
74
|
+
for (const entry of fs.readdirSync(subcratePath, { withFileTypes: true })) {
|
|
75
|
+
if (!entry.isFile() || !entry.name.endsWith("crate")) {
|
|
77
76
|
continue;
|
|
78
|
-
const full = path.join(subcratePath, name);
|
|
79
|
-
const crate = this._buildCratesFromFilepath(full);
|
|
80
|
-
if (result[crate.name]) {
|
|
81
|
-
const merged_crate = crate.plus(result[crate.name]);
|
|
82
|
-
result[crate.name] = merged_crate;
|
|
83
77
|
}
|
|
84
|
-
|
|
85
|
-
|
|
78
|
+
const fullPath = path.join(subcratePath, entry.name);
|
|
79
|
+
const crate = this._buildCratesFromFilepath(fullPath, topLevelCrateMap);
|
|
80
|
+
if (!topLevelCrateMap.has(crate.name)) {
|
|
81
|
+
topLevelCrateMap.set(crate.name, crate);
|
|
86
82
|
}
|
|
87
83
|
}
|
|
88
|
-
return
|
|
84
|
+
return topLevelCrateMap;
|
|
89
85
|
}
|
|
90
|
-
_buildCratesFromFilepath(filepath) {
|
|
86
|
+
_buildCratesFromFilepath(filepath, topLevelCrateMap) {
|
|
91
87
|
const crateNames = Array.from(Builder._parseCrateNames(filepath));
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
88
|
+
if (crateNames.length === 0) {
|
|
89
|
+
throw new Error(`No crates parsed from ${filepath}`);
|
|
90
|
+
}
|
|
91
|
+
const tracks = [];
|
|
92
|
+
for (const p of this._parseCrateTracks(filepath)) {
|
|
93
|
+
console.log('make track from path ', p, 'from file path ', filepath);
|
|
94
|
+
tracks.push(track_1.Track.fromPath(p));
|
|
95
|
+
}
|
|
96
|
+
let root = topLevelCrateMap.get(crateNames[0]);
|
|
97
|
+
if (!root) {
|
|
98
|
+
root = new crate_1.Crate(crateNames[0]);
|
|
99
|
+
}
|
|
100
|
+
let current = root;
|
|
101
|
+
for (const crateName of crateNames.slice(1)) {
|
|
102
|
+
let nextCrate = current.children.get(crateName);
|
|
103
|
+
if (!nextCrate) {
|
|
104
|
+
nextCrate = new crate_1.Crate(crateName);
|
|
105
|
+
current.children.set(crateName, nextCrate);
|
|
106
106
|
}
|
|
107
|
+
current = nextCrate;
|
|
108
|
+
}
|
|
109
|
+
for (const track of tracks) {
|
|
110
|
+
current.addTrack(track);
|
|
107
111
|
}
|
|
108
|
-
|
|
109
|
-
throw new Error(`no crates parsed from ${filepath}`);
|
|
110
|
-
return crate;
|
|
112
|
+
return root;
|
|
111
113
|
}
|
|
112
114
|
*_parseCrateTracks(filepath) {
|
|
113
115
|
let buffer = fs.readFileSync(filepath);
|
|
114
|
-
|
|
116
|
+
const OTRK = Buffer.from("otrk", "utf8");
|
|
117
|
+
const PTRK = Buffer.from("ptrk", "utf8");
|
|
115
118
|
while (buffer.length > 0) {
|
|
116
|
-
const otrkIdx = buffer.indexOf(
|
|
119
|
+
const otrkIdx = buffer.indexOf(OTRK);
|
|
117
120
|
if (otrkIdx < 0)
|
|
118
121
|
break;
|
|
119
|
-
const ptrkIdx = buffer.indexOf(
|
|
122
|
+
const ptrkIdx = buffer.indexOf(PTRK);
|
|
120
123
|
if (ptrkIdx < 0)
|
|
121
124
|
break;
|
|
122
125
|
const ptrkSection = buffer.slice(ptrkIdx);
|
|
123
126
|
const trackNameLength = ptrkSection.readUInt32BE(4);
|
|
124
127
|
const trackNameEncoded = ptrkSection.slice(8, 8 + trackNameLength);
|
|
125
128
|
let filePath = (0, util_1.seratoDecode)(trackNameEncoded);
|
|
126
|
-
if (!filePath.startsWith(
|
|
127
|
-
filePath =
|
|
129
|
+
if (!filePath.startsWith("/")) {
|
|
130
|
+
filePath = "/" + filePath;
|
|
131
|
+
}
|
|
128
132
|
yield filePath;
|
|
129
|
-
buffer =
|
|
133
|
+
buffer = buffer.slice(ptrkIdx + 8 + trackNameLength);
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
_construct(crate) {
|
|
@@ -155,27 +159,25 @@ class Builder {
|
|
|
155
159
|
const columnSection = (0, util_1.concatBytes)(parts);
|
|
156
160
|
// ----- PLAYLIST SECTION -----
|
|
157
161
|
const playlistParts = [];
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
this._encoder.write(track);
|
|
162
|
-
}
|
|
163
|
-
const absoluteTrackPath = path.resolve(track.path);
|
|
164
|
-
const otrkSize = (0, util_1.intToBytes)(absoluteTrackPath.length * 2 + 8, 4);
|
|
165
|
-
const ptrkSize = (0, util_1.intToBytes)(absoluteTrackPath.length * 2, 4);
|
|
166
|
-
playlistParts.push((0, util_1.latin1Encode)("otrk"));
|
|
167
|
-
playlistParts.push(otrkSize);
|
|
168
|
-
playlistParts.push((0, util_1.latin1Encode)("ptrk"));
|
|
169
|
-
playlistParts.push(ptrkSize);
|
|
170
|
-
playlistParts.push((0, util_1.seratoEncode)(absoluteTrackPath));
|
|
162
|
+
for (const track of crate.tracks) {
|
|
163
|
+
if (this._encoder) {
|
|
164
|
+
this._encoder.write(track);
|
|
171
165
|
}
|
|
166
|
+
const absoluteTrackPath = path.resolve(track.path);
|
|
167
|
+
const otrkSize = (0, util_1.intToBytes)(absoluteTrackPath.length * 2 + 8, 4);
|
|
168
|
+
const ptrkSize = (0, util_1.intToBytes)(absoluteTrackPath.length * 2, 4);
|
|
169
|
+
playlistParts.push((0, util_1.latin1Encode)("otrk"));
|
|
170
|
+
playlistParts.push(otrkSize);
|
|
171
|
+
playlistParts.push((0, util_1.latin1Encode)("ptrk"));
|
|
172
|
+
playlistParts.push(ptrkSize);
|
|
173
|
+
playlistParts.push((0, util_1.seratoEncode)(absoluteTrackPath));
|
|
172
174
|
}
|
|
173
175
|
const playlistSection = (0, util_1.concatBytes)(playlistParts);
|
|
174
176
|
// ----- FINAL CONTENTS -----
|
|
175
177
|
const contents = (0, util_1.concatBytes)([header, columnSection, playlistSection]);
|
|
176
178
|
return contents;
|
|
177
179
|
}
|
|
178
|
-
save(root, savePath = DEFAULT_SERATO_FOLDER, overwrite = false) {
|
|
180
|
+
save(root, savePath = exports.DEFAULT_SERATO_FOLDER, overwrite = false) {
|
|
179
181
|
for (const [crate, filepath] of this._buildCrateFilepath(root, savePath)) {
|
|
180
182
|
if (fs.existsSync(filepath) && !overwrite)
|
|
181
183
|
continue;
|
|
@@ -56,7 +56,7 @@ class V2Mp3Encoder extends baseEncoder_1.BaseEncoder {
|
|
|
56
56
|
const mp3tag = new mp3tag_js_1.default(buffer, true);
|
|
57
57
|
mp3tag.read();
|
|
58
58
|
const geob = mp3tag.tags.v2?.GEOB;
|
|
59
|
-
if (!geob
|
|
59
|
+
if (!geob) {
|
|
60
60
|
return [];
|
|
61
61
|
}
|
|
62
62
|
const data = Buffer.from(geob[0].object);
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC1D,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HotCueType = exports.HotCue = exports.Track = exports.Crate = exports.V2Mp3Encoder = exports.Builder = void 0;
|
|
3
|
+
exports.HotCueType = exports.HotCue = exports.Track = exports.Crate = exports.V2Mp3Encoder = exports.DEFAULT_SERATO_FOLDER = exports.Builder = void 0;
|
|
4
4
|
var builder_1 = require("./builder");
|
|
5
5
|
Object.defineProperty(exports, "Builder", { enumerable: true, get: function () { return builder_1.Builder; } });
|
|
6
|
+
Object.defineProperty(exports, "DEFAULT_SERATO_FOLDER", { enumerable: true, get: function () { return builder_1.DEFAULT_SERATO_FOLDER; } });
|
|
6
7
|
var v2Mp3Encoder_1 = require("./encoders/v2/v2Mp3Encoder");
|
|
7
8
|
Object.defineProperty(exports, "V2Mp3Encoder", { enumerable: true, get: function () { return v2Mp3Encoder_1.V2Mp3Encoder; } });
|
|
8
9
|
var crate_1 = require("./model/crate");
|
package/dist/model/crate.d.ts
CHANGED
|
@@ -3,13 +3,11 @@ export declare class Crate {
|
|
|
3
3
|
private _children;
|
|
4
4
|
readonly name: string;
|
|
5
5
|
private _tracks;
|
|
6
|
-
constructor(name: string, children?: Crate
|
|
7
|
-
get children(): Crate
|
|
6
|
+
constructor(name: string, children?: Map<string, Crate>);
|
|
7
|
+
get children(): Map<string, Crate>;
|
|
8
8
|
get tracks(): Set<Track>;
|
|
9
9
|
addTrack(track: Track): void;
|
|
10
10
|
toString(): string;
|
|
11
|
-
plus(other: Crate): Crate;
|
|
12
|
-
deepCopy(memodict?: Map<any, any>): Crate;
|
|
13
11
|
equals(other: Crate): boolean;
|
|
14
12
|
}
|
|
15
13
|
//# sourceMappingURL=crate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crate.d.ts","sourceRoot":"","sources":["../../src/model/crate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,qBAAa,KAAK;IAChB,OAAO,CAAC,SAAS,
|
|
1
|
+
{"version":3,"file":"crate.d.ts","sourceRoot":"","sources":["../../src/model/crate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAGhC,qBAAa,KAAK;IAChB,OAAO,CAAC,SAAS,CAAqB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,OAAO,CAAa;gBAEhB,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC;IAMvD,IAAI,QAAQ,IAAI,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAEjC;IAED,IAAI,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAEvB;IAED,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAO5B,QAAQ,IAAI,MAAM;IAQlB,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;CAiB9B"}
|
package/dist/model/crate.js
CHANGED
|
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Crate = void 0;
|
|
4
4
|
const util_1 = require("../util");
|
|
5
5
|
class Crate {
|
|
6
|
-
constructor(name, children
|
|
7
|
-
this._children =
|
|
6
|
+
constructor(name, children) {
|
|
7
|
+
this._children = children ?? new Map();
|
|
8
8
|
this.name = (0, util_1.sanitizeFilename)(name);
|
|
9
9
|
this._tracks = new Set();
|
|
10
10
|
}
|
|
@@ -26,48 +26,18 @@ class Crate {
|
|
|
26
26
|
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
27
27
|
return this.toString();
|
|
28
28
|
}
|
|
29
|
-
plus(other) {
|
|
30
|
-
if (this.name !== other.name) {
|
|
31
|
-
throw new Error("Cannot merge crates with different names");
|
|
32
|
-
}
|
|
33
|
-
const childrenCopy = [...this._children, ...other._children].map((c) => c.deepCopy());
|
|
34
|
-
const merged = new Crate(this.name, childrenCopy);
|
|
35
|
-
const allTracks = new Set([...this._tracks, ...other._tracks]);
|
|
36
|
-
for (const track of allTracks) {
|
|
37
|
-
merged.addTrack(track);
|
|
38
|
-
}
|
|
39
|
-
return merged;
|
|
40
|
-
}
|
|
41
|
-
deepCopy(memodict = new Map()) {
|
|
42
|
-
if (memodict.has(this)) {
|
|
43
|
-
return memodict.get(this);
|
|
44
|
-
}
|
|
45
|
-
const childrenCopy = this._children.map((c) => c.deepCopy(memodict));
|
|
46
|
-
const copy = new Crate(this.name, childrenCopy);
|
|
47
|
-
memodict.set(this, copy);
|
|
48
|
-
for (const track of this._tracks) {
|
|
49
|
-
copy.addTrack(track);
|
|
50
|
-
}
|
|
51
|
-
return copy;
|
|
52
|
-
}
|
|
53
29
|
equals(other) {
|
|
54
30
|
if (this.name !== other.name)
|
|
55
31
|
return false;
|
|
56
32
|
if (this._tracks.size !== other._tracks.size)
|
|
57
33
|
return false;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (!
|
|
34
|
+
for (const [name, child] of this.children) {
|
|
35
|
+
const otherChild = other.children.get(name);
|
|
36
|
+
if (!otherChild) {
|
|
61
37
|
return false;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const sortedChildren = [...this._children].sort((a, b) => a._tracks.size - b._tracks.size);
|
|
65
|
-
const sortedOtherChildren = [...other._children].sort((a, b) => a._tracks.size - b._tracks.size);
|
|
66
|
-
if (sortedChildren.length !== sortedOtherChildren.length)
|
|
38
|
+
}
|
|
39
|
+
if (!child.equals(otherChild)) {
|
|
67
40
|
return false;
|
|
68
|
-
for (let i = 0; i < sortedChildren.length; i++) {
|
|
69
|
-
if (!sortedChildren[i].equals(sortedOtherChildren[i]))
|
|
70
|
-
return false;
|
|
71
41
|
}
|
|
72
42
|
}
|
|
73
43
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/model/track.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,qBAAa,KAAK;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAElB,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;gBAGjB,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KAChB;IAeR,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK;
|
|
1
|
+
{"version":3,"file":"track.d.ts","sourceRoot":"","sources":["../../src/model/track.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAElC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAEhC,qBAAa,KAAK;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAElB,QAAQ,EAAE,KAAK,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;gBAGjB,SAAS,EAAE,MAAM,EACjB,MAAM,GAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KAChB;IAeR,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK;IAQ5D,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAIrC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAehC"}
|
package/dist/model/track.js
CHANGED
|
@@ -50,7 +50,9 @@ class Track {
|
|
|
50
50
|
this.cueLoops = params.cueLoops ?? [];
|
|
51
51
|
}
|
|
52
52
|
static fromPath(trackPath, userRoot) {
|
|
53
|
-
const resolved = userRoot
|
|
53
|
+
const resolved = userRoot != null
|
|
54
|
+
? path.resolve(userRoot, trackPath)
|
|
55
|
+
: path.resolve(trackPath);
|
|
54
56
|
return new Track(resolved);
|
|
55
57
|
}
|
|
56
58
|
addBeatgridMarker(tempo) {
|
package/dist/util.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buffer } from 'buffer';
|
|
2
2
|
export declare function splitString(input: Buffer, after?: number, delimiter?: Buffer): Buffer;
|
|
3
|
-
export declare function seratoDecode(
|
|
3
|
+
export declare function seratoDecode(buffer: Buffer): string;
|
|
4
4
|
export declare function concatBytes(arrays: Uint8Array[]): Uint8Array;
|
|
5
5
|
export declare function latin1Encode(str: string): Uint8Array;
|
|
6
6
|
export declare function intToBytes(value: number, length: number): Uint8Array;
|
package/dist/util.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,EAAE,SAAS,GAAE,MAA0B,GAAG,MAAM,CAY5G;
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,EAAE,SAAS,GAAE,MAA0B,GAAG,MAAM,CAY5G;AAGD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAenD;AAGD,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,UAAU,CAS5D;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAEpD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAOpE;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAWlD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,qBAAa,mBAAoB,SAAQ,KAAK;CAAG"}
|
package/dist/util.js
CHANGED
|
@@ -24,35 +24,17 @@ function splitString(input, after = 72, delimiter = buffer_1.Buffer.from('\n'))
|
|
|
24
24
|
return acc;
|
|
25
25
|
}, []));
|
|
26
26
|
}
|
|
27
|
-
function seratoDecode(
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const y = block.readUInt8(2);
|
|
37
|
-
const z = block.readUInt8(3);
|
|
38
|
-
const c = (z & 0x7F) | ((y & 0x01) << 7);
|
|
39
|
-
const b = ((y & 0x7F) >> 1) | ((x & 0x03) << 6);
|
|
40
|
-
const a = ((x & 0x7F) >> 2) | ((w & 0x07) << 5);
|
|
41
|
-
out.push(a, b, c);
|
|
27
|
+
function seratoDecode(buffer) {
|
|
28
|
+
let result = "";
|
|
29
|
+
for (let i = 0; i + 1 < buffer.length; i += 2) {
|
|
30
|
+
// Take 2 bytes
|
|
31
|
+
const chunk = buffer.slice(i, i + 2);
|
|
32
|
+
// Reverse bytes (Python: chunk[::-1])
|
|
33
|
+
const reversed = buffer_1.Buffer.from([chunk[1], chunk[0]]);
|
|
34
|
+
// Decode as UTF-16LE (Node uses LE explicitly)
|
|
35
|
+
result += reversed.toString("utf16le");
|
|
42
36
|
}
|
|
43
|
-
|
|
44
|
-
// Convert bytes to string by grouping into 2-byte code units
|
|
45
|
-
const bytes = buffer_1.Buffer.from(out);
|
|
46
|
-
let str = '';
|
|
47
|
-
for (let i = 0; i < bytes.length; i += 2) {
|
|
48
|
-
const hi = bytes[i];
|
|
49
|
-
const lo = (i + 1) < bytes.length ? bytes[i + 1] : 0;
|
|
50
|
-
const code = (hi << 8) | lo;
|
|
51
|
-
if (code === 0)
|
|
52
|
-
break;
|
|
53
|
-
str += String.fromCharCode(code);
|
|
54
|
-
}
|
|
55
|
-
return str;
|
|
37
|
+
return result;
|
|
56
38
|
}
|
|
57
39
|
function concatBytes(arrays) {
|
|
58
40
|
const totalLength = arrays.reduce((sum, a) => sum + a.length, 0);
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// eslint.config.js
|
|
2
|
+
const tsParser = require("@typescript-eslint/parser");
|
|
3
|
+
const tsPlugin = require("@typescript-eslint/eslint-plugin");
|
|
4
|
+
|
|
5
|
+
module.exports = [
|
|
6
|
+
{
|
|
7
|
+
ignores: [
|
|
8
|
+
"node_modules/**",
|
|
9
|
+
"dist/**",
|
|
10
|
+
"build/**",
|
|
11
|
+
"*.log",
|
|
12
|
+
"*.tmp",
|
|
13
|
+
"*.tsbuildinfo",
|
|
14
|
+
".vscode/**",
|
|
15
|
+
".idea/**",
|
|
16
|
+
"eslint.config.js"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
files: ["**/*.ts"],
|
|
21
|
+
languageOptions: {
|
|
22
|
+
parser: tsParser,
|
|
23
|
+
parserOptions: {
|
|
24
|
+
ecmaVersion: "latest",
|
|
25
|
+
sourceType: "module",
|
|
26
|
+
project: "./tsconfig.json",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
plugins: {
|
|
30
|
+
"@typescript-eslint": tsPlugin
|
|
31
|
+
},
|
|
32
|
+
rules: {
|
|
33
|
+
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
|
|
34
|
+
"@typescript-eslint/no-floating-promises": "error",
|
|
35
|
+
"@typescript-eslint/explicit-function-return-type": "warn",
|
|
36
|
+
"@typescript-eslint/strict-boolean-expressions": "warn"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
files: ["**/*.js"],
|
|
41
|
+
languageOptions: {
|
|
42
|
+
ecmaVersion: "latest",
|
|
43
|
+
sourceType: "module"
|
|
44
|
+
},
|
|
45
|
+
rules: {
|
|
46
|
+
"no-unused-vars": "warn",
|
|
47
|
+
"no-undef": "error"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
];
|
package/package.json
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tserato",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build": "tsc"
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"lint": "eslint . --ext .ts"
|
|
9
10
|
},
|
|
10
11
|
"repository": {
|
|
11
12
|
"type": "git",
|
|
12
13
|
"url": "https://github.com/laker-93/tserato.git"
|
|
13
14
|
},
|
|
14
15
|
"devDependencies": {
|
|
16
|
+
"@eslint/eslintrc": "^3.3.3",
|
|
15
17
|
"@types/node": "^24.5.1",
|
|
18
|
+
"@typescript-eslint/eslint-plugin": "^8.50.1",
|
|
19
|
+
"@typescript-eslint/parser": "^8.50.1",
|
|
20
|
+
"eslint": "^9.39.2",
|
|
16
21
|
"typescript": "^5.3.3",
|
|
17
22
|
"vitest": "^0.34.6"
|
|
18
23
|
},
|