tserato 0.1.0 → 0.1.11
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 +56 -55
- package/dist/encoders/v2/v2Mp3Encoder.d.ts +2 -0
- package/dist/encoders/v2/v2Mp3Encoder.d.ts.map +1 -1
- package/dist/encoders/v2/v2Mp3Encoder.js +12 -1
- package/dist/index.d.ts +2 -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 +4 -0
- package/dist/model/track.d.ts.map +1 -1
- package/dist/model/track.js +7 -1
- package/dist/model/trackMeta.d.ts +7 -0
- package/dist/model/trackMeta.d.ts.map +1 -0
- package/dist/model/trackMeta.js +2 -0
- 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 +8 -4
- package/src/builder.ts +197 -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 +245 -0
- package/src/index.ts +7 -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 +81 -0
- package/src/model/trackMeta.ts +6 -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;IAsChC,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,67 @@ 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
|
+
tracks.push(track_1.Track.fromPath(p));
|
|
94
|
+
}
|
|
95
|
+
let root = topLevelCrateMap.get(crateNames[0]);
|
|
96
|
+
if (!root) {
|
|
97
|
+
root = new crate_1.Crate(crateNames[0]);
|
|
98
|
+
}
|
|
99
|
+
let current = root;
|
|
100
|
+
for (const crateName of crateNames.slice(1)) {
|
|
101
|
+
let nextCrate = current.children.get(crateName);
|
|
102
|
+
if (!nextCrate) {
|
|
103
|
+
nextCrate = new crate_1.Crate(crateName);
|
|
104
|
+
current.children.set(crateName, nextCrate);
|
|
106
105
|
}
|
|
106
|
+
current = nextCrate;
|
|
107
|
+
}
|
|
108
|
+
for (const track of tracks) {
|
|
109
|
+
current.addTrack(track);
|
|
107
110
|
}
|
|
108
|
-
|
|
109
|
-
throw new Error(`no crates parsed from ${filepath}`);
|
|
110
|
-
return crate;
|
|
111
|
+
return root;
|
|
111
112
|
}
|
|
112
113
|
*_parseCrateTracks(filepath) {
|
|
113
114
|
let buffer = fs.readFileSync(filepath);
|
|
114
|
-
|
|
115
|
+
const OTRK = Buffer.from("otrk", "utf8");
|
|
116
|
+
const PTRK = Buffer.from("ptrk", "utf8");
|
|
115
117
|
while (buffer.length > 0) {
|
|
116
|
-
const otrkIdx = buffer.indexOf(
|
|
118
|
+
const otrkIdx = buffer.indexOf(OTRK);
|
|
117
119
|
if (otrkIdx < 0)
|
|
118
120
|
break;
|
|
119
|
-
const ptrkIdx = buffer.indexOf(
|
|
121
|
+
const ptrkIdx = buffer.indexOf(PTRK);
|
|
120
122
|
if (ptrkIdx < 0)
|
|
121
123
|
break;
|
|
122
124
|
const ptrkSection = buffer.slice(ptrkIdx);
|
|
123
125
|
const trackNameLength = ptrkSection.readUInt32BE(4);
|
|
124
126
|
const trackNameEncoded = ptrkSection.slice(8, 8 + trackNameLength);
|
|
125
127
|
let filePath = (0, util_1.seratoDecode)(trackNameEncoded);
|
|
126
|
-
if (!filePath.startsWith(
|
|
127
|
-
filePath =
|
|
128
|
+
if (!filePath.startsWith("/")) {
|
|
129
|
+
filePath = "/" + filePath;
|
|
130
|
+
}
|
|
128
131
|
yield filePath;
|
|
129
|
-
buffer =
|
|
132
|
+
buffer = buffer.slice(ptrkIdx + 8 + trackNameLength);
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
135
|
_construct(crate) {
|
|
@@ -155,27 +158,25 @@ class Builder {
|
|
|
155
158
|
const columnSection = (0, util_1.concatBytes)(parts);
|
|
156
159
|
// ----- PLAYLIST SECTION -----
|
|
157
160
|
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));
|
|
161
|
+
for (const track of crate.tracks) {
|
|
162
|
+
if (this._encoder) {
|
|
163
|
+
this._encoder.write(track);
|
|
171
164
|
}
|
|
165
|
+
const absoluteTrackPath = path.resolve(track.path);
|
|
166
|
+
const otrkSize = (0, util_1.intToBytes)(absoluteTrackPath.length * 2 + 8, 4);
|
|
167
|
+
const ptrkSize = (0, util_1.intToBytes)(absoluteTrackPath.length * 2, 4);
|
|
168
|
+
playlistParts.push((0, util_1.latin1Encode)("otrk"));
|
|
169
|
+
playlistParts.push(otrkSize);
|
|
170
|
+
playlistParts.push((0, util_1.latin1Encode)("ptrk"));
|
|
171
|
+
playlistParts.push(ptrkSize);
|
|
172
|
+
playlistParts.push((0, util_1.seratoEncode)(absoluteTrackPath));
|
|
172
173
|
}
|
|
173
174
|
const playlistSection = (0, util_1.concatBytes)(playlistParts);
|
|
174
175
|
// ----- FINAL CONTENTS -----
|
|
175
176
|
const contents = (0, util_1.concatBytes)([header, columnSection, playlistSection]);
|
|
176
177
|
return contents;
|
|
177
178
|
}
|
|
178
|
-
save(root, savePath = DEFAULT_SERATO_FOLDER, overwrite = false) {
|
|
179
|
+
save(root, savePath = exports.DEFAULT_SERATO_FOLDER, overwrite = false) {
|
|
179
180
|
for (const [crate, filepath] of this._buildCrateFilepath(root, savePath)) {
|
|
180
181
|
if (fs.existsSync(filepath) && !overwrite)
|
|
181
182
|
continue;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { HotCue } from '../../../src/model/hotCue';
|
|
2
2
|
import { Track } from '../../model/track';
|
|
3
3
|
import { BaseEncoder } from '../baseEncoder';
|
|
4
|
+
import { TrackMeta } from '../../model/trackMeta';
|
|
4
5
|
export declare class V2Mp3Encoder extends BaseEncoder {
|
|
5
6
|
get tagName(): string;
|
|
6
7
|
get tagVersion(): Buffer;
|
|
7
8
|
get markersName(): string;
|
|
8
9
|
write(track: Track): void;
|
|
10
|
+
readMetaData(track: Track): TrackMeta;
|
|
9
11
|
readCues(track: Track): HotCue[];
|
|
10
12
|
private _decode;
|
|
11
13
|
private _removeNullPadding;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2Mp3Encoder.d.ts","sourceRoot":"","sources":["../../../src/encoders/v2/v2Mp3Encoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAEnD,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"v2Mp3Encoder.d.ts","sourceRoot":"","sources":["../../../src/encoders/v2/v2Mp3Encoder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAEnD,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAI7C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AA8BlD,qBAAa,YAAa,SAAQ,WAAW;IAE3C,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAKzB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS;IAcrC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,EAAE;IAoBhC,OAAO,CAAE,OAAO;IAmDhB,OAAO,CAAC,kBAAkB;IAK1B,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,MAAM;IAkCd,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,IAAI;IAWZ,OAAO,CAAC,qBAAqB;IAI7B,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,cAAc;CASvB"}
|
|
@@ -49,6 +49,17 @@ class V2Mp3Encoder extends baseEncoder_1.BaseEncoder {
|
|
|
49
49
|
const payload = this._encode(track);
|
|
50
50
|
this._write(track, payload);
|
|
51
51
|
}
|
|
52
|
+
readMetaData(track) {
|
|
53
|
+
const buffer = fs.readFileSync(track.path.toString());
|
|
54
|
+
const mp3tag = new mp3tag_js_1.default(buffer, true);
|
|
55
|
+
mp3tag.read();
|
|
56
|
+
const v2 = mp3tag.tags.v2;
|
|
57
|
+
return {
|
|
58
|
+
title: v2?.TIT2,
|
|
59
|
+
artist: v2?.TPE1,
|
|
60
|
+
album: v2?.TALB
|
|
61
|
+
};
|
|
62
|
+
}
|
|
52
63
|
readCues(track) {
|
|
53
64
|
// Read the buffer of an audio file
|
|
54
65
|
const buffer = fs.readFileSync(track.path.toString());
|
|
@@ -56,7 +67,7 @@ class V2Mp3Encoder extends baseEncoder_1.BaseEncoder {
|
|
|
56
67
|
const mp3tag = new mp3tag_js_1.default(buffer, true);
|
|
57
68
|
mp3tag.read();
|
|
58
69
|
const geob = mp3tag.tags.v2?.GEOB;
|
|
59
|
-
if (!geob
|
|
70
|
+
if (!geob) {
|
|
60
71
|
return [];
|
|
61
72
|
}
|
|
62
73
|
const data = Buffer.from(geob[0].object);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
export { Builder } from './builder';
|
|
1
|
+
export { Builder, DEFAULT_SERATO_FOLDER } from './builder';
|
|
2
2
|
export { V2Mp3Encoder } from './encoders/v2/v2Mp3Encoder';
|
|
3
3
|
export { Crate } from './model/crate';
|
|
4
4
|
export { Track } from './model/track';
|
|
5
|
+
export { TrackMeta } from './model/trackMeta';
|
|
5
6
|
export { HotCue } from './model/hotCue';
|
|
6
7
|
export { HotCueType } from './model/hotCueType';
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
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,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,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;
|
package/dist/model/track.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HotCue } from './hotCue';
|
|
2
2
|
import { Tempo } from './tempo';
|
|
3
|
+
import { TrackMeta } from './trackMeta';
|
|
3
4
|
export declare class Track {
|
|
4
5
|
path: string;
|
|
5
6
|
trackId: string;
|
|
@@ -11,6 +12,7 @@ export declare class Track {
|
|
|
11
12
|
beatgrid: Tempo[];
|
|
12
13
|
hotCues: HotCue[];
|
|
13
14
|
cueLoops: HotCue[];
|
|
15
|
+
trackMeta: TrackMeta | null;
|
|
14
16
|
constructor(trackPath: string, params?: {
|
|
15
17
|
trackId?: string;
|
|
16
18
|
averageBpm?: number;
|
|
@@ -21,9 +23,11 @@ export declare class Track {
|
|
|
21
23
|
beatgrid?: Tempo[];
|
|
22
24
|
hotCues?: HotCue[];
|
|
23
25
|
cueLoops?: HotCue[];
|
|
26
|
+
trackMeta?: TrackMeta;
|
|
24
27
|
});
|
|
25
28
|
static fromPath(trackPath: string, userRoot?: string): Track;
|
|
26
29
|
addBeatgridMarker(tempo: Tempo): void;
|
|
27
30
|
addHotCue(hotCue: HotCue): void;
|
|
31
|
+
addTrackMeta(trackMeta: TrackMeta): void;
|
|
28
32
|
}
|
|
29
33
|
//# sourceMappingURL=track.d.ts.map
|
|
@@ -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;
|
|
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;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,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;IACnB,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;gBAG1B,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;QACpB,SAAS,CAAC,EAAE,SAAS,CAAC;KAClB;IAgBR,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;IAgB/B,YAAY,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;CAGzC"}
|
package/dist/model/track.js
CHANGED
|
@@ -48,9 +48,12 @@ class Track {
|
|
|
48
48
|
this.beatgrid = params.beatgrid ?? [];
|
|
49
49
|
this.hotCues = params.hotCues ?? [];
|
|
50
50
|
this.cueLoops = params.cueLoops ?? [];
|
|
51
|
+
this.trackMeta = params.trackMeta ?? null;
|
|
51
52
|
}
|
|
52
53
|
static fromPath(trackPath, userRoot) {
|
|
53
|
-
const resolved = userRoot
|
|
54
|
+
const resolved = userRoot != null
|
|
55
|
+
? path.resolve(userRoot, trackPath)
|
|
56
|
+
: path.resolve(trackPath);
|
|
54
57
|
return new Track(resolved);
|
|
55
58
|
}
|
|
56
59
|
addBeatgridMarker(tempo) {
|
|
@@ -71,5 +74,8 @@ class Track {
|
|
|
71
74
|
this.hotCues.splice(atIndex, 0, hotCue);
|
|
72
75
|
}
|
|
73
76
|
}
|
|
77
|
+
addTrackMeta(trackMeta) {
|
|
78
|
+
this.trackMeta = trackMeta;
|
|
79
|
+
}
|
|
74
80
|
}
|
|
75
81
|
exports.Track = Track;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trackMeta.d.ts","sourceRoot":"","sources":["../../src/model/trackMeta.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB"}
|
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,24 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tserato",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
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
|
},
|
|
19
24
|
"dependencies": {
|
|
20
25
|
"base-64": "^1.0.0",
|
|
21
|
-
"mp3tag.js": "^3.14.1"
|
|
22
|
-
"node-id3": "^0.2.6"
|
|
26
|
+
"mp3tag.js": "^3.14.1"
|
|
23
27
|
}
|
|
24
28
|
}
|