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.
@@ -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): Record<string, Crate>;
10
+ parseCratesFromRootPath(subcratePath: string): Map<string, Crate>;
10
11
  private _buildCratesFromFilepath;
11
12
  private _parseCrateTracks;
12
13
  private _construct;
@@ -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;AAMtC,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,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAiBpE,OAAO,CAAC,wBAAwB;IAqBhC,OAAO,CAAE,iBAAiB;IAkB1B,OAAO,CAAC,UAAU;IA2DlB,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,GAAE,MAA8B,EAAE,SAAS,UAAQ;CAO9E"}
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
- const DEFAULT_SERATO_FOLDER = path.join(process.env.HOME || process.cwd(), 'Music', '_Serato_');
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
- if (children && children.length) {
53
- for (const child of children)
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
- const result = {};
74
- const entries = fs.readdirSync(subcratePath);
75
- for (const name of entries) {
76
- if (!name.endsWith('crate'))
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
- else {
85
- result[crate.name] = crate;
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 result;
84
+ return topLevelCrateMap;
89
85
  }
90
- _buildCratesFromFilepath(filepath) {
86
+ _buildCratesFromFilepath(filepath, topLevelCrateMap) {
91
87
  const crateNames = Array.from(Builder._parseCrateNames(filepath));
92
- let childCrate = null;
93
- let crate = null;
94
- for (const crateName of crateNames.reverse()) {
95
- if (childCrate == null) {
96
- crate = new crate_1.Crate(crateName);
97
- for (const filePath of this._parseCrateTracks(filepath)) {
98
- const t = track_1.Track.fromPath(filePath);
99
- crate.addTrack(t);
100
- }
101
- childCrate = crate;
102
- }
103
- else {
104
- crate = new crate_1.Crate(crateName, [childCrate]);
105
- childCrate = crate;
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
- if (!crate)
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
- buffer = Buffer.from(buffer);
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(Buffer.from('otrk'));
119
+ const otrkIdx = buffer.indexOf(OTRK);
117
120
  if (otrkIdx < 0)
118
121
  break;
119
- const ptrkIdx = buffer.indexOf(Buffer.from('ptrk'), otrkIdx);
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 = '/' + filePath;
129
+ if (!filePath.startsWith("/")) {
130
+ filePath = "/" + filePath;
131
+ }
128
132
  yield filePath;
129
- buffer = ptrkSection.slice(8 + trackNameLength);
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
- if (crate.tracks) {
159
- for (const track of crate.tracks) {
160
- if (this._encoder) {
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 || !geob) {
59
+ if (!geob) {
60
60
  return [];
61
61
  }
62
62
  const data = Buffer.from(geob[0].object);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,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"}
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");
@@ -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,CAAU;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,OAAO,CAAa;gBAEhB,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,KAAK,EAAO;IAMhD,IAAI,QAAQ,IAAI,KAAK,EAAE,CAEtB;IAED,IAAI,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAEvB;IAED,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAO5B,QAAQ,IAAI,MAAM;IAQlB,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK;IAazB,QAAQ,CAAC,QAAQ,gBAAsB,GAAG,KAAK;IAa/C,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;CAqB9B"}
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"}
@@ -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 = [...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
- // compare tracks by reference equality (like Python set)
59
- for (const track of this._tracks) {
60
- if (!other._tracks.has(track))
34
+ for (const [name, child] of this.children) {
35
+ const otherChild = other.children.get(name);
36
+ if (!otherChild) {
61
37
  return false;
62
- }
63
- if (this._children.length || other._children.length) {
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;IAK5D,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAIrC,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;CAehC"}
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"}
@@ -50,7 +50,9 @@ class Track {
50
50
  this.cueLoops = params.cueLoops ?? [];
51
51
  }
52
52
  static fromPath(trackPath, userRoot) {
53
- const resolved = userRoot ? path.resolve(userRoot, trackPath) : path.resolve(trackPath);
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(s: Buffer): string;
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;
@@ -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;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CA2B9C;AAED,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"}
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(s) {
28
- // Decodes Serato 4-byte blocks into UTF-16-encoded string
29
- const out = [];
30
- for (let i = 0; i < s.length; i += 4) {
31
- const block = s.slice(i, i + 4);
32
- if (block.length < 4)
33
- break;
34
- const w = block.readUInt8(0);
35
- const x = block.readUInt8(1);
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
- // Interpret as UTF-16-BE pairs
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);
@@ -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.0",
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
  },