alphatheta-connect 0.15.0 → 0.19.1

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 (41) hide show
  1. package/README.md +29 -3
  2. package/lib/artwork/index.d.ts +2 -1
  3. package/lib/cli.js +1122 -1332
  4. package/lib/cli.js.map +1 -1
  5. package/lib/constants.d.ts +7 -1
  6. package/lib/db/getArtworkFromFile.d.ts +2 -0
  7. package/lib/db/getFile.d.ts +7 -2
  8. package/lib/db/getMetadata.d.ts +3 -2
  9. package/lib/db/getTrackAnalysis.d.ts +28 -0
  10. package/lib/db/index.d.ts +8 -8
  11. package/lib/entities.d.ts +13 -111
  12. package/lib/index.d.ts +10 -2
  13. package/lib/index.js +1357 -1364
  14. package/lib/index.js.map +1 -1
  15. package/lib/localdb/database-adapter.d.ts +2 -47
  16. package/lib/localdb/index.d.ts +1 -1
  17. package/lib/localdb/onelibrary/index.d.ts +5 -0
  18. package/lib/localdb/onelibrary.d.ts +3 -181
  19. package/lib/localdb/rekordbox/anlz-parsers.d.ts +37 -0
  20. package/lib/localdb/rekordbox/entity-creators.d.ts +27 -0
  21. package/lib/localdb/rekordbox/hydrator.d.ts +20 -0
  22. package/lib/localdb/rekordbox/index.d.ts +13 -0
  23. package/lib/localdb/rekordbox/table-mappings.d.ts +10 -0
  24. package/lib/localdb/rekordbox/types.d.ts +118 -0
  25. package/lib/localdb/rekordbox.d.ts +6 -110
  26. package/lib/logger.d.ts +13 -0
  27. package/lib/metadata.d.ts +45 -0
  28. package/lib/network.d.ts +25 -0
  29. package/lib/nfs/index.d.ts +35 -10
  30. package/lib/nfs/index.test.d.ts +1 -0
  31. package/lib/nfs/programs.d.ts +10 -1
  32. package/lib/passive/index.d.ts +1 -1
  33. package/lib/passive/remotedb.d.ts +12 -1
  34. package/lib/remotedb/queries.d.ts +2 -2
  35. package/lib/types.d.ts +55 -78
  36. package/lib/types.js +21 -38
  37. package/lib/types.js.map +1 -1
  38. package/lib/utils/index.d.ts +9 -0
  39. package/lib/virtualcdj/index.d.ts +7 -1
  40. package/package.json +23 -2
  41. package/lib/localdb/onelibrary-schema.d.ts +0 -250
package/lib/cli.js CHANGED
@@ -14,6 +14,7 @@ exports.PictureType = exports.createNfsFileReaderWithInfo = exports.createNfsFil
14
14
  exports.isArtworkExtractionSupported = isArtworkExtractionSupported;
15
15
  exports.extractArtwork = extractArtwork;
16
16
  exports.extractArtworkFromDevice = extractArtworkFromDevice;
17
+ const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
17
18
  const nfs_1 = __webpack_require__(/*! src/nfs */ "./src/nfs/index.ts");
18
19
  const parsers_1 = __webpack_require__(/*! ./parsers */ "./src/artwork/parsers/index.ts");
19
20
  const reader_1 = __webpack_require__(/*! ./reader */ "./src/artwork/reader.ts");
@@ -50,10 +51,14 @@ async function extractArtwork(reader) {
50
51
  }
51
52
  }
52
53
  }
53
- async function extractArtworkFromDevice(device, slot, filePath) {
54
+ async function extractArtworkFromDevice(device, slot, filePath, logger = logger_1.noopLogger) {
55
+ logger.debug(`[artwork-nfs] getFileInfo: device=${device.ip.address}, slot=${slot}, path=${filePath}`);
54
56
  const fileInfo = await (0, nfs_1.getFileInfo)({ device, slot, path: filePath });
57
+ logger.debug(`[artwork-nfs] File found: ${fileInfo.size} bytes`);
55
58
  const reader = (0, reader_1.createNfsFileReader)(device, slot, filePath, fileInfo.size);
56
- return extractArtwork(reader);
59
+ const result = await extractArtwork(reader);
60
+ logger.debug(`[artwork-nfs] extractArtwork result: ${result ? `${result.mimeType} (${result.data.length}b)` : 'null'}`);
61
+ return result;
57
62
  }
58
63
 
59
64
 
@@ -674,7 +679,7 @@ cli();
674
679
  "use strict";
675
680
 
676
681
  Object.defineProperty(exports, "__esModule", ({ value: true }));
677
- exports.VIRTUAL_CDJ_FIRMWARE = exports.VIRTUAL_CDJ_NAME = exports.PROLINK_HEADER = exports.STARTUP_STAGE_INTERVAL = exports.ANNOUNCE_INTERVAL = exports.STATUS_PORT = exports.BEAT_PORT = exports.ANNOUNCE_PORT = exports.DEFAULT_VCDJ_ID = void 0;
682
+ exports.MAX_CDJ_DEVICE_ID = exports.MIN_CDJ_DEVICE_ID = exports.VIRTUAL_CDJ_FIRMWARE = exports.VIRTUAL_CDJ_NAME = exports.PROLINK_HEADER = exports.STARTUP_STAGE_INTERVAL = exports.ANNOUNCE_INTERVAL = exports.STATUS_PORT = exports.BEAT_PORT = exports.ANNOUNCE_PORT = exports.DEFAULT_VCDJ_ID = void 0;
678
683
  /**
679
684
  * The default virtual CDJ ID to use.
680
685
  *
@@ -715,7 +720,13 @@ exports.VIRTUAL_CDJ_NAME = 'ProLink-Connect';
715
720
  * VirtualCDJFirmware is a string indicating the firmware version reported with
716
721
  * status packets.
717
722
  */
718
- exports.VIRTUAL_CDJ_FIRMWARE = '1.43';
723
+ exports.VIRTUAL_CDJ_FIRMWARE = '3.20';
724
+ /**
725
+ * CDJs use device IDs 1-6. Devices outside this range (e.g. Stagehand at 154)
726
+ * should not be queried for media slots or databases as they may crash.
727
+ */
728
+ exports.MIN_CDJ_DEVICE_ID = 1;
729
+ exports.MAX_CDJ_DEVICE_ID = 6;
719
730
 
720
731
 
721
732
  /***/ },
@@ -833,37 +844,46 @@ var __importStar = (this && this.__importStar) || (function () {
833
844
  Object.defineProperty(exports, "__esModule", ({ value: true }));
834
845
  exports.viaFileExtraction = viaFileExtraction;
835
846
  const artwork_1 = __webpack_require__(/*! src/artwork */ "./src/artwork/index.ts");
847
+ const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
836
848
  const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
849
+ const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
837
850
  const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
838
851
  /**
839
852
  * Extract artwork directly from an audio file via NFS.
840
853
  */
841
854
  async function viaFileExtraction(device, opts) {
842
- var _a, _b;
855
+ var _a, _b, _c;
843
856
  const { trackSlot, track, span } = opts;
857
+ const logger = (_a = opts.logger) !== null && _a !== void 0 ? _a : logger_1.noopLogger;
844
858
  if (trackSlot !== types_1.MediaSlot.USB &&
845
859
  trackSlot !== types_1.MediaSlot.SD &&
846
860
  trackSlot !== types_1.MediaSlot.RB) {
861
+ logger.debug(`[artwork-nfs] Skipping: unsupported slot ${(0, utils_1.getSlotName)(trackSlot)} (device ${device.name})`);
847
862
  return null;
848
863
  }
849
864
  const slot = trackSlot;
850
865
  if (!track.filePath) {
866
+ logger.debug('[artwork-nfs] Skipping: no filePath on track');
851
867
  return null;
852
868
  }
853
- const extension = (_b = (_a = track.filePath.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
869
+ const extension = (_c = (_b = track.filePath.split('.').pop()) === null || _b === void 0 ? void 0 : _b.toLowerCase()) !== null && _c !== void 0 ? _c : '';
854
870
  if (!(0, artwork_1.isArtworkExtractionSupported)(extension)) {
871
+ logger.debug(`[artwork-nfs] Skipping: unsupported extension ".${extension}" (${track.filePath})`);
855
872
  return null;
856
873
  }
874
+ logger.debug(`[artwork-nfs] Extracting from ${track.filePath} (slot=${(0, utils_1.getSlotName)(trackSlot)}, device=${device.name} @ ${device.ip.address})`);
857
875
  const tx = span
858
876
  ? span.startChild({ op: 'getArtworkFromFile' })
859
877
  : Telemetry.startTransaction({ name: 'getArtworkFromFile' });
860
878
  try {
861
- const artwork = await (0, artwork_1.extractArtworkFromDevice)(device, slot, track.filePath);
879
+ const artwork = await (0, artwork_1.extractArtworkFromDevice)(device, slot, track.filePath, logger);
862
880
  if (!artwork) {
881
+ logger.debug('[artwork-nfs] No embedded artwork found in file');
863
882
  tx.setData('result', 'no_artwork');
864
883
  tx.finish();
865
884
  return null;
866
885
  }
886
+ logger.debug(`[artwork-nfs] Success: ${artwork.mimeType} (${artwork.data.length} bytes)`);
867
887
  tx.setData('result', 'success');
868
888
  tx.setData('mimeType', artwork.mimeType);
869
889
  tx.setData('size', artwork.data.length);
@@ -871,6 +891,8 @@ async function viaFileExtraction(device, opts) {
871
891
  return artwork.data;
872
892
  }
873
893
  catch (error) {
894
+ const msg = error instanceof Error ? error.message : String(error);
895
+ logger.warn(`[artwork-nfs] NFS extraction failed: ${msg}`);
874
896
  tx.setData('result', 'error');
875
897
  tx.finish();
876
898
  Telemetry.captureException(error);
@@ -1018,12 +1040,15 @@ var __importStar = (this && this.__importStar) || (function () {
1018
1040
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1019
1041
  exports.viaRemote = viaRemote;
1020
1042
  exports.viaLocal = viaLocal;
1043
+ const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
1021
1044
  const nfs_1 = __webpack_require__(/*! src/nfs */ "./src/nfs/index.ts");
1022
1045
  const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
1023
1046
  const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
1024
1047
  const CHUNK_SIZE = 8192; // Maximum allowed XDR read size
1025
1048
  function viaRemote(_remote, _device, _opts) {
1026
- console.error('Getting a file from Rekordbox via ProDJ-Link is not yet supported.');
1049
+ var _a;
1050
+ const logger = (_a = _opts.logger) !== null && _a !== void 0 ? _a : logger_1.noopLogger;
1051
+ logger.error('Getting a file from Rekordbox via ProDJ-Link is not yet supported.');
1027
1052
  return null;
1028
1053
  // const conn = await remote.get(deviceId);
1029
1054
  // if (conn === null) {
@@ -1045,7 +1070,9 @@ function viaRemote(_remote, _device, _opts) {
1045
1070
  // });
1046
1071
  }
1047
1072
  async function viaLocal(local, device, opts) {
1073
+ var _a;
1048
1074
  const { deviceId, trackSlot, track } = opts;
1075
+ const logger = (_a = opts.logger) !== null && _a !== void 0 ? _a : logger_1.noopLogger;
1049
1076
  if (trackSlot !== types_1.MediaSlot.USB && trackSlot !== types_1.MediaSlot.SD) {
1050
1077
  throw new Error('Expected USB or SD or RB slot for remote database query');
1051
1078
  }
@@ -1059,7 +1086,7 @@ async function viaLocal(local, device, opts) {
1059
1086
  slot: trackSlot,
1060
1087
  path: track.filePath,
1061
1088
  onProgress: progress => {
1062
- console.log(progress.read, progress.total);
1089
+ logger.trace('%d %d', progress.read, progress.total);
1063
1090
  },
1064
1091
  chunkSize: CHUNK_SIZE,
1065
1092
  });
@@ -1099,24 +1126,41 @@ async function viaRemote(remote, opts) {
1099
1126
  trackType,
1100
1127
  menuTarget: remotedb_1.MenuTarget.Main,
1101
1128
  };
1129
+ const isUnanalyzed = trackType === types_1.TrackType.Unanalyzed || trackType === types_1.TrackType.AudioCD;
1130
+ const isStreaming = trackType === types_1.TrackType.Streaming;
1131
+ const skipLocalFileLookups = isUnanalyzed || isStreaming;
1132
+ // Unanalyzed tracks use GetGenericMetadata (reads ID3 tags from the audio file).
1133
+ // Streaming tracks (Beatport) use the regular GetMetadata query.
1102
1134
  const track = await conn.query({
1103
1135
  queryDescriptor,
1104
- query: remotedb_1.Query.GetMetadata,
1105
- args: { trackId },
1106
- span,
1107
- });
1108
- track.filePath = await conn.query({
1109
- queryDescriptor,
1110
- query: remotedb_1.Query.GetTrackInfo,
1111
- args: { trackId },
1112
- span,
1113
- });
1114
- track.beatGrid = await conn.query({
1115
- queryDescriptor,
1116
- query: remotedb_1.Query.GetBeatGrid,
1136
+ query: isUnanalyzed ? remotedb_1.Query.GetGenericMetadata : remotedb_1.Query.GetMetadata,
1117
1137
  args: { trackId },
1118
1138
  span,
1119
1139
  });
1140
+ // Try to get file path — for streaming tracks this returns the Beatport track ID
1141
+ // (e.g. "/26883657.m4a") which we use for Beatport API lookups
1142
+ try {
1143
+ track.filePath = await conn.query({
1144
+ queryDescriptor,
1145
+ query: remotedb_1.Query.GetTrackInfo,
1146
+ args: { trackId },
1147
+ span,
1148
+ });
1149
+ }
1150
+ catch (err) {
1151
+ if (!skipLocalFileLookups) {
1152
+ throw err;
1153
+ }
1154
+ }
1155
+ // Beat grid is only available for analyzed local tracks
1156
+ if (!skipLocalFileLookups) {
1157
+ track.beatGrid = await conn.query({
1158
+ queryDescriptor,
1159
+ query: remotedb_1.Query.GetBeatGrid,
1160
+ args: { trackId },
1161
+ span,
1162
+ });
1163
+ }
1120
1164
  return track;
1121
1165
  }
1122
1166
  async function viaLocal(local, device, opts) {
@@ -1128,13 +1172,16 @@ async function viaLocal(local, device, opts) {
1128
1172
  if (adapter === null) {
1129
1173
  return null;
1130
1174
  }
1131
- const track = adapter.findTrack(trackId);
1132
- if (track === null) {
1175
+ const dbTrack = adapter.findTrack(trackId);
1176
+ if (dbTrack === null) {
1133
1177
  return null;
1134
1178
  }
1135
- const anlz = await (0, rekordbox_1.loadAnlz)(track, 'DAT', (0, utils_1.anlzLoader)({ device, slot: trackSlot }));
1136
- track.beatGrid = anlz.beatGrid;
1137
- track.cueAndLoops = anlz.cueAndLoops;
1179
+ const anlz = await (0, rekordbox_1.loadAnlz)(dbTrack, 'DAT', (0, utils_1.anlzLoader)({ device, slot: trackSlot }));
1180
+ const track = {
1181
+ ...dbTrack,
1182
+ beatGrid: anlz.beatGrid,
1183
+ waveformHd: null,
1184
+ };
1138
1185
  return track;
1139
1186
  }
1140
1187
 
@@ -1215,6 +1262,48 @@ async function viaLocal(local, opts) {
1215
1262
  }
1216
1263
 
1217
1264
 
1265
+ /***/ },
1266
+
1267
+ /***/ "./src/db/getTrackAnalysis.ts"
1268
+ /*!************************************!*\
1269
+ !*** ./src/db/getTrackAnalysis.ts ***!
1270
+ \************************************/
1271
+ (__unused_webpack_module, exports, __webpack_require__) {
1272
+
1273
+ "use strict";
1274
+
1275
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
1276
+ exports.viaLocal = viaLocal;
1277
+ const rekordbox_1 = __webpack_require__(/*! src/localdb/rekordbox */ "./src/localdb/rekordbox.ts");
1278
+ const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
1279
+ const utils_1 = __webpack_require__(/*! ./utils */ "./src/db/utils.ts");
1280
+ async function viaLocal(local, device, opts) {
1281
+ var _a, _b, _c, _d;
1282
+ const { deviceId, trackSlot, track } = opts;
1283
+ if (trackSlot !== types_1.MediaSlot.USB && trackSlot !== types_1.MediaSlot.SD) {
1284
+ throw new Error('Expected USB or SD slot for local database query');
1285
+ }
1286
+ const conn = await local.get(deviceId, trackSlot);
1287
+ if (conn === null) {
1288
+ return null;
1289
+ }
1290
+ const resolver = (0, utils_1.anlzLoader)({ device, slot: trackSlot });
1291
+ const [extResult, twoxResult] = await Promise.all([
1292
+ (0, rekordbox_1.loadAnlz)(track, 'EXT', resolver),
1293
+ (0, rekordbox_1.loadAnlz)(track, '2EX', resolver).catch(() => null),
1294
+ ]);
1295
+ return {
1296
+ extendedCues: extResult.extendedCues,
1297
+ songStructure: extResult.songStructure,
1298
+ waveformColorPreview: (_a = extResult.waveformColorPreview) !== null && _a !== void 0 ? _a : undefined,
1299
+ waveformHd: extResult.waveformHd,
1300
+ waveform3BandPreview: (_b = twoxResult === null || twoxResult === void 0 ? void 0 : twoxResult.waveform3BandPreview) !== null && _b !== void 0 ? _b : null,
1301
+ waveform3BandDetail: (_c = twoxResult === null || twoxResult === void 0 ? void 0 : twoxResult.waveform3BandDetail) !== null && _c !== void 0 ? _c : null,
1302
+ vocalConfig: (_d = twoxResult === null || twoxResult === void 0 ? void 0 : twoxResult.vocalConfig) !== null && _d !== void 0 ? _d : null,
1303
+ };
1304
+ }
1305
+
1306
+
1218
1307
  /***/ },
1219
1308
 
1220
1309
  /***/ "./src/db/getWaveforms.ts"
@@ -1249,9 +1338,30 @@ async function viaRemote(remote, opts) {
1249
1338
  args: { trackId: track.id },
1250
1339
  span,
1251
1340
  });
1252
- return { waveformHd };
1341
+ const isStreaming = trackType === types_1.TrackType.Streaming;
1342
+ if (!isStreaming) {
1343
+ return { waveformHd };
1344
+ }
1345
+ // Streaming tracks (e.g. Beatport LINK) have no local ANLZ file but the CDJ
1346
+ // serves waveform preview and detailed via remotedb.
1347
+ const [waveformPreview, waveformDetailed] = await Promise.all([
1348
+ conn.query({
1349
+ queryDescriptor,
1350
+ query: remotedb_1.Query.GetWaveformPreview,
1351
+ args: { trackId: track.id },
1352
+ span,
1353
+ }),
1354
+ conn.query({
1355
+ queryDescriptor,
1356
+ query: remotedb_1.Query.GetWaveformDetailed,
1357
+ args: { trackId: track.id },
1358
+ span,
1359
+ }),
1360
+ ]);
1361
+ return { waveformHd, waveformPreview, waveformDetailed };
1253
1362
  }
1254
1363
  async function viaLocal(local, device, opts) {
1364
+ var _a;
1255
1365
  const { deviceId, trackSlot, track } = opts;
1256
1366
  if (trackSlot !== types_1.MediaSlot.USB && trackSlot !== types_1.MediaSlot.SD) {
1257
1367
  throw new Error('Expected USB or SD slot for remote database query');
@@ -1261,7 +1371,10 @@ async function viaLocal(local, device, opts) {
1261
1371
  return null;
1262
1372
  }
1263
1373
  const anlz = await (0, rekordbox_1.loadAnlz)(track, 'EXT', (0, utils_1.anlzLoader)({ device, slot: trackSlot }));
1264
- return { waveformHd: anlz.waveformHd };
1374
+ return {
1375
+ waveformHd: anlz.waveformHd,
1376
+ waveformColorPreview: (_a = anlz.waveformColorPreview) !== null && _a !== void 0 ? _a : undefined,
1377
+ };
1265
1378
  }
1266
1379
 
1267
1380
 
@@ -1319,7 +1432,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
1319
1432
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
1320
1433
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
1321
1434
  };
1322
- var _Database_hostDevice, _Database_deviceManager, _Database_localDatabase, _Database_remoteDatabase, _Database_getTrackLookupStrategy, _Database_getMediaLookupStrategy;
1435
+ var _Database_deviceManager, _Database_localDatabase, _Database_remoteDatabase, _Database_getTrackLookupStrategy, _Database_getMediaLookupStrategy;
1323
1436
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1324
1437
  const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
1325
1438
  const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
@@ -1330,6 +1443,7 @@ const GetArtworkThumbnail = __importStar(__webpack_require__(/*! ./getArtworkThu
1330
1443
  const GetFile = __importStar(__webpack_require__(/*! ./getFile */ "./src/db/getFile.ts"));
1331
1444
  const GetMetadata = __importStar(__webpack_require__(/*! ./getMetadata */ "./src/db/getMetadata.ts"));
1332
1445
  const GetPlaylist = __importStar(__webpack_require__(/*! ./getPlaylist */ "./src/db/getPlaylist.ts"));
1446
+ const GetTrackAnalysis = __importStar(__webpack_require__(/*! ./getTrackAnalysis */ "./src/db/getTrackAnalysis.ts"));
1333
1447
  const GetWaveforms = __importStar(__webpack_require__(/*! ./getWaveforms */ "./src/db/getWaveforms.ts"));
1334
1448
  var LookupStrategy;
1335
1449
  (function (LookupStrategy) {
@@ -1342,8 +1456,7 @@ var LookupStrategy;
1342
1456
  * network for information from their databases.
1343
1457
  */
1344
1458
  class Database {
1345
- constructor(hostDevice, local, remote, deviceManager) {
1346
- _Database_hostDevice.set(this, void 0);
1459
+ constructor(local, remote, deviceManager) {
1347
1460
  _Database_deviceManager.set(this, void 0);
1348
1461
  /**
1349
1462
  * The local database service, used when querying media devices connected
@@ -1357,8 +1470,13 @@ class Database {
1357
1470
  _Database_remoteDatabase.set(this, void 0);
1358
1471
  _Database_getTrackLookupStrategy.set(this, (device, type) => {
1359
1472
  const isUnanalyzed = type === types_1.TrackType.AudioCD || type === types_1.TrackType.Unanalyzed;
1360
- const requiresCdjRemote = device.type === types_1.DeviceType.CDJ && isUnanalyzed && this.cdjSupportsRemotedb;
1361
- return device.type === types_1.DeviceType.Rekordbox || requiresCdjRemote
1473
+ const isStreaming = type === types_1.TrackType.Streaming;
1474
+ // Unanalyzed and streaming tracks on CDJs must use RemoteDB
1475
+ // (streaming services like Beatport have no local database)
1476
+ if (device.type === types_1.DeviceType.CDJ && (isUnanalyzed || isStreaming)) {
1477
+ return LookupStrategy.Remote;
1478
+ }
1479
+ return device.type === types_1.DeviceType.Rekordbox
1362
1480
  ? LookupStrategy.Remote
1363
1481
  : device.type === types_1.DeviceType.CDJ && type === types_1.TrackType.RB
1364
1482
  ? LookupStrategy.Local
@@ -1369,19 +1487,10 @@ class Database {
1369
1487
  : device.type === types_1.DeviceType.Rekordbox
1370
1488
  ? LookupStrategy.NoneAvailable
1371
1489
  : LookupStrategy.Local);
1372
- __classPrivateFieldSet(this, _Database_hostDevice, hostDevice, "f");
1373
1490
  __classPrivateFieldSet(this, _Database_localDatabase, local, "f");
1374
1491
  __classPrivateFieldSet(this, _Database_remoteDatabase, remote, "f");
1375
1492
  __classPrivateFieldSet(this, _Database_deviceManager, deviceManager, "f");
1376
1493
  }
1377
- /**
1378
- * Reports weather or not the CDJs can be communicated to over the remote
1379
- * database protocol. This is important when trying to query for unanalyzed or
1380
- * compact disc tracks.
1381
- */
1382
- get cdjSupportsRemotedb() {
1383
- return __classPrivateFieldGet(this, _Database_hostDevice, "f").id > 0 && __classPrivateFieldGet(this, _Database_hostDevice, "f").id < 7;
1384
- }
1385
1494
  /**
1386
1495
  * Get the database type (oneLibrary or pdb) for a loaded device slot.
1387
1496
  * Returns null if the slot uses remote database or no database is loaded.
@@ -1542,6 +1651,35 @@ class Database {
1542
1651
  tx.finish();
1543
1652
  return waveforms;
1544
1653
  }
1654
+ /**
1655
+ * Retrieves all analysis data from the EXT file for a track.
1656
+ * Returns extended cues, song structure, waveform color preview, and HD waveform.
1657
+ */
1658
+ async getTrackAnalysis(opts) {
1659
+ const { deviceId, trackType, trackSlot, span } = opts;
1660
+ const tx = span
1661
+ ? span.startChild({ op: 'dbGetTrackAnalysis' })
1662
+ : Telemetry.startTransaction({ name: 'dbGetTrackAnalysis' });
1663
+ tx.setTag('deviceId', deviceId.toString());
1664
+ tx.setTag('trackType', (0, utils_1.getTrackTypeName)(trackType));
1665
+ tx.setTag('trackSlot', (0, utils_1.getSlotName)(trackSlot));
1666
+ const callOpts = { ...opts, span: tx };
1667
+ const device = await __classPrivateFieldGet(this, _Database_deviceManager, "f").getDeviceEnsured(deviceId);
1668
+ if (device === null) {
1669
+ tx.finish();
1670
+ return null;
1671
+ }
1672
+ const strategy = __classPrivateFieldGet(this, _Database_getTrackLookupStrategy, "f").call(this, device, trackType);
1673
+ let analysis = null;
1674
+ if (strategy === LookupStrategy.Local) {
1675
+ analysis = await GetTrackAnalysis.viaLocal(__classPrivateFieldGet(this, _Database_localDatabase, "f"), device, callOpts);
1676
+ }
1677
+ if (strategy === LookupStrategy.NoneAvailable || strategy === LookupStrategy.Remote) {
1678
+ tx.setStatus(telemetry_1.SpanStatus.Unavailable);
1679
+ }
1680
+ tx.finish();
1681
+ return analysis;
1682
+ }
1545
1683
  /**
1546
1684
  * Retrieve folders, playlists, and tracks within the playlist tree. The id
1547
1685
  * may be left undefined to query the root of the playlist tree.
@@ -1576,7 +1714,7 @@ class Database {
1576
1714
  return contents;
1577
1715
  }
1578
1716
  }
1579
- _Database_hostDevice = new WeakMap(), _Database_deviceManager = new WeakMap(), _Database_localDatabase = new WeakMap(), _Database_remoteDatabase = new WeakMap(), _Database_getTrackLookupStrategy = new WeakMap(), _Database_getMediaLookupStrategy = new WeakMap();
1717
+ _Database_deviceManager = new WeakMap(), _Database_localDatabase = new WeakMap(), _Database_remoteDatabase = new WeakMap(), _Database_getTrackLookupStrategy = new WeakMap(), _Database_getMediaLookupStrategy = new WeakMap();
1580
1718
  exports["default"] = Database;
1581
1719
 
1582
1720
 
@@ -1858,16 +1996,17 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
1858
1996
  var _LocalDatabase_hostDevice, _LocalDatabase_deviceManager, _LocalDatabase_statusEmitter, _LocalDatabase_emitter, _LocalDatabase_slotLocks, _LocalDatabase_dbs, _LocalDatabase_preference, _LocalDatabase_handleDeviceRemoved, _LocalDatabase_fetchFileWithFallback, _LocalDatabase_tryLoadOneLibrary, _LocalDatabase_loadPdbDatabase, _LocalDatabase_hydrateDatabase;
1859
1997
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1860
1998
  const async_mutex_1 = __webpack_require__(/*! async-mutex */ "async-mutex");
1999
+ const onelibrary_connect_1 = __webpack_require__(/*! onelibrary-connect */ "onelibrary-connect");
1861
2000
  const crypto_1 = __webpack_require__(/*! crypto */ "crypto");
1862
2001
  const events_1 = __webpack_require__(/*! events */ "events");
1863
2002
  const fs = __importStar(__webpack_require__(/*! fs */ "fs"));
1864
2003
  const os = __importStar(__webpack_require__(/*! os */ "os"));
1865
2004
  const path = __importStar(__webpack_require__(/*! path */ "path"));
2005
+ const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
1866
2006
  const nfs_1 = __webpack_require__(/*! src/nfs */ "./src/nfs/index.ts");
1867
2007
  const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
1868
2008
  const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
1869
2009
  const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
1870
- const onelibrary_1 = __webpack_require__(/*! ./onelibrary */ "./src/localdb/onelibrary.ts");
1871
2010
  const orm_1 = __webpack_require__(/*! ./orm */ "./src/localdb/orm.ts");
1872
2011
  const rekordbox_1 = __webpack_require__(/*! ./rekordbox */ "./src/localdb/rekordbox.ts");
1873
2012
  /**
@@ -1944,7 +2083,9 @@ class LocalDatabase {
1944
2083
  * Helper to fetch a file from device, trying both dotted and non-dotted paths
1945
2084
  */
1946
2085
  _LocalDatabase_fetchFileWithFallback.set(this, async (device, slot, basePath, tx) => {
1947
- const attemptOrder = process.platform === 'win32' ? [basePath, `.${basePath}`] : [`.${basePath}`, basePath];
2086
+ const attemptOrder = process.platform === 'win32'
2087
+ ? [basePath, `.${basePath}`]
2088
+ : [`.${basePath}`, basePath];
1948
2089
  try {
1949
2090
  return await (0, nfs_1.fetchFile)({
1950
2091
  device,
@@ -1955,7 +2096,7 @@ class LocalDatabase {
1955
2096
  });
1956
2097
  }
1957
2098
  catch {
1958
- return await (0, nfs_1.fetchFile)({
2099
+ return (0, nfs_1.fetchFile)({
1959
2100
  device,
1960
2101
  slot,
1961
2102
  path: attemptOrder[1],
@@ -1976,7 +2117,7 @@ class LocalDatabase {
1976
2117
  const tempDir = os.tmpdir();
1977
2118
  const tempFile = path.join(tempDir, `prolink-onelibrary-${device.id}-${slot}-${Date.now()}.db`);
1978
2119
  fs.writeFileSync(tempFile, dbData);
1979
- const adapter = new onelibrary_1.OneLibraryAdapter(tempFile);
2120
+ const adapter = new onelibrary_connect_1.OneLibraryAdapter(tempFile);
1980
2121
  return { adapter, tempFile };
1981
2122
  }
1982
2123
  catch {
@@ -2089,8 +2230,10 @@ class LocalDatabase {
2089
2230
  if (device === undefined) {
2090
2231
  return null;
2091
2232
  }
2092
- if (device.type !== types_1.DeviceType.CDJ) {
2093
- throw new Error('Cannot create database from devices that are not CDJs');
2233
+ if (device.type !== types_1.DeviceType.CDJ ||
2234
+ device.id < constants_1.MIN_CDJ_DEVICE_ID ||
2235
+ device.id > constants_1.MAX_CDJ_DEVICE_ID) {
2236
+ return null;
2094
2237
  }
2095
2238
  let media;
2096
2239
  try {
@@ -2133,7 +2276,9 @@ class LocalDatabase {
2133
2276
  */
2134
2277
  async preload() {
2135
2278
  const allDevices = [...__classPrivateFieldGet(this, _LocalDatabase_deviceManager, "f").devices.values()];
2136
- const cdjDevices = allDevices.filter(device => device.type === types_1.DeviceType.CDJ);
2279
+ const cdjDevices = allDevices.filter(device => device.type === types_1.DeviceType.CDJ &&
2280
+ device.id >= constants_1.MIN_CDJ_DEVICE_ID &&
2281
+ device.id <= constants_1.MAX_CDJ_DEVICE_ID);
2137
2282
  if (cdjDevices.length === 0) {
2138
2283
  return;
2139
2284
  }
@@ -2254,6 +2399,9 @@ var RekordboxAnlz = (function() {
2254
2399
  WAVE_SCROLL: 1347900979,
2255
2400
  WAVE_COLOR_PREVIEW: 1347900980,
2256
2401
  WAVE_COLOR_SCROLL: 1347900981,
2402
+ WAVE_COLOR_3BAND_PREVIEW: 1347900982,
2403
+ WAVE_COLOR_3BAND_DETAIL: 1347900983,
2404
+ VOCAL_CONFIG: 1347900995,
2257
2405
 
2258
2406
  1346588466: "CUES_2",
2259
2407
  1346588482: "CUES",
@@ -2266,6 +2414,9 @@ var RekordboxAnlz = (function() {
2266
2414
  1347900979: "WAVE_SCROLL",
2267
2415
  1347900980: "WAVE_COLOR_PREVIEW",
2268
2416
  1347900981: "WAVE_COLOR_SCROLL",
2417
+ 1347900982: "WAVE_COLOR_3BAND_PREVIEW",
2418
+ 1347900983: "WAVE_COLOR_3BAND_DETAIL",
2419
+ 1347900995: "VOCAL_CONFIG",
2269
2420
  });
2270
2421
 
2271
2422
  RekordboxAnlz.TrackMood = Object.freeze({
@@ -2387,6 +2538,39 @@ var RekordboxAnlz = (function() {
2387
2538
  return PathTag;
2388
2539
  })();
2389
2540
 
2541
+ /**
2542
+ * A 3-band color waveform preview (PWV6, found in .2EX files).
2543
+ * Same resolution as PWV4 (typically 1200 entries) but with 3 bytes
2544
+ * per entry representing low, mid, and high frequency band amplitudes.
2545
+ * Layout is identical to wave_color_preview_tag (PWV4).
2546
+ * @see {@link https://djl-analysis.deepsymmetry.org/djl-analysis/track-metadata.html#color-3band-preview-waveform|Source}
2547
+ */
2548
+
2549
+ var WaveColor3bandPreviewTag = RekordboxAnlz.WaveColor3bandPreviewTag = (function() {
2550
+ function WaveColor3bandPreviewTag(_io, _parent, _root) {
2551
+ this._io = _io;
2552
+ this._parent = _parent;
2553
+ this._root = _root || this;
2554
+
2555
+ this._read();
2556
+ }
2557
+ WaveColor3bandPreviewTag.prototype._read = function() {
2558
+ this.lenEntryBytes = this._io.readU4be();
2559
+ this.lenEntries = this._io.readU4be();
2560
+ this.entries = this._io.readBytes((this.lenEntries * this.lenEntryBytes));
2561
+ }
2562
+
2563
+ /**
2564
+ * The size of each entry, in bytes. Always 3 (low, mid, high).
2565
+ */
2566
+
2567
+ /**
2568
+ * The number of waveform data points. Typically 1200.
2569
+ */
2570
+
2571
+ return WaveColor3bandPreviewTag;
2572
+ })();
2573
+
2390
2574
  /**
2391
2575
  * Stores a waveform preview image suitable for display above
2392
2576
  * the touch strip for jumping to a track position.
@@ -2532,6 +2716,76 @@ var RekordboxAnlz = (function() {
2532
2716
  return WaveColorPreviewTag;
2533
2717
  })();
2534
2718
 
2719
+ /**
2720
+ * A 3-band color detail waveform (PWV7, found in .2EX files).
2721
+ * Higher resolution than PWV6. Each entry is 3 bytes (low, mid, high).
2722
+ * Layout is identical to wave_color_scroll_tag (PWV5).
2723
+ * @see {@link https://djl-analysis.deepsymmetry.org/djl-analysis/track-metadata.html#color-3band-detail-waveform|Source}
2724
+ */
2725
+
2726
+ var WaveColor3bandDetailTag = RekordboxAnlz.WaveColor3bandDetailTag = (function() {
2727
+ function WaveColor3bandDetailTag(_io, _parent, _root) {
2728
+ this._io = _io;
2729
+ this._parent = _parent;
2730
+ this._root = _root || this;
2731
+
2732
+ this._read();
2733
+ }
2734
+ WaveColor3bandDetailTag.prototype._read = function() {
2735
+ this.lenEntryBytes = this._io.readU4be();
2736
+ this.lenEntries = this._io.readU4be();
2737
+ this._unnamed2 = this._io.readU4be();
2738
+ this.entries = this._io.readBytes((this.lenEntries * this.lenEntryBytes));
2739
+ }
2740
+
2741
+ /**
2742
+ * The size of each entry, in bytes. Always 3 (low, mid, high).
2743
+ */
2744
+
2745
+ /**
2746
+ * The number of columns of waveform data.
2747
+ */
2748
+
2749
+ return WaveColor3bandDetailTag;
2750
+ })();
2751
+
2752
+ /**
2753
+ * Vocal detection configuration (PWVC, found in .2EX files).
2754
+ * Contains threshold values used to classify frequency content
2755
+ * as vocal or non-vocal. Body is 8 bytes: unknown(2) + 3 thresholds(2 each).
2756
+ * Observed threshold ranges: low 80-114, mid 80-146, high 98-159.
2757
+ */
2758
+
2759
+ var VocalConfigTag = RekordboxAnlz.VocalConfigTag = (function() {
2760
+ function VocalConfigTag(_io, _parent, _root) {
2761
+ this._io = _io;
2762
+ this._parent = _parent;
2763
+ this._root = _root || this;
2764
+
2765
+ this._read();
2766
+ }
2767
+ VocalConfigTag.prototype._read = function() {
2768
+ this._unnamed0 = this._io.readU2be();
2769
+ this.thresholdLow = this._io.readU2be();
2770
+ this.thresholdMid = this._io.readU2be();
2771
+ this.thresholdHigh = this._io.readU2be();
2772
+ }
2773
+
2774
+ /**
2775
+ * Low frequency vocal detection threshold. Observed range: 80-114.
2776
+ */
2777
+
2778
+ /**
2779
+ * Mid frequency vocal detection threshold. Observed range: 80-146.
2780
+ */
2781
+
2782
+ /**
2783
+ * High frequency vocal detection threshold. Observed range: 98-159.
2784
+ */
2785
+
2786
+ return VocalConfigTag;
2787
+ })();
2788
+
2535
2789
  var PhraseHigh = RekordboxAnlz.PhraseHigh = (function() {
2536
2790
  function PhraseHigh(_io, _parent, _root) {
2537
2791
  this._io = _io;
@@ -3039,6 +3293,11 @@ var RekordboxAnlz = (function() {
3039
3293
  var _io__raw_body = new KaitaiStream(this._raw_body);
3040
3294
  this.body = new BeatGridTag(_io__raw_body, this, this._root);
3041
3295
  break;
3296
+ case RekordboxAnlz.SectionTags.VOCAL_CONFIG:
3297
+ this._raw_body = this._io.readBytes((this.lenTag - 12));
3298
+ var _io__raw_body = new KaitaiStream(this._raw_body);
3299
+ this.body = new VocalConfigTag(_io__raw_body, this, this._root);
3300
+ break;
3042
3301
  case RekordboxAnlz.SectionTags.WAVE_PREVIEW:
3043
3302
  this._raw_body = this._io.readBytes((this.lenTag - 12));
3044
3303
  var _io__raw_body = new KaitaiStream(this._raw_body);
@@ -3059,6 +3318,16 @@ var RekordboxAnlz = (function() {
3059
3318
  var _io__raw_body = new KaitaiStream(this._raw_body);
3060
3319
  this.body = new WavePreviewTag(_io__raw_body, this, this._root);
3061
3320
  break;
3321
+ case RekordboxAnlz.SectionTags.WAVE_COLOR_3BAND_DETAIL:
3322
+ this._raw_body = this._io.readBytes((this.lenTag - 12));
3323
+ var _io__raw_body = new KaitaiStream(this._raw_body);
3324
+ this.body = new WaveColor3bandDetailTag(_io__raw_body, this, this._root);
3325
+ break;
3326
+ case RekordboxAnlz.SectionTags.WAVE_COLOR_3BAND_PREVIEW:
3327
+ this._raw_body = this._io.readBytes((this.lenTag - 12));
3328
+ var _io__raw_body = new KaitaiStream(this._raw_body);
3329
+ this.body = new WaveColor3bandPreviewTag(_io__raw_body, this, this._root);
3330
+ break;
3062
3331
  default:
3063
3332
  this._raw_body = this._io.readBytes((this.lenTag - 12));
3064
3333
  var _io__raw_body = new KaitaiStream(this._raw_body);
@@ -4874,141 +5143,14 @@ return RekordboxPdb;
4874
5143
 
4875
5144
  /***/ },
4876
5145
 
4877
- /***/ "./src/localdb/onelibrary-schema.ts"
4878
- /*!******************************************!*\
4879
- !*** ./src/localdb/onelibrary-schema.ts ***!
4880
- \******************************************/
4881
- (__unused_webpack_module, exports) {
4882
-
4883
- "use strict";
4884
-
4885
- /**
4886
- * TypeScript interfaces for the OneLibrary (exportLibrary.db) SQLite schema.
4887
- *
4888
- * These match the actual column names in the SQLite database,
4889
- * which differ from the legacy PDB format.
4890
- *
4891
- * Note: Column names use camelCase in the database (e.g., bpmx100, titleForSearch)
4892
- */
4893
- Object.defineProperty(exports, "__esModule", ({ value: true }));
4894
- exports.MenuItemKind = exports.MyTagAttribute = exports.PlaylistAttribute = exports.CueKind = void 0;
4895
- // ============================================================================
4896
- // Cue Kind Constants
4897
- // ============================================================================
4898
- /**
4899
- * Cue point types based on the 'kind' field in the cue table.
4900
- *
4901
- * Note: These values may vary. Verify with actual data.
4902
- */
4903
- exports.CueKind = {
4904
- MEMORY_CUE: 0,
4905
- HOT_CUE: 1,
4906
- // Additional types may exist
4907
- };
4908
- /**
4909
- * Playlist attribute types based on the 'attribute' field.
4910
- */
4911
- exports.PlaylistAttribute = {
4912
- PLAYLIST: 0,
4913
- FOLDER: 1,
4914
- };
4915
- /**
4916
- * MyTag attribute types based on the 'attribute' field.
4917
- */
4918
- exports.MyTagAttribute = {
4919
- TAG: 0,
4920
- FOLDER: 1,
4921
- };
4922
- // ============================================================================
4923
- // Menu Item Kind Constants
4924
- // ============================================================================
4925
- /**
4926
- * Menu item kinds for browsing categories.
4927
- * These match the 'kind' field in the menuItem table.
4928
- */
4929
- exports.MenuItemKind = {
4930
- GENRE: 128,
4931
- ARTIST: 129,
4932
- ALBUM: 130,
4933
- TRACK: 131,
4934
- PLAYLIST: 132,
4935
- BPM: 133,
4936
- RATING: 134,
4937
- YEAR: 135,
4938
- REMIXER: 136,
4939
- LABEL: 137,
4940
- ORIGINAL_ARTIST: 138,
4941
- KEY: 139,
4942
- DATE_ADDED: 140,
4943
- CUE: 141,
4944
- COLOR: 142,
4945
- FOLDER: 144,
4946
- SEARCH: 145,
4947
- TIME: 146,
4948
- BITRATE: 147,
4949
- FILE_NAME: 148,
4950
- HISTORY: 149,
4951
- COMMENTS: 150,
4952
- DJ_PLAY_COUNT: 151,
4953
- HOT_CUE_BANK: 152,
4954
- DEFAULT: 161,
4955
- ALPHABET: 162,
4956
- MATCHING: 170,
4957
- };
4958
-
4959
-
4960
- /***/ },
4961
-
4962
- /***/ "./src/localdb/onelibrary.ts"
4963
- /*!***********************************!*\
4964
- !*** ./src/localdb/onelibrary.ts ***!
4965
- \***********************************/
5146
+ /***/ "./src/localdb/orm.ts"
5147
+ /*!****************************!*\
5148
+ !*** ./src/localdb/orm.ts ***!
5149
+ \****************************/
4966
5150
  (__unused_webpack_module, exports, __webpack_require__) {
4967
5151
 
4968
5152
  "use strict";
4969
5153
 
4970
- /**
4971
- * OneLibrary Database Adapter
4972
- *
4973
- * Provides an interface for reading the OneLibrary (exportLibrary.db) SQLite database
4974
- * used by modern rekordbox versions and Pioneer DJ devices.
4975
- *
4976
- * The database is encrypted with SQLCipher 4. The encryption key is derived from
4977
- * a hardcoded obfuscated blob.
4978
- */
4979
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4980
- if (k2 === undefined) k2 = k;
4981
- var desc = Object.getOwnPropertyDescriptor(m, k);
4982
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
4983
- desc = { enumerable: true, get: function() { return m[k]; } };
4984
- }
4985
- Object.defineProperty(o, k2, desc);
4986
- }) : (function(o, m, k, k2) {
4987
- if (k2 === undefined) k2 = k;
4988
- o[k2] = m[k];
4989
- }));
4990
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
4991
- Object.defineProperty(o, "default", { enumerable: true, value: v });
4992
- }) : function(o, v) {
4993
- o["default"] = v;
4994
- });
4995
- var __importStar = (this && this.__importStar) || (function () {
4996
- var ownKeys = function(o) {
4997
- ownKeys = Object.getOwnPropertyNames || function (o) {
4998
- var ar = [];
4999
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
5000
- return ar;
5001
- };
5002
- return ownKeys(o);
5003
- };
5004
- return function (mod) {
5005
- if (mod && mod.__esModule) return mod;
5006
- var result = {};
5007
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
5008
- __setModuleDefault(result, mod);
5009
- return result;
5010
- };
5011
- })();
5012
5154
  var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
5013
5155
  if (kind === "m") throw new TypeError("Private method is not writable");
5014
5156
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
@@ -5023,673 +5165,14 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
5023
5165
  var __importDefault = (this && this.__importDefault) || function (mod) {
5024
5166
  return (mod && mod.__esModule) ? mod : { "default": mod };
5025
5167
  };
5026
- var _OneLibraryAdapter_instances, _OneLibraryAdapter_db, _OneLibraryAdapter_stmtCache, _OneLibraryAdapter_getStmt, _OneLibraryAdapter_contentToTrack, _OneLibraryAdapter_cueToCueAndLoop, _OneLibraryAdapter_playlistRowToPlaylist, _OneLibraryAdapter_myTagRowToMyTag;
5168
+ var _MetadataORM_conn, _MetadataORM_stmtCache, _MetadataORM_inTransaction;
5027
5169
  Object.defineProperty(exports, "__esModule", ({ value: true }));
5028
- exports.OneLibraryAdapter = void 0;
5029
- exports.getEncryptionKey = getEncryptionKey;
5030
- exports.openOneLibraryDb = openOneLibraryDb;
5170
+ exports.MetadataORM = exports.Table = void 0;
5031
5171
  const better_sqlite3_multiple_ciphers_1 = __importDefault(__webpack_require__(/*! better-sqlite3-multiple-ciphers */ "better-sqlite3-multiple-ciphers"));
5032
- const zlib = __importStar(__webpack_require__(/*! zlib */ "zlib"));
5033
- const onelibrary_schema_1 = __webpack_require__(/*! ./onelibrary-schema */ "./src/localdb/onelibrary-schema.ts");
5034
- // ============================================================================
5035
- // Encryption Key Derivation
5036
- // ============================================================================
5037
- /**
5038
- * The obfuscated encryption key blob from pyrekordbox
5039
- */
5040
- const BLOB = Buffer.from('PN_1dH8$oLJY)16j_RvM6qphWw`476>;C1cWmI#se(PG`j}~xAjlufj?`#0i{;=glh(SkW)y0>n?YEiD`l%t(', 'ascii');
5041
- /**
5042
- * XOR key used for deobfuscation
5043
- */
5044
- const BLOB_KEY = Buffer.from('657f48f84c437cc1', 'ascii');
5045
- /**
5046
- * Base85 (RFC 1924) decode
5047
- */
5048
- function base85Decode(input) {
5049
- const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~';
5050
- const charToValue = new Map();
5051
- for (let i = 0; i < alphabet.length; i++) {
5052
- charToValue.set(alphabet[i], i);
5053
- }
5054
- const inputStr = input.toString('ascii');
5055
- const result = [];
5056
- for (let i = 0; i < inputStr.length; i += 5) {
5057
- const chunk = inputStr.slice(i, i + 5);
5058
- let value = 0;
5059
- for (const char of chunk) {
5060
- const v = charToValue.get(char);
5061
- if (v === undefined) {
5062
- throw new Error(`Invalid base85 character: ${char}`);
5063
- }
5064
- value = value * 85 + v;
5065
- }
5066
- const bytes = [
5067
- (value >> 24) & 0xff,
5068
- (value >> 16) & 0xff,
5069
- (value >> 8) & 0xff,
5070
- value & 0xff,
5071
- ];
5072
- const numBytes = chunk.length === 5 ? 4 : chunk.length - 1;
5073
- result.push(...bytes.slice(0, numBytes));
5074
- }
5075
- return Buffer.from(result);
5076
- }
5077
- /**
5078
- * Deobfuscate the blob to get the encryption key
5079
- */
5080
- function deobfuscate(blob) {
5081
- const decoded = base85Decode(blob);
5082
- const xored = Buffer.alloc(decoded.length);
5083
- for (let i = 0; i < decoded.length; i++) {
5084
- xored[i] = decoded[i] ^ BLOB_KEY[i % BLOB_KEY.length];
5085
- }
5086
- const decompressed = zlib.inflateSync(xored);
5087
- return decompressed.toString('utf-8');
5088
- }
5089
- /**
5090
- * Get the SQLCipher encryption key for OneLibrary databases
5091
- */
5092
- function getEncryptionKey() {
5093
- const key = deobfuscate(BLOB);
5094
- if (!key.startsWith('r8gd')) {
5095
- throw new Error('Invalid encryption key derived');
5096
- }
5097
- return key;
5098
- }
5099
- // ============================================================================
5100
- // Database Connection
5101
- // ============================================================================
5172
+ const lodash_1 = __webpack_require__(/*! lodash */ "lodash");
5173
+ const schema_1 = __webpack_require__(/*! ./schema */ "./src/localdb/schema.ts");
5102
5174
  /**
5103
- * Open a OneLibrary database with SQLCipher decryption
5104
- */
5105
- function openOneLibraryDb(dbPath) {
5106
- const key = getEncryptionKey();
5107
- const db = new better_sqlite3_multiple_ciphers_1.default(dbPath, { readonly: true });
5108
- db.pragma('cipher = sqlcipher');
5109
- db.pragma('legacy = 4');
5110
- db.pragma(`key = '${key}'`);
5111
- return db;
5112
- }
5113
- // ============================================================================
5114
- // OneLibrary Adapter
5115
- // ============================================================================
5116
- /**
5117
- * Adapter for OneLibrary database that matches the ORM interface.
5118
- * Queries the SQLite file directly instead of hydrating into memory.
5119
- */
5120
- class OneLibraryAdapter {
5121
- constructor(dbPath) {
5122
- _OneLibraryAdapter_instances.add(this);
5123
- this.type = 'oneLibrary';
5124
- _OneLibraryAdapter_db.set(this, void 0);
5125
- _OneLibraryAdapter_stmtCache.set(this, new Map());
5126
- __classPrivateFieldSet(this, _OneLibraryAdapter_db, openOneLibraryDb(dbPath), "f");
5127
- }
5128
- /**
5129
- * Close the database connection
5130
- */
5131
- close() {
5132
- __classPrivateFieldGet(this, _OneLibraryAdapter_stmtCache, "f").clear();
5133
- __classPrivateFieldGet(this, _OneLibraryAdapter_db, "f").close();
5134
- }
5135
- // ==========================================================================
5136
- // Track Queries
5137
- // ==========================================================================
5138
- /**
5139
- * Find a track by ID
5140
- */
5141
- findTrack(id) {
5142
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5143
- SELECT c.*,
5144
- a.name as artistName,
5145
- al.name as albumName,
5146
- g.name as genreName,
5147
- k.name as keyName,
5148
- col.name as colorName,
5149
- lbl.name as labelName,
5150
- img.path as artworkPath,
5151
- remix.name as remixerName,
5152
- orig.name as originalArtistName,
5153
- comp.name as composerName
5154
- FROM content c
5155
- LEFT JOIN artist a ON c.artist_id_artist = a.artist_id
5156
- LEFT JOIN album al ON c.album_id = al.album_id
5157
- LEFT JOIN genre g ON c.genre_id = g.genre_id
5158
- LEFT JOIN key k ON c.key_id = k.key_id
5159
- LEFT JOIN color col ON c.color_id = col.color_id
5160
- LEFT JOIN label lbl ON c.label_id = lbl.label_id
5161
- LEFT JOIN image img ON c.image_id = img.image_id
5162
- LEFT JOIN artist remix ON c.artist_id_remixer = remix.artist_id
5163
- LEFT JOIN artist orig ON c.artist_id_originalArtist = orig.artist_id
5164
- LEFT JOIN artist comp ON c.artist_id_composer = comp.artist_id
5165
- WHERE c.content_id = ?
5166
- `).get(id);
5167
- if (!row) {
5168
- return null;
5169
- }
5170
- return __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_contentToTrack).call(this, row);
5171
- }
5172
- /**
5173
- * Find all tracks in the database
5174
- */
5175
- findAllTracks() {
5176
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5177
- SELECT c.*,
5178
- a.name as artistName,
5179
- al.name as albumName,
5180
- g.name as genreName,
5181
- k.name as keyName,
5182
- col.name as colorName,
5183
- lbl.name as labelName,
5184
- img.path as artworkPath,
5185
- remix.name as remixerName,
5186
- orig.name as originalArtistName,
5187
- comp.name as composerName
5188
- FROM content c
5189
- LEFT JOIN artist a ON c.artist_id_artist = a.artist_id
5190
- LEFT JOIN album al ON c.album_id = al.album_id
5191
- LEFT JOIN genre g ON c.genre_id = g.genre_id
5192
- LEFT JOIN key k ON c.key_id = k.key_id
5193
- LEFT JOIN color col ON c.color_id = col.color_id
5194
- LEFT JOIN label lbl ON c.label_id = lbl.label_id
5195
- LEFT JOIN image img ON c.image_id = img.image_id
5196
- LEFT JOIN artist remix ON c.artist_id_remixer = remix.artist_id
5197
- LEFT JOIN artist orig ON c.artist_id_originalArtist = orig.artist_id
5198
- LEFT JOIN artist comp ON c.artist_id_composer = comp.artist_id
5199
- `).all();
5200
- return rows.map(row => __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_contentToTrack).call(this, row));
5201
- }
5202
- // ==========================================================================
5203
- // Cue Queries
5204
- // ==========================================================================
5205
- /**
5206
- * Find cue points for a track
5207
- */
5208
- findCues(trackId) {
5209
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5210
- SELECT * FROM cue WHERE content_id = ?
5211
- `).all(trackId);
5212
- return rows.map(row => __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_cueToCueAndLoop).call(this, row));
5213
- }
5214
- // ==========================================================================
5215
- // Playlist Queries
5216
- // ==========================================================================
5217
- /**
5218
- * Find a playlist by ID
5219
- */
5220
- findPlaylistById(playlistId) {
5221
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5222
- SELECT * FROM playlist WHERE playlist_id = ?
5223
- `).get(playlistId);
5224
- return row ? __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_playlistRowToPlaylist).call(this, row) : null;
5225
- }
5226
- /**
5227
- * Query for a list of {folders, playlists, tracks} given a playlist ID.
5228
- * If no ID is provided the root list is queried.
5229
- */
5230
- findPlaylist(playlistId) {
5231
- const parentCondition = playlistId === undefined ? 'playlist_id_parent IS NULL' : 'playlist_id_parent = ?';
5232
- // Lookup playlists / folders for this playlist ID
5233
- const playlistRows = (playlistId === undefined
5234
- ? __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `SELECT * FROM playlist WHERE ${parentCondition}`).all()
5235
- : __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `SELECT * FROM playlist WHERE ${parentCondition}`).all(playlistId));
5236
- const folders = [];
5237
- const playlists = [];
5238
- for (const row of playlistRows) {
5239
- const playlist = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_playlistRowToPlaylist).call(this, row);
5240
- if (playlist.isFolder) {
5241
- folders.push(playlist);
5242
- }
5243
- else {
5244
- playlists.push(playlist);
5245
- }
5246
- }
5247
- // Get track entries for this playlist
5248
- const entryRows = playlistId === undefined
5249
- ? []
5250
- : __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5251
- SELECT * FROM playlist_content WHERE playlist_id = ? ORDER BY sequenceNo
5252
- `).all(playlistId);
5253
- const trackEntries = entryRows.map((row, index) => ({
5254
- id: index, // playlist_content doesn't have a unique ID, use index
5255
- sortIndex: row.sequenceNo,
5256
- playlistId: row.playlist_id,
5257
- trackId: row.content_id,
5258
- }));
5259
- return { folders, playlists, trackEntries };
5260
- }
5261
- /**
5262
- * Get track IDs for a playlist in order
5263
- */
5264
- findPlaylistContents(playlistId) {
5265
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5266
- SELECT content_id FROM playlist_content
5267
- WHERE playlist_id = ? ORDER BY sequenceNo
5268
- `).all(playlistId);
5269
- return rows.map(r => r.content_id);
5270
- }
5271
- // ==========================================================================
5272
- // Reference Table Queries
5273
- // ==========================================================================
5274
- findArtist(artistId) {
5275
- var _a;
5276
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5277
- SELECT * FROM artist WHERE artist_id = ?
5278
- `).get(artistId);
5279
- return row ? { id: row.artist_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
5280
- }
5281
- findAlbum(albumId) {
5282
- var _a;
5283
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5284
- SELECT * FROM album WHERE album_id = ?
5285
- `).get(albumId);
5286
- return row ? { id: row.album_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
5287
- }
5288
- findGenre(genreId) {
5289
- var _a;
5290
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5291
- SELECT * FROM genre WHERE genre_id = ?
5292
- `).get(genreId);
5293
- return row ? { id: row.genre_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
5294
- }
5295
- findKey(keyId) {
5296
- var _a;
5297
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5298
- SELECT * FROM key WHERE key_id = ?
5299
- `).get(keyId);
5300
- return row ? { id: row.key_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
5301
- }
5302
- findColor(colorId) {
5303
- var _a;
5304
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5305
- SELECT * FROM color WHERE color_id = ?
5306
- `).get(colorId);
5307
- return row ? { id: row.color_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
5308
- }
5309
- findLabel(labelId) {
5310
- var _a;
5311
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5312
- SELECT * FROM label WHERE label_id = ?
5313
- `).get(labelId);
5314
- return row ? { id: row.label_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
5315
- }
5316
- findArtwork(imageId) {
5317
- var _a;
5318
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5319
- SELECT * FROM image WHERE image_id = ?
5320
- `).get(imageId);
5321
- return row ? { id: row.image_id, path: (_a = row.path) !== null && _a !== void 0 ? _a : undefined } : null;
5322
- }
5323
- // ==========================================================================
5324
- // MyTag (User Tags) Queries
5325
- // ==========================================================================
5326
- /**
5327
- * Find all root-level MyTags (folders and tags with no parent)
5328
- */
5329
- findMyTags(parentId) {
5330
- const parentCondition = parentId === undefined
5331
- ? 'myTag_id_parent IS NULL OR myTag_id_parent = 0'
5332
- : 'myTag_id_parent = ?';
5333
- const rows = (parentId === undefined
5334
- ? __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `SELECT * FROM myTag WHERE ${parentCondition} ORDER BY sequenceNo`).all()
5335
- : __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `SELECT * FROM myTag WHERE ${parentCondition} ORDER BY sequenceNo`).all(parentId));
5336
- const folders = [];
5337
- const tags = [];
5338
- for (const row of rows) {
5339
- const tag = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_myTagRowToMyTag).call(this, row);
5340
- if (tag.isFolder) {
5341
- folders.push(tag);
5342
- }
5343
- else {
5344
- tags.push(tag);
5345
- }
5346
- }
5347
- return { folders, tags };
5348
- }
5349
- /**
5350
- * Find a MyTag by ID
5351
- */
5352
- findMyTagById(myTagId) {
5353
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5354
- SELECT * FROM myTag WHERE myTag_id = ?
5355
- `).get(myTagId);
5356
- return row ? __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_myTagRowToMyTag).call(this, row) : null;
5357
- }
5358
- /**
5359
- * Get track IDs for a MyTag
5360
- */
5361
- findMyTagContents(myTagId) {
5362
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5363
- SELECT content_id FROM myTag_content WHERE myTag_id = ?
5364
- `).all(myTagId);
5365
- return rows.map(r => r.content_id);
5366
- }
5367
- /**
5368
- * Get all MyTags assigned to a track
5369
- */
5370
- findMyTagsForTrack(trackId) {
5371
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5372
- SELECT t.* FROM myTag t
5373
- INNER JOIN myTag_content tc ON t.myTag_id = tc.myTag_id
5374
- WHERE tc.content_id = ?
5375
- `).all(trackId);
5376
- return rows.map(row => __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_myTagRowToMyTag).call(this, row));
5377
- }
5378
- // ==========================================================================
5379
- // History Queries
5380
- // ==========================================================================
5381
- /**
5382
- * Find all history sessions
5383
- */
5384
- findHistorySessions() {
5385
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5386
- SELECT * FROM history ORDER BY sequenceNo
5387
- `).all();
5388
- return rows.map(row => {
5389
- var _a;
5390
- return ({
5391
- id: row.history_id,
5392
- name: (_a = row.name) !== null && _a !== void 0 ? _a : '',
5393
- parentId: row.history_id_parent,
5394
- });
5395
- });
5396
- }
5397
- /**
5398
- * Get track IDs for a history session in order
5399
- */
5400
- findHistoryContents(historyId) {
5401
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5402
- SELECT content_id FROM history_content
5403
- WHERE history_id = ? ORDER BY sequenceNo
5404
- `).all(historyId);
5405
- return rows.map(r => r.content_id);
5406
- }
5407
- // ==========================================================================
5408
- // Hot Cue Bank Queries
5409
- // ==========================================================================
5410
- /**
5411
- * Find all hot cue bank lists
5412
- */
5413
- findHotCueBankLists() {
5414
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5415
- SELECT * FROM hotCueBankList ORDER BY sequenceNo
5416
- `).all();
5417
- return rows.map(row => {
5418
- var _a;
5419
- return ({
5420
- id: row.hotCueBankList_id,
5421
- name: (_a = row.name) !== null && _a !== void 0 ? _a : '',
5422
- parentId: row.hotCueBankList_id_parent,
5423
- });
5424
- });
5425
- }
5426
- /**
5427
- * Get cue IDs for a hot cue bank list
5428
- */
5429
- findHotCueBankListCues(bankListId) {
5430
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5431
- SELECT cue_id FROM hotCueBankList_cue
5432
- WHERE hotCueBankList_id = ? ORDER BY sequenceNo
5433
- `).all(bankListId);
5434
- return rows.map(r => r.cue_id);
5435
- }
5436
- // ==========================================================================
5437
- // Menu Configuration Queries
5438
- // ==========================================================================
5439
- /**
5440
- * Get all menu items (browse categories)
5441
- */
5442
- findMenuItems() {
5443
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5444
- SELECT * FROM menuItem ORDER BY menuItem_id
5445
- `).all();
5446
- return rows.map(row => {
5447
- var _a, _b;
5448
- return ({
5449
- id: row.menuItem_id,
5450
- kind: (_a = row.kind) !== null && _a !== void 0 ? _a : 0,
5451
- name: (_b = row.name) !== null && _b !== void 0 ? _b : '',
5452
- });
5453
- });
5454
- }
5455
- /**
5456
- * Get visible categories with their menu item info
5457
- */
5458
- findVisibleCategories() {
5459
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5460
- SELECT c.*, m.name as menuName, m.kind as menuKind
5461
- FROM category c
5462
- LEFT JOIN menuItem m ON c.menuItem_id = m.menuItem_id
5463
- WHERE c.isVisible = 1
5464
- ORDER BY c.sequenceNo
5465
- `).all();
5466
- return rows.map(row => {
5467
- var _a, _b, _c;
5468
- return ({
5469
- id: row.category_id,
5470
- menuItemId: (_a = row.menuItem_id) !== null && _a !== void 0 ? _a : 0,
5471
- name: (_b = row.menuName) !== null && _b !== void 0 ? _b : '',
5472
- kind: (_c = row.menuKind) !== null && _c !== void 0 ? _c : 0,
5473
- isVisible: !!row.isVisible,
5474
- });
5475
- });
5476
- }
5477
- /**
5478
- * Get visible sort options
5479
- */
5480
- findVisibleSortOptions() {
5481
- const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5482
- SELECT s.*, m.name as menuName, m.kind as menuKind
5483
- FROM sort s
5484
- LEFT JOIN menuItem m ON s.menuItem_id = m.menuItem_id
5485
- WHERE s.isVisible = 1
5486
- ORDER BY s.sequenceNo
5487
- `).all();
5488
- return rows.map(row => {
5489
- var _a, _b, _c;
5490
- return ({
5491
- id: row.sort_id,
5492
- menuItemId: (_a = row.menuItem_id) !== null && _a !== void 0 ? _a : 0,
5493
- name: (_b = row.menuName) !== null && _b !== void 0 ? _b : '',
5494
- kind: (_c = row.menuKind) !== null && _c !== void 0 ? _c : 0,
5495
- isVisible: !!row.isVisible,
5496
- isSelectedAsSubColumn: !!row.isSelectedAsSubColumn,
5497
- });
5498
- });
5499
- }
5500
- // ==========================================================================
5501
- // Device Property Queries
5502
- // ==========================================================================
5503
- /**
5504
- * Get device properties
5505
- */
5506
- getProperty() {
5507
- var _a, _b, _c, _d, _e;
5508
- const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
5509
- SELECT * FROM property LIMIT 1
5510
- `).get();
5511
- if (!row) {
5512
- return null;
5513
- }
5514
- return {
5515
- deviceName: (_a = row.deviceName) !== null && _a !== void 0 ? _a : '',
5516
- dbVersion: (_b = row.dbVersion) !== null && _b !== void 0 ? _b : '',
5517
- numberOfContents: (_c = row.numberOfContents) !== null && _c !== void 0 ? _c : 0,
5518
- createdDate: (_d = row.createdDate) !== null && _d !== void 0 ? _d : '',
5519
- backgroundColorType: (_e = row.backGroundColorType) !== null && _e !== void 0 ? _e : 0,
5520
- };
5521
- }
5522
- }
5523
- exports.OneLibraryAdapter = OneLibraryAdapter;
5524
- _OneLibraryAdapter_db = new WeakMap(), _OneLibraryAdapter_stmtCache = new WeakMap(), _OneLibraryAdapter_instances = new WeakSet(), _OneLibraryAdapter_getStmt = function _OneLibraryAdapter_getStmt(sql) {
5525
- let stmt = __classPrivateFieldGet(this, _OneLibraryAdapter_stmtCache, "f").get(sql);
5526
- if (!stmt) {
5527
- stmt = __classPrivateFieldGet(this, _OneLibraryAdapter_db, "f").prepare(sql);
5528
- __classPrivateFieldGet(this, _OneLibraryAdapter_stmtCache, "f").set(sql, stmt);
5529
- }
5530
- return stmt;
5531
- }, _OneLibraryAdapter_contentToTrack = function _OneLibraryAdapter_contentToTrack(row) {
5532
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0;
5533
- const track = {
5534
- id: row.content_id,
5535
- title: (_a = row.title) !== null && _a !== void 0 ? _a : '',
5536
- duration: row.length ? row.length / 1000 : 0, // ms to seconds
5537
- bitrate: (_b = row.bitrate) !== null && _b !== void 0 ? _b : undefined,
5538
- tempo: row.bpmx100 ? row.bpmx100 / 100 : 0,
5539
- rating: (_c = row.rating) !== null && _c !== void 0 ? _c : 0,
5540
- comment: (_d = row.djComment) !== null && _d !== void 0 ? _d : '',
5541
- filePath: (_e = row.path) !== null && _e !== void 0 ? _e : '',
5542
- fileName: (_f = row.fileName) !== null && _f !== void 0 ? _f : '',
5543
- trackNumber: (_g = row.trackNo) !== null && _g !== void 0 ? _g : undefined,
5544
- discNumber: (_h = row.discNo) !== null && _h !== void 0 ? _h : undefined,
5545
- sampleRate: (_j = row.samplingRate) !== null && _j !== void 0 ? _j : undefined,
5546
- sampleDepth: (_k = row.bitDepth) !== null && _k !== void 0 ? _k : undefined,
5547
- playCount: (_l = row.djPlayCount) !== null && _l !== void 0 ? _l : undefined,
5548
- year: (_m = row.releaseYear) !== null && _m !== void 0 ? _m : undefined,
5549
- mixName: (_o = row.subtitle) !== null && _o !== void 0 ? _o : undefined,
5550
- autoloadHotcues: !!row.isHotCueAutoLoadOn,
5551
- kuvoPublic: !!row.isKuvoDeliverStatusOn,
5552
- fileSize: (_p = row.fileSize) !== null && _p !== void 0 ? _p : undefined,
5553
- // Normalize analyzePath by trimming the .DAT extension (same as rekordbox.ts)
5554
- // loadAnlz() will append the appropriate extension (.DAT or .EXT) when loading
5555
- analyzePath: row.analysisDataFilePath
5556
- ? row.analysisDataFilePath.substring(0, row.analysisDataFilePath.length - 4)
5557
- : undefined,
5558
- releaseDate: (_q = row.releaseDate) !== null && _q !== void 0 ? _q : undefined,
5559
- dateAdded: row.dateAdded ? new Date(row.dateAdded) : undefined,
5560
- beatGrid: null,
5561
- cueAndLoops: null,
5562
- waveformHd: null,
5563
- artwork: row.image_id
5564
- ? { id: row.image_id, path: (_r = row.artworkPath) !== null && _r !== void 0 ? _r : undefined }
5565
- : null,
5566
- artist: row.artist_id_artist
5567
- ? { id: row.artist_id_artist, name: (_s = row.artistName) !== null && _s !== void 0 ? _s : '' }
5568
- : null,
5569
- originalArtist: row.artist_id_originalArtist
5570
- ? {
5571
- id: row.artist_id_originalArtist,
5572
- name: (_t = row.originalArtistName) !== null && _t !== void 0 ? _t : '',
5573
- }
5574
- : null,
5575
- remixer: row.artist_id_remixer
5576
- ? { id: row.artist_id_remixer, name: (_u = row.remixerName) !== null && _u !== void 0 ? _u : '' }
5577
- : null,
5578
- composer: row.artist_id_composer
5579
- ? { id: row.artist_id_composer, name: (_v = row.composerName) !== null && _v !== void 0 ? _v : '' }
5580
- : null,
5581
- album: row.album_id
5582
- ? { id: row.album_id, name: (_w = row.albumName) !== null && _w !== void 0 ? _w : '' }
5583
- : null,
5584
- label: row.label_id
5585
- ? { id: row.label_id, name: (_x = row.labelName) !== null && _x !== void 0 ? _x : '' }
5586
- : null,
5587
- genre: row.genre_id
5588
- ? { id: row.genre_id, name: (_y = row.genreName) !== null && _y !== void 0 ? _y : '' }
5589
- : null,
5590
- color: row.color_id
5591
- ? { id: row.color_id, name: (_z = row.colorName) !== null && _z !== void 0 ? _z : '' }
5592
- : null,
5593
- key: row.key_id ? { id: row.key_id, name: (_0 = row.keyName) !== null && _0 !== void 0 ? _0 : '' } : null,
5594
- };
5595
- return track;
5596
- }, _OneLibraryAdapter_cueToCueAndLoop = function _OneLibraryAdapter_cueToCueAndLoop(row) {
5597
- var _a, _b, _c, _d, _e, _f;
5598
- const offset = row.inUsec ? row.inUsec / 1000 : 0; // microseconds to ms
5599
- const outOffset = (_a = row.outUsec) !== null && _a !== void 0 ? _a : null;
5600
- const isLoop = outOffset !== null && outOffset > 0;
5601
- const length = isLoop ? (outOffset - ((_b = row.inUsec) !== null && _b !== void 0 ? _b : 0)) / 1000 : 0;
5602
- // Determine if this is a hot cue (kind value mapping may vary)
5603
- // Based on observation: kind 0 = memory cue, kind >= 1 may be hot cue
5604
- const isHotCue = ((_c = row.kind) !== null && _c !== void 0 ? _c : 0) >= 1 && ((_d = row.kind) !== null && _d !== void 0 ? _d : 0) <= 8;
5605
- const button = isHotCue ? ((_e = row.kind) !== null && _e !== void 0 ? _e : 0) : undefined;
5606
- const label = (_f = row.cueComment) !== null && _f !== void 0 ? _f : undefined;
5607
- const color = row.colorTableIndex;
5608
- if (isLoop && isHotCue) {
5609
- return {
5610
- type: 'hot_loop',
5611
- offset,
5612
- length,
5613
- button: button,
5614
- label,
5615
- color,
5616
- };
5617
- }
5618
- if (isLoop) {
5619
- return {
5620
- type: 'loop',
5621
- offset,
5622
- length,
5623
- label,
5624
- color,
5625
- };
5626
- }
5627
- if (isHotCue) {
5628
- return {
5629
- type: 'hot_cue',
5630
- offset,
5631
- button: button,
5632
- label,
5633
- color,
5634
- };
5635
- }
5636
- return {
5637
- type: 'cue_point',
5638
- offset,
5639
- label,
5640
- color,
5641
- };
5642
- }, _OneLibraryAdapter_playlistRowToPlaylist = function _OneLibraryAdapter_playlistRowToPlaylist(row) {
5643
- var _a;
5644
- return {
5645
- id: row.playlist_id,
5646
- name: (_a = row.name) !== null && _a !== void 0 ? _a : '',
5647
- isFolder: row.attribute === onelibrary_schema_1.PlaylistAttribute.FOLDER,
5648
- parentId: row.playlist_id_parent,
5649
- };
5650
- }, _OneLibraryAdapter_myTagRowToMyTag = function _OneLibraryAdapter_myTagRowToMyTag(row) {
5651
- var _a;
5652
- return {
5653
- id: row.myTag_id,
5654
- name: (_a = row.name) !== null && _a !== void 0 ? _a : '',
5655
- isFolder: row.attribute === onelibrary_schema_1.MyTagAttribute.FOLDER,
5656
- parentId: row.myTag_id_parent,
5657
- };
5658
- };
5659
-
5660
-
5661
- /***/ },
5662
-
5663
- /***/ "./src/localdb/orm.ts"
5664
- /*!****************************!*\
5665
- !*** ./src/localdb/orm.ts ***!
5666
- \****************************/
5667
- (__unused_webpack_module, exports, __webpack_require__) {
5668
-
5669
- "use strict";
5670
-
5671
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
5672
- if (kind === "m") throw new TypeError("Private method is not writable");
5673
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5674
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5675
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
5676
- };
5677
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
5678
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
5679
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5680
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5681
- };
5682
- var __importDefault = (this && this.__importDefault) || function (mod) {
5683
- return (mod && mod.__esModule) ? mod : { "default": mod };
5684
- };
5685
- var _MetadataORM_conn, _MetadataORM_stmtCache, _MetadataORM_inTransaction;
5686
- Object.defineProperty(exports, "__esModule", ({ value: true }));
5687
- exports.MetadataORM = exports.Table = void 0;
5688
- const better_sqlite3_multiple_ciphers_1 = __importDefault(__webpack_require__(/*! better-sqlite3-multiple-ciphers */ "better-sqlite3-multiple-ciphers"));
5689
- const lodash_1 = __webpack_require__(/*! lodash */ "lodash");
5690
- const schema_1 = __webpack_require__(/*! ./schema */ "./src/localdb/schema.ts");
5691
- /**
5692
- * Table names available
5175
+ * Table names available
5693
5176
  */
5694
5177
  var Table;
5695
5178
  (function (Table) {
@@ -5863,215 +5346,245 @@ _MetadataORM_conn = new WeakMap(), _MetadataORM_stmtCache = new WeakMap(), _Meta
5863
5346
 
5864
5347
  "use strict";
5865
5348
 
5866
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5867
- if (k2 === undefined) k2 = k;
5868
- var desc = Object.getOwnPropertyDescriptor(m, k);
5869
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
5870
- desc = { enumerable: true, get: function() { return m[k]; } };
5871
- }
5872
- Object.defineProperty(o, k2, desc);
5873
- }) : (function(o, m, k, k2) {
5874
- if (k2 === undefined) k2 = k;
5875
- o[k2] = m[k];
5876
- }));
5877
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
5878
- Object.defineProperty(o, "default", { enumerable: true, value: v });
5879
- }) : function(o, v) {
5880
- o["default"] = v;
5881
- });
5882
- var __importStar = (this && this.__importStar) || (function () {
5883
- var ownKeys = function(o) {
5884
- ownKeys = Object.getOwnPropertyNames || function (o) {
5885
- var ar = [];
5886
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
5887
- return ar;
5888
- };
5889
- return ownKeys(o);
5890
- };
5891
- return function (mod) {
5892
- if (mod && mod.__esModule) return mod;
5893
- var result = {};
5894
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
5895
- __setModuleDefault(result, mod);
5896
- return result;
5897
- };
5898
- })();
5899
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
5900
- if (kind === "m") throw new TypeError("Private method is not writable");
5901
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5902
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5903
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
5904
- };
5905
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
5906
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
5907
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5908
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5909
- };
5910
- var __importDefault = (this && this.__importDefault) || function (mod) {
5911
- return (mod && mod.__esModule) ? mod : { "default": mod };
5912
- };
5913
- var _RekordboxHydrator_orm, _RekordboxHydrator_onProgress;
5349
+ /**
5350
+ * Rekordbox Database Utilities
5351
+ *
5352
+ * Re-exports from the rekordbox/ folder for backward compatibility.
5353
+ * @see ./rekordbox/index.ts for the implementation
5354
+ */
5914
5355
  Object.defineProperty(exports, "__esModule", ({ value: true }));
5915
- exports.hydrateDatabase = hydrateDatabase;
5916
- exports.loadAnlz = loadAnlz;
5917
- const kaitai_struct_1 = __webpack_require__(/*! kaitai-struct */ "kaitai-struct");
5918
- const rekordbox_anlz_ksy_1 = __importDefault(__webpack_require__(/*! src/localdb/kaitai/rekordbox_anlz.ksy */ "./src/localdb/kaitai/rekordbox_anlz.ksy"));
5919
- const rekordbox_pdb_ksy_1 = __importDefault(__webpack_require__(/*! src/localdb/kaitai/rekordbox_pdb.ksy */ "./src/localdb/kaitai/rekordbox_pdb.ksy"));
5920
- const orm_1 = __webpack_require__(/*! src/localdb/orm */ "./src/localdb/orm.ts");
5356
+ exports.loadAnlz = exports.hydrateDatabase = void 0;
5357
+ var index_1 = __webpack_require__(/*! ./rekordbox/index */ "./src/localdb/rekordbox/index.ts");
5358
+ Object.defineProperty(exports, "hydrateDatabase", ({ enumerable: true, get: function () { return index_1.hydrateDatabase; } }));
5359
+ Object.defineProperty(exports, "loadAnlz", ({ enumerable: true, get: function () { return index_1.loadAnlz; } }));
5360
+
5361
+
5362
+ /***/ },
5363
+
5364
+ /***/ "./src/localdb/rekordbox/anlz-parsers.ts"
5365
+ /*!***********************************************!*\
5366
+ !*** ./src/localdb/rekordbox/anlz-parsers.ts ***!
5367
+ \***********************************************/
5368
+ (__unused_webpack_module, exports, __webpack_require__) {
5369
+
5370
+ "use strict";
5371
+
5372
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
5373
+ exports.makeBeatGrid = makeBeatGrid;
5374
+ exports.makeCueAndLoop = makeCueAndLoop;
5375
+ exports.makeWaveformHd = makeWaveformHd;
5376
+ exports.makeExtendedCues = makeExtendedCues;
5377
+ exports.makeSongStructure = makeSongStructure;
5378
+ exports.makeWaveformPreview = makeWaveformPreview;
5379
+ exports.makeWaveform3BandPreview = makeWaveform3BandPreview;
5380
+ exports.makeWaveform3BandDetail = makeWaveform3BandDetail;
5381
+ exports.makeVocalConfig = makeVocalConfig;
5921
5382
  const utils_1 = __webpack_require__(/*! src/localdb/utils */ "./src/localdb/utils.ts");
5922
5383
  const converters_1 = __webpack_require__(/*! src/utils/converters */ "./src/utils/converters.ts");
5923
- const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
5924
5384
  /**
5925
- * Given a rekordbox pdb file contents. This function will hydrate the provided
5926
- * database with all entities from the Rekordbox database. This includes all
5927
- * track metadata, including analyzed metadata (such as beatgrids and waveforms).
5385
+ * Fill beatgrid data from the ANLZ section
5928
5386
  */
5929
- async function hydrateDatabase({ pdbData, span, ...options }) {
5930
- const hydrator = new RekordboxHydrator(options);
5931
- await hydrator.hydrateFromPdb(pdbData, span);
5387
+ function makeBeatGrid(data) {
5388
+ return data.body.beats.map((beat) => ({
5389
+ offset: beat.time,
5390
+ bpm: beat.tempo / 100,
5391
+ count: beat.beatNumber,
5392
+ }));
5932
5393
  }
5933
5394
  /**
5934
- * Loads the ANLZ data of a Track entity from the analyzePath.
5395
+ * Fill cue and loop data from the ANLZ section
5935
5396
  */
5936
- async function loadAnlz(track, type, anlzResolver) {
5937
- const path = `${track.analyzePath}.${type}`;
5938
- const anlzData = await anlzResolver(path);
5939
- const stream = new kaitai_struct_1.KaitaiStream(anlzData);
5940
- const anlz = new rekordbox_anlz_ksy_1.default(stream);
5941
- const result = {};
5942
- const resultDat = result;
5943
- const resultExt = result;
5944
- for (const section of anlz.sections) {
5945
- switch (section.fourcc) {
5946
- case SectionTags.BEAT_GRID:
5947
- resultDat.beatGrid = makeBeatGrid(section);
5948
- break;
5949
- case SectionTags.CUES:
5950
- resultDat.cueAndLoops = makeCueAndLoop(section);
5951
- break;
5952
- case SectionTags.CUES_2:
5953
- resultExt.extendedCues = makeExtendedCues(section);
5954
- break;
5955
- case SectionTags.WAVE_PREVIEW:
5956
- resultDat.waveformPreview = makeWaveformPreview(section);
5957
- break;
5958
- case SectionTags.WAVE_TINY:
5959
- resultDat.waveformTiny = makeWaveformPreview(section);
5960
- break;
5961
- case SectionTags.WAVE_SCROLL:
5962
- resultExt.waveformDetail = Buffer.from(section.body.entries);
5963
- break;
5964
- case SectionTags.WAVE_COLOR_PREVIEW:
5965
- resultExt.waveformColorPreview = Buffer.from(section.body.entries);
5966
- break;
5967
- case SectionTags.WAVE_COLOR_SCROLL:
5968
- resultExt.waveformHd = makeWaveformHd(section);
5969
- break;
5970
- case SectionTags.SONG_STRUCTURE:
5971
- resultExt.songStructure = makeSongStructure(section);
5972
- break;
5973
- // VBR and PATH tags are defined but not currently extracted
5974
- // as they're not commonly needed in the application
5975
- case SectionTags.VBR:
5976
- case SectionTags.PATH:
5977
- break;
5978
- }
5979
- }
5980
- return result;
5397
+ function makeCueAndLoop(data) {
5398
+ return data.body.cues.map((entry) => {
5399
+ // Cues with the status 0 are likely leftovers that were removed
5400
+ const button = entry.hotCue === 0 ? false : entry.type;
5401
+ const isCue = entry.type === 0x01;
5402
+ const isLoop = entry.type === 0x02;
5403
+ // NOTE: Unlike the remotedb, these entries are already in milliseconds.
5404
+ const offset = entry.time;
5405
+ const length = entry.loopTime - offset;
5406
+ return (0, utils_1.makeCueLoopEntry)(isCue, isLoop, offset, length, button);
5407
+ });
5981
5408
  }
5982
5409
  /**
5983
- * This service provides utilities for translating rekordbox database (pdb_ and
5984
- * analysis (ANLZ) files into the common entity types used in this library.
5410
+ * Fill waveform HD data from the ANLZ section
5985
5411
  */
5986
- class RekordboxHydrator {
5987
- constructor({ orm, onProgress }) {
5988
- _RekordboxHydrator_orm.set(this, void 0);
5989
- _RekordboxHydrator_onProgress.set(this, void 0);
5990
- __classPrivateFieldSet(this, _RekordboxHydrator_orm, orm, "f");
5991
- __classPrivateFieldSet(this, _RekordboxHydrator_onProgress, onProgress !== null && onProgress !== void 0 ? onProgress : (() => null), "f");
5992
- }
5993
- /**
5994
- * Extract entries from a rekordbox pdb file and hydrate the passed database
5995
- * connection with entities derived from the rekordbox entries.
5996
- */
5997
- async hydrateFromPdb(pdbData, span) {
5998
- const tx = span
5999
- ? span.startChild({ op: 'hydrateFromPdb' })
6000
- : Telemetry.startTransaction({ name: 'hydrateFromPdb' });
6001
- const parseTx = tx.startChild({ op: 'parsePdbData', data: { size: pdbData.length } });
6002
- const stream = new kaitai_struct_1.KaitaiStream(pdbData);
6003
- const db = new rekordbox_pdb_ksy_1.default(stream);
6004
- parseTx.finish();
6005
- const hydrateTx = tx.startChild({ op: 'hydration' });
6006
- await Promise.all(db.tables.map((table) => this.hydrateFromTable(table, hydrateTx)));
6007
- hydrateTx.finish();
6008
- tx.finish();
6009
- }
6010
- /**
6011
- * Hydrate the database with entities from the provided RekordboxPdb table.
6012
- * See pdbEntityCreators for how tables are mapped into database entities.
6013
- */
6014
- async hydrateFromTable(table, span) {
6015
- const tableName = pdbTables[table.type];
6016
- const createObject = pdbEntityCreators[table.type];
6017
- const tx = span.startChild({ op: 'hydrateFromTable', description: tableName });
6018
- if (createObject === undefined) {
6019
- return;
5412
+ function makeWaveformHd(data) {
5413
+ return (0, converters_1.convertWaveformHDData)(Buffer.from(data.body.entries));
5414
+ }
5415
+ /**
5416
+ * Parse extended cues (PCO2) with colors and comments
5417
+ */
5418
+ function makeExtendedCues(data) {
5419
+ return data.body.cues.map((entry) => {
5420
+ var _a, _b, _c, _d;
5421
+ const cue = {
5422
+ hotCue: entry.hotCue,
5423
+ type: entry.type,
5424
+ time: entry.time,
5425
+ };
5426
+ // Add loop end time if this is a loop
5427
+ if (entry.type === 2 && entry.loopTime !== undefined) {
5428
+ cue.loopTime = entry.loopTime;
6020
5429
  }
6021
- let totalSaved = 0;
6022
- let totalItems = 0;
6023
- for (const _row of tableRows(table)) {
6024
- void _row; // Intentionally unused - just counting
6025
- totalItems++;
5430
+ // Add color ID for memory points/loops
5431
+ if (entry.colorId !== undefined && entry.colorId > 0) {
5432
+ cue.colorId = entry.colorId;
6026
5433
  }
6027
- tx.setData('items', totalItems);
6028
- // Use transaction for bulk inserts (10-100x faster)
6029
- __classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").beginTransaction();
6030
- try {
6031
- for (const row of tableRows(table)) {
6032
- const entity = createObject(row);
6033
- __classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").insertEntity(tableName, entity);
6034
- totalSaved++;
6035
- // Report progress and yield every 100 rows (instead of every row)
6036
- if (totalSaved % 100 === 0 || totalSaved === totalItems) {
6037
- __classPrivateFieldGet(this, _RekordboxHydrator_onProgress, "f").call(this, { complete: totalSaved, table: tableName, total: totalItems });
6038
- // Yield to event loop periodically to keep UI responsive
6039
- await new Promise(r => setTimeout(r, 0));
6040
- }
6041
- }
5434
+ // Add hot cue color information
5435
+ if (entry.colorCode !== undefined && entry.colorCode > 0) {
5436
+ cue.colorCode = entry.colorCode;
5437
+ cue.colorRgb = {
5438
+ r: (_a = entry.colorRed) !== null && _a !== void 0 ? _a : 0,
5439
+ g: (_b = entry.colorGreen) !== null && _b !== void 0 ? _b : 0,
5440
+ b: (_c = entry.colorBlue) !== null && _c !== void 0 ? _c : 0,
5441
+ };
6042
5442
  }
6043
- finally {
6044
- __classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").commit();
5443
+ // Add comment if present
5444
+ if (entry.lenComment > 0 && entry.comment) {
5445
+ cue.comment = entry.comment;
6045
5446
  }
6046
- tx.finish();
6047
- }
5447
+ // Add quantized loop information if present
5448
+ if (entry.loopNumerator !== undefined && entry.loopNumerator > 0) {
5449
+ cue.loopNumerator = entry.loopNumerator;
5450
+ cue.loopDenominator = (_d = entry.loopDenominator) !== null && _d !== void 0 ? _d : 1;
5451
+ }
5452
+ return cue;
5453
+ });
6048
5454
  }
6049
- _RekordboxHydrator_orm = new WeakMap(), _RekordboxHydrator_onProgress = new WeakMap();
6050
5455
  /**
6051
- * Utility generator that pages through a table and yields every present row.
6052
- * This flattens the concept of rowGroups and refs.
5456
+ * Parse song structure (PSSI) with phrase analysis
6053
5457
  */
6054
- function* tableRows(table) {
6055
- const { firstPage, lastPage } = table;
6056
- let pageRef = firstPage;
6057
- do {
6058
- const page = pageRef.body;
6059
- // Adjust our page ref for the next iteration. We do this early in our loop
6060
- // so we can break without having to remember to update for the next iter.
6061
- pageRef = page.nextPage;
6062
- // Ignore non-data pages. Not sure what these are for?
6063
- if (!page.isDataPage) {
6064
- continue;
6065
- }
6066
- const rows = page.rowGroups
6067
- .map((group) => group.rows)
6068
- .flat()
6069
- .filter((row) => row.present);
6070
- for (const row of rows) {
6071
- yield row.body;
5458
+ function makeSongStructure(data) {
5459
+ var _a, _b;
5460
+ const moodMap = {
5461
+ 1: 'high',
5462
+ 2: 'mid',
5463
+ 3: 'low',
5464
+ };
5465
+ const bankMap = {
5466
+ 0: 'default',
5467
+ 1: 'cool',
5468
+ 2: 'natural',
5469
+ 3: 'hot',
5470
+ 4: 'subtle',
5471
+ 5: 'warm',
5472
+ 6: 'vivid',
5473
+ 7: 'club_1',
5474
+ 8: 'club_2',
5475
+ };
5476
+ // Phrase type mappings based on mood
5477
+ const phraseTypeMap = {
5478
+ high: {
5479
+ 1: 'Intro',
5480
+ 2: 'Up',
5481
+ 3: 'Down',
5482
+ 5: 'Chorus',
5483
+ 6: 'Outro',
5484
+ },
5485
+ mid: {
5486
+ 1: 'Intro',
5487
+ 2: 'Verse 1',
5488
+ 3: 'Verse 2',
5489
+ 4: 'Verse 3',
5490
+ 5: 'Verse 4',
5491
+ 6: 'Verse 5',
5492
+ 7: 'Verse 6',
5493
+ 8: 'Bridge',
5494
+ 9: 'Chorus',
5495
+ 10: 'Outro',
5496
+ },
5497
+ low: {
5498
+ 1: 'Intro',
5499
+ 2: 'Verse 1',
5500
+ 3: 'Verse 1',
5501
+ 4: 'Verse 1',
5502
+ 5: 'Verse 2',
5503
+ 6: 'Verse 2',
5504
+ 7: 'Verse 2',
5505
+ 8: 'Bridge',
5506
+ 9: 'Chorus',
5507
+ 10: 'Outro',
5508
+ },
5509
+ };
5510
+ const mood = (_a = moodMap[data.body.mood]) !== null && _a !== void 0 ? _a : 'high';
5511
+ const bank = (_b = bankMap[data.body.rawBank]) !== null && _b !== void 0 ? _b : 'default';
5512
+ const phrases = data.body.entries.map((entry) => {
5513
+ var _a;
5514
+ const phrase = {
5515
+ index: entry.index,
5516
+ beat: entry.beat,
5517
+ kind: entry.kind,
5518
+ phraseType: (_a = phraseTypeMap[mood][entry.kind]) !== null && _a !== void 0 ? _a : 'Unknown',
5519
+ };
5520
+ // Add fill-in information if present
5521
+ if (entry.fill > 0) {
5522
+ phrase.fill = entry.fill;
5523
+ phrase.fillBeat = entry.beatFill;
6072
5524
  }
6073
- } while (pageRef.index <= lastPage.index);
5525
+ return phrase;
5526
+ });
5527
+ return {
5528
+ mood,
5529
+ bank,
5530
+ endBeat: data.body.endBeat,
5531
+ phrases,
5532
+ };
6074
5533
  }
5534
+ /**
5535
+ * Parse waveform preview data (PWAV/PWV2)
5536
+ */
5537
+ function makeWaveformPreview(data) {
5538
+ return {
5539
+ data: Buffer.from(data.body.data),
5540
+ };
5541
+ }
5542
+ /**
5543
+ * Parse 3-band color waveform preview (PWV6 from .2EX files)
5544
+ */
5545
+ function makeWaveform3BandPreview(data) {
5546
+ return {
5547
+ numEntries: data.body.lenEntries,
5548
+ data: Buffer.from(data.body.entries),
5549
+ };
5550
+ }
5551
+ /**
5552
+ * Parse 3-band color detail waveform (PWV7 from .2EX files)
5553
+ */
5554
+ function makeWaveform3BandDetail(data) {
5555
+ return {
5556
+ numEntries: data.body.lenEntries,
5557
+ data: Buffer.from(data.body.entries),
5558
+ };
5559
+ }
5560
+ /**
5561
+ * Parse vocal detection config (PWVC from .2EX files)
5562
+ */
5563
+ function makeVocalConfig(data) {
5564
+ return {
5565
+ thresholdLow: data.body.thresholdLow,
5566
+ thresholdMid: data.body.thresholdMid,
5567
+ thresholdHigh: data.body.thresholdHigh,
5568
+ };
5569
+ }
5570
+
5571
+
5572
+ /***/ },
5573
+
5574
+ /***/ "./src/localdb/rekordbox/entity-creators.ts"
5575
+ /*!**************************************************!*\
5576
+ !*** ./src/localdb/rekordbox/entity-creators.ts ***!
5577
+ \**************************************************/
5578
+ (__unused_webpack_module, exports) {
5579
+
5580
+ "use strict";
5581
+
5582
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
5583
+ exports.makeIdNameHydrator = void 0;
5584
+ exports.createTrack = createTrack;
5585
+ exports.createPlaylist = createPlaylist;
5586
+ exports.createPlaylistEntry = createPlaylistEntry;
5587
+ exports.createArtworkEntry = createArtworkEntry;
6075
5588
  const ensureDate = (date) => date instanceof Date && !isNaN(date.valueOf()) ? date : undefined;
6076
5589
  /**
6077
5590
  * Utility to create a hydrator that hydrates the provided entity with the id
@@ -6084,6 +5597,7 @@ const makeIdNameHydrator = () => (row) => {
6084
5597
  name: (_a = row.name.body.text) !== null && _a !== void 0 ? _a : '',
6085
5598
  });
6086
5599
  };
5600
+ exports.makeIdNameHydrator = makeIdNameHydrator;
6087
5601
  /**
6088
5602
  * Translates a pdb track row entry to a {@link Track} entity.
6089
5603
  */
@@ -6176,170 +5690,282 @@ function createArtworkEntry(artworkRow) {
6176
5690
  };
6177
5691
  return art;
6178
5692
  }
5693
+
5694
+
5695
+ /***/ },
5696
+
5697
+ /***/ "./src/localdb/rekordbox/hydrator.ts"
5698
+ /*!*******************************************!*\
5699
+ !*** ./src/localdb/rekordbox/hydrator.ts ***!
5700
+ \*******************************************/
5701
+ (__unused_webpack_module, exports, __webpack_require__) {
5702
+
5703
+ "use strict";
5704
+
5705
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5706
+ if (k2 === undefined) k2 = k;
5707
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5708
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
5709
+ desc = { enumerable: true, get: function() { return m[k]; } };
5710
+ }
5711
+ Object.defineProperty(o, k2, desc);
5712
+ }) : (function(o, m, k, k2) {
5713
+ if (k2 === undefined) k2 = k;
5714
+ o[k2] = m[k];
5715
+ }));
5716
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
5717
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
5718
+ }) : function(o, v) {
5719
+ o["default"] = v;
5720
+ });
5721
+ var __importStar = (this && this.__importStar) || (function () {
5722
+ var ownKeys = function(o) {
5723
+ ownKeys = Object.getOwnPropertyNames || function (o) {
5724
+ var ar = [];
5725
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
5726
+ return ar;
5727
+ };
5728
+ return ownKeys(o);
5729
+ };
5730
+ return function (mod) {
5731
+ if (mod && mod.__esModule) return mod;
5732
+ var result = {};
5733
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
5734
+ __setModuleDefault(result, mod);
5735
+ return result;
5736
+ };
5737
+ })();
5738
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
5739
+ if (kind === "m") throw new TypeError("Private method is not writable");
5740
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5741
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5742
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
5743
+ };
5744
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
5745
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
5746
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5747
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5748
+ };
5749
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5750
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5751
+ };
5752
+ var _RekordboxHydrator_orm, _RekordboxHydrator_onProgress;
5753
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
5754
+ exports.RekordboxHydrator = void 0;
5755
+ const kaitai_struct_1 = __webpack_require__(/*! kaitai-struct */ "kaitai-struct");
5756
+ const rekordbox_pdb_ksy_1 = __importDefault(__webpack_require__(/*! src/localdb/kaitai/rekordbox_pdb.ksy */ "./src/localdb/kaitai/rekordbox_pdb.ksy"));
5757
+ const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
5758
+ const table_mappings_1 = __webpack_require__(/*! ./table-mappings */ "./src/localdb/rekordbox/table-mappings.ts");
6179
5759
  /**
6180
- * Fill beatgrid data from the ANLZ section
6181
- */
6182
- function makeBeatGrid(data) {
6183
- return data.body.beats.map((beat) => ({
6184
- offset: beat.time,
6185
- bpm: beat.tempo / 100,
6186
- count: beat.beatNumber,
6187
- }));
6188
- }
6189
- /**
6190
- * Fill cue and loop data from the ANLZ section
6191
- */
6192
- function makeCueAndLoop(data) {
6193
- return data.body.cues.map((entry) => {
6194
- // Cues with the status 0 are likely leftovers that were removed
6195
- const button = entry.hotCue === 0 ? false : entry.type;
6196
- const isCue = entry.type === 0x01;
6197
- const isLoop = entry.type === 0x02;
6198
- // NOTE: Unlike the remotedb, these entries are already in milliseconds.
6199
- const offset = entry.time;
6200
- const length = entry.loopTime - offset;
6201
- return (0, utils_1.makeCueLoopEntry)(isCue, isLoop, offset, length, button);
6202
- });
6203
- }
6204
- /**
6205
- * Fill waveform HD data from the ANLZ section
6206
- */
6207
- function makeWaveformHd(data) {
6208
- return (0, converters_1.convertWaveformHDData)(Buffer.from(data.body.entries));
6209
- }
6210
- /**
6211
- * Parse extended cues (PCO2) with colors and comments
5760
+ * This service provides utilities for translating rekordbox database (pdb_ and
5761
+ * analysis (ANLZ) files into the common entity types used in this library.
6212
5762
  */
6213
- function makeExtendedCues(data) {
6214
- return data.body.cues.map((entry) => {
6215
- var _a, _b, _c, _d;
6216
- const cue = {
6217
- hotCue: entry.hotCue,
6218
- type: entry.type,
6219
- time: entry.time,
6220
- };
6221
- // Add loop end time if this is a loop
6222
- if (entry.type === 2 && entry.loopTime !== undefined) {
6223
- cue.loopTime = entry.loopTime;
5763
+ class RekordboxHydrator {
5764
+ constructor({ orm, onProgress }) {
5765
+ _RekordboxHydrator_orm.set(this, void 0);
5766
+ _RekordboxHydrator_onProgress.set(this, void 0);
5767
+ __classPrivateFieldSet(this, _RekordboxHydrator_orm, orm, "f");
5768
+ __classPrivateFieldSet(this, _RekordboxHydrator_onProgress, onProgress !== null && onProgress !== void 0 ? onProgress : (() => null), "f");
5769
+ }
5770
+ /**
5771
+ * Extract entries from a rekordbox pdb file and hydrate the passed database
5772
+ * connection with entities derived from the rekordbox entries.
5773
+ */
5774
+ async hydrateFromPdb(pdbData, span) {
5775
+ const tx = span
5776
+ ? span.startChild({ op: 'hydrateFromPdb' })
5777
+ : Telemetry.startTransaction({ name: 'hydrateFromPdb' });
5778
+ const parseTx = tx.startChild({ op: 'parsePdbData', data: { size: pdbData.length } });
5779
+ const stream = new kaitai_struct_1.KaitaiStream(pdbData);
5780
+ const db = new rekordbox_pdb_ksy_1.default(stream);
5781
+ parseTx.finish();
5782
+ const hydrateTx = tx.startChild({ op: 'hydration' });
5783
+ await Promise.all(db.tables.map((table) => this.hydrateFromTable(table, hydrateTx)));
5784
+ hydrateTx.finish();
5785
+ tx.finish();
5786
+ }
5787
+ /**
5788
+ * Hydrate the database with entities from the provided RekordboxPdb table.
5789
+ * See pdbEntityCreators for how tables are mapped into database entities.
5790
+ */
5791
+ async hydrateFromTable(table, span) {
5792
+ const tableName = table_mappings_1.pdbTables[table.type];
5793
+ const createObject = table_mappings_1.pdbEntityCreators[table.type];
5794
+ const tx = span.startChild({ op: 'hydrateFromTable', description: tableName });
5795
+ if (createObject === undefined) {
5796
+ return;
6224
5797
  }
6225
- // Add color ID for memory points/loops
6226
- if (entry.colorId !== undefined && entry.colorId > 0) {
6227
- cue.colorId = entry.colorId;
5798
+ let totalSaved = 0;
5799
+ let totalItems = 0;
5800
+ for (const _row of tableRows(table)) {
5801
+ void _row; // Intentionally unused - just counting
5802
+ totalItems++;
6228
5803
  }
6229
- // Add hot cue color information
6230
- if (entry.colorCode !== undefined && entry.colorCode > 0) {
6231
- cue.colorCode = entry.colorCode;
6232
- cue.colorRgb = {
6233
- r: (_a = entry.colorRed) !== null && _a !== void 0 ? _a : 0,
6234
- g: (_b = entry.colorGreen) !== null && _b !== void 0 ? _b : 0,
6235
- b: (_c = entry.colorBlue) !== null && _c !== void 0 ? _c : 0,
6236
- };
5804
+ tx.setData('items', totalItems);
5805
+ // Use transaction for bulk inserts (10-100x faster)
5806
+ __classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").beginTransaction();
5807
+ try {
5808
+ for (const row of tableRows(table)) {
5809
+ const entity = createObject(row);
5810
+ __classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").insertEntity(tableName, entity);
5811
+ totalSaved++;
5812
+ // Report progress and yield every 100 rows (instead of every row)
5813
+ if (totalSaved % 100 === 0 || totalSaved === totalItems) {
5814
+ __classPrivateFieldGet(this, _RekordboxHydrator_onProgress, "f").call(this, { complete: totalSaved, table: tableName, total: totalItems });
5815
+ // Yield to event loop periodically to keep UI responsive
5816
+ await new Promise(r => setTimeout(r, 0));
5817
+ }
5818
+ }
6237
5819
  }
6238
- // Add comment if present
6239
- if (entry.lenComment > 0 && entry.comment) {
6240
- cue.comment = entry.comment;
5820
+ finally {
5821
+ __classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").commit();
6241
5822
  }
6242
- // Add quantized loop information if present
6243
- if (entry.loopNumerator !== undefined && entry.loopNumerator > 0) {
6244
- cue.loopNumerator = entry.loopNumerator;
6245
- cue.loopDenominator = (_d = entry.loopDenominator) !== null && _d !== void 0 ? _d : 1;
5823
+ tx.finish();
5824
+ }
5825
+ }
5826
+ exports.RekordboxHydrator = RekordboxHydrator;
5827
+ _RekordboxHydrator_orm = new WeakMap(), _RekordboxHydrator_onProgress = new WeakMap();
5828
+ /**
5829
+ * Utility generator that pages through a table and yields every present row.
5830
+ * This flattens the concept of rowGroups and refs.
5831
+ */
5832
+ function* tableRows(table) {
5833
+ const { firstPage, lastPage } = table;
5834
+ let pageRef = firstPage;
5835
+ do {
5836
+ const page = pageRef.body;
5837
+ // Adjust our page ref for the next iteration. We do this early in our loop
5838
+ // so we can break without having to remember to update for the next iter.
5839
+ pageRef = page.nextPage;
5840
+ // Ignore non-data pages. Not sure what these are for?
5841
+ if (!page.isDataPage) {
5842
+ continue;
6246
5843
  }
6247
- return cue;
6248
- });
5844
+ const rows = page.rowGroups
5845
+ .map((group) => group.rows)
5846
+ .flat()
5847
+ .filter((row) => row.present);
5848
+ for (const row of rows) {
5849
+ yield row.body;
5850
+ }
5851
+ } while (pageRef.index <= lastPage.index);
6249
5852
  }
5853
+
5854
+
5855
+ /***/ },
5856
+
5857
+ /***/ "./src/localdb/rekordbox/index.ts"
5858
+ /*!****************************************!*\
5859
+ !*** ./src/localdb/rekordbox/index.ts ***!
5860
+ \****************************************/
5861
+ (__unused_webpack_module, exports, __webpack_require__) {
5862
+
5863
+ "use strict";
5864
+
5865
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5866
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5867
+ };
5868
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
5869
+ exports.hydrateDatabase = hydrateDatabase;
5870
+ exports.loadAnlz = loadAnlz;
5871
+ const kaitai_struct_1 = __webpack_require__(/*! kaitai-struct */ "kaitai-struct");
5872
+ const rekordbox_anlz_ksy_1 = __importDefault(__webpack_require__(/*! src/localdb/kaitai/rekordbox_anlz.ksy */ "./src/localdb/kaitai/rekordbox_anlz.ksy"));
5873
+ const anlz_parsers_1 = __webpack_require__(/*! ./anlz-parsers */ "./src/localdb/rekordbox/anlz-parsers.ts");
5874
+ const hydrator_1 = __webpack_require__(/*! ./hydrator */ "./src/localdb/rekordbox/hydrator.ts");
5875
+ const { SectionTags } = rekordbox_anlz_ksy_1.default;
6250
5876
  /**
6251
- * Parse song structure (PSSI) with phrase analysis
5877
+ * Given a rekordbox pdb file contents. This function will hydrate the provided
5878
+ * database with all entities from the Rekordbox database. This includes all
5879
+ * track metadata, including analyzed metadata (such as beatgrids and waveforms).
6252
5880
  */
6253
- function makeSongStructure(data) {
6254
- var _a, _b;
6255
- const moodMap = {
6256
- 1: 'high',
6257
- 2: 'mid',
6258
- 3: 'low',
6259
- };
6260
- const bankMap = {
6261
- 0: 'default',
6262
- 1: 'cool',
6263
- 2: 'natural',
6264
- 3: 'hot',
6265
- 4: 'subtle',
6266
- 5: 'warm',
6267
- 6: 'vivid',
6268
- 7: 'club_1',
6269
- 8: 'club_2',
6270
- };
6271
- // Phrase type mappings based on mood
6272
- const phraseTypeMap = {
6273
- high: {
6274
- 1: 'Intro',
6275
- 2: 'Up',
6276
- 3: 'Down',
6277
- 5: 'Chorus',
6278
- 6: 'Outro',
6279
- },
6280
- mid: {
6281
- 1: 'Intro',
6282
- 2: 'Verse 1',
6283
- 3: 'Verse 2',
6284
- 4: 'Verse 3',
6285
- 5: 'Verse 4',
6286
- 6: 'Verse 5',
6287
- 7: 'Verse 6',
6288
- 8: 'Bridge',
6289
- 9: 'Chorus',
6290
- 10: 'Outro',
6291
- },
6292
- low: {
6293
- 1: 'Intro',
6294
- 2: 'Verse 1',
6295
- 3: 'Verse 1',
6296
- 4: 'Verse 1',
6297
- 5: 'Verse 2',
6298
- 6: 'Verse 2',
6299
- 7: 'Verse 2',
6300
- 8: 'Bridge',
6301
- 9: 'Chorus',
6302
- 10: 'Outro',
6303
- },
6304
- };
6305
- const mood = (_a = moodMap[data.body.mood]) !== null && _a !== void 0 ? _a : 'high';
6306
- const bank = (_b = bankMap[data.body.rawBank]) !== null && _b !== void 0 ? _b : 'default';
6307
- const phrases = data.body.entries.map((entry) => {
6308
- var _a;
6309
- const phrase = {
6310
- index: entry.index,
6311
- beat: entry.beat,
6312
- kind: entry.kind,
6313
- phraseType: (_a = phraseTypeMap[mood][entry.kind]) !== null && _a !== void 0 ? _a : 'Unknown',
6314
- };
6315
- // Add fill-in information if present
6316
- if (entry.fill > 0) {
6317
- phrase.fill = entry.fill;
6318
- phrase.fillBeat = entry.beatFill;
6319
- }
6320
- return phrase;
6321
- });
6322
- return {
6323
- mood,
6324
- bank,
6325
- endBeat: data.body.endBeat,
6326
- phrases,
6327
- };
5881
+ async function hydrateDatabase({ pdbData, span, ...options }) {
5882
+ const hydrator = new hydrator_1.RekordboxHydrator(options);
5883
+ await hydrator.hydrateFromPdb(pdbData, span);
6328
5884
  }
6329
5885
  /**
6330
- * Parse waveform preview data (PWAV/PWV2)
5886
+ * Loads the ANLZ data of a Track entity from the analyzePath.
6331
5887
  */
6332
- function makeWaveformPreview(data) {
6333
- return {
6334
- data: Buffer.from(data.body.data),
6335
- };
5888
+ async function loadAnlz(track, type, anlzResolver) {
5889
+ const path = `${track.analyzePath}.${type}`;
5890
+ const anlzData = await anlzResolver(path);
5891
+ const stream = new kaitai_struct_1.KaitaiStream(anlzData);
5892
+ const anlz = new rekordbox_anlz_ksy_1.default(stream);
5893
+ const result = {};
5894
+ const resultDat = result;
5895
+ const resultExt = result;
5896
+ const result2ex = result;
5897
+ for (const section of anlz.sections) {
5898
+ switch (section.fourcc) {
5899
+ case SectionTags.BEAT_GRID:
5900
+ resultDat.beatGrid = (0, anlz_parsers_1.makeBeatGrid)(section);
5901
+ break;
5902
+ case SectionTags.CUES:
5903
+ resultDat.cueAndLoops = (0, anlz_parsers_1.makeCueAndLoop)(section);
5904
+ break;
5905
+ case SectionTags.CUES_2:
5906
+ resultExt.extendedCues = (0, anlz_parsers_1.makeExtendedCues)(section);
5907
+ break;
5908
+ case SectionTags.WAVE_PREVIEW:
5909
+ resultDat.waveformPreview = (0, anlz_parsers_1.makeWaveformPreview)(section);
5910
+ break;
5911
+ case SectionTags.WAVE_TINY:
5912
+ resultDat.waveformTiny = (0, anlz_parsers_1.makeWaveformPreview)(section);
5913
+ break;
5914
+ case SectionTags.WAVE_SCROLL:
5915
+ resultExt.waveformDetail = Buffer.from(section.body.entries);
5916
+ break;
5917
+ case SectionTags.WAVE_COLOR_PREVIEW:
5918
+ resultExt.waveformColorPreview = Buffer.from(section.body.entries);
5919
+ break;
5920
+ case SectionTags.WAVE_COLOR_SCROLL:
5921
+ resultExt.waveformHd = (0, anlz_parsers_1.makeWaveformHd)(section);
5922
+ break;
5923
+ case SectionTags.SONG_STRUCTURE:
5924
+ resultExt.songStructure = (0, anlz_parsers_1.makeSongStructure)(section);
5925
+ break;
5926
+ case SectionTags.WAVE_COLOR_3BAND_PREVIEW:
5927
+ result2ex.waveform3BandPreview = (0, anlz_parsers_1.makeWaveform3BandPreview)(section);
5928
+ break;
5929
+ case SectionTags.WAVE_COLOR_3BAND_DETAIL:
5930
+ result2ex.waveform3BandDetail = (0, anlz_parsers_1.makeWaveform3BandDetail)(section);
5931
+ break;
5932
+ case SectionTags.VOCAL_CONFIG:
5933
+ result2ex.vocalConfig = (0, anlz_parsers_1.makeVocalConfig)(section);
5934
+ break;
5935
+ // VBR and PATH tags are defined but not currently extracted
5936
+ // as they're not commonly needed in the application
5937
+ case SectionTags.VBR:
5938
+ case SectionTags.PATH:
5939
+ break;
5940
+ }
5941
+ }
5942
+ return result;
6336
5943
  }
5944
+
5945
+
5946
+ /***/ },
5947
+
5948
+ /***/ "./src/localdb/rekordbox/table-mappings.ts"
5949
+ /*!*************************************************!*\
5950
+ !*** ./src/localdb/rekordbox/table-mappings.ts ***!
5951
+ \*************************************************/
5952
+ (__unused_webpack_module, exports, __webpack_require__) {
5953
+
5954
+ "use strict";
5955
+
5956
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5957
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5958
+ };
5959
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
5960
+ exports.pdbEntityCreators = exports.pdbTables = void 0;
5961
+ const rekordbox_pdb_ksy_1 = __importDefault(__webpack_require__(/*! src/localdb/kaitai/rekordbox_pdb.ksy */ "./src/localdb/kaitai/rekordbox_pdb.ksy"));
5962
+ const orm_1 = __webpack_require__(/*! src/localdb/orm */ "./src/localdb/orm.ts");
5963
+ const entity_creators_1 = __webpack_require__(/*! ./entity-creators */ "./src/localdb/rekordbox/entity-creators.ts");
6337
5964
  const { PageType } = rekordbox_pdb_ksy_1.default;
6338
- const { SectionTags } = rekordbox_anlz_ksy_1.default;
6339
5965
  /**
6340
5966
  * Maps rekordbox pdb table types to orm table names.
6341
5967
  */
6342
- const pdbTables = {
5968
+ exports.pdbTables = {
6343
5969
  [PageType.TRACKS]: orm_1.Table.Track,
6344
5970
  [PageType.ARTISTS]: orm_1.Table.Artist,
6345
5971
  [PageType.GENRES]: orm_1.Table.Genre,
@@ -6355,17 +5981,17 @@ const pdbTables = {
6355
5981
  * Maps rekordbox pdb table types to functions that create entity objects for
6356
5982
  * the passed pdb row.
6357
5983
  */
6358
- const pdbEntityCreators = {
6359
- [PageType.TRACKS]: createTrack,
6360
- [PageType.ARTISTS]: makeIdNameHydrator(),
6361
- [PageType.GENRES]: makeIdNameHydrator(),
6362
- [PageType.ALBUMS]: makeIdNameHydrator(),
6363
- [PageType.LABELS]: makeIdNameHydrator(),
6364
- [PageType.COLORS]: makeIdNameHydrator(),
6365
- [PageType.KEYS]: makeIdNameHydrator(),
6366
- [PageType.ARTWORK]: createArtworkEntry,
6367
- [PageType.PLAYLIST_TREE]: createPlaylist,
6368
- [PageType.PLAYLIST_ENTRIES]: createPlaylistEntry,
5984
+ exports.pdbEntityCreators = {
5985
+ [PageType.TRACKS]: entity_creators_1.createTrack,
5986
+ [PageType.ARTISTS]: (0, entity_creators_1.makeIdNameHydrator)(),
5987
+ [PageType.GENRES]: (0, entity_creators_1.makeIdNameHydrator)(),
5988
+ [PageType.ALBUMS]: (0, entity_creators_1.makeIdNameHydrator)(),
5989
+ [PageType.LABELS]: (0, entity_creators_1.makeIdNameHydrator)(),
5990
+ [PageType.COLORS]: (0, entity_creators_1.makeIdNameHydrator)(),
5991
+ [PageType.KEYS]: (0, entity_creators_1.makeIdNameHydrator)(),
5992
+ [PageType.ARTWORK]: entity_creators_1.createArtworkEntry,
5993
+ [PageType.PLAYLIST_TREE]: entity_creators_1.createPlaylist,
5994
+ [PageType.PLAYLIST_ENTRIES]: entity_creators_1.createPlaylistEntry,
6369
5995
  // TODO: Register PageType.HISTORY
6370
5996
  };
6371
5997
 
@@ -6506,6 +6132,27 @@ const makeCueLoopEntry = (isCue, isLoop, offset, length, button) => button !== f
6506
6132
  exports.makeCueLoopEntry = makeCueLoopEntry;
6507
6133
 
6508
6134
 
6135
+ /***/ },
6136
+
6137
+ /***/ "./src/logger.ts"
6138
+ /*!***********************!*\
6139
+ !*** ./src/logger.ts ***!
6140
+ \***********************/
6141
+ (__unused_webpack_module, exports) {
6142
+
6143
+ "use strict";
6144
+
6145
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
6146
+ exports.noopLogger = void 0;
6147
+ exports.noopLogger = {
6148
+ trace() { },
6149
+ debug() { },
6150
+ info() { },
6151
+ warn() { },
6152
+ error() { },
6153
+ };
6154
+
6155
+
6509
6156
  /***/ },
6510
6157
 
6511
6158
  /***/ "./src/mixstatus/index.ts"
@@ -6527,7 +6174,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
6527
6174
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
6528
6175
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6529
6176
  };
6530
- var _MixstatusProcessor_emitter, _MixstatusProcessor_lastState, _MixstatusProcessor_lastStartTime, _MixstatusProcessor_lastStoppedTimes, _MixstatusProcessor_livePlayers, _MixstatusProcessor_isSetActive, _MixstatusProcessor_cancelSetEnding, _MixstatusProcessor_config, _MixstatusProcessor_onAir, _MixstatusProcessor_promotePlayer, _MixstatusProcessor_promoteNextPlayer, _MixstatusProcessor_markPlayerStopped, _MixstatusProcessor_setMayStop, _MixstatusProcessor_playerMayBeFirst, _MixstatusProcessor_playerMayStop, _MixstatusProcessor_handlePlaystateChange, _MixstatusProcessor_handleOnairChange;
6177
+ var _MixstatusProcessor_emitter, _MixstatusProcessor_lastState, _MixstatusProcessor_lastStartTime, _MixstatusProcessor_lastStoppedTimes, _MixstatusProcessor_livePlayers, _MixstatusProcessor_lastEmittedTrackId, _MixstatusProcessor_isSetActive, _MixstatusProcessor_cancelSetEnding, _MixstatusProcessor_config, _MixstatusProcessor_onAir, _MixstatusProcessor_promotePlayer, _MixstatusProcessor_promoteNextPlayer, _MixstatusProcessor_markPlayerStopped, _MixstatusProcessor_setMayStop, _MixstatusProcessor_playerMayBeFirst, _MixstatusProcessor_playerMayStop, _MixstatusProcessor_handlePlaystateChange, _MixstatusProcessor_handleOnairChange;
6531
6178
  Object.defineProperty(exports, "__esModule", ({ value: true }));
6532
6179
  exports.MixstatusProcessor = void 0;
6533
6180
  const events_1 = __webpack_require__(/*! events */ "events");
@@ -6583,6 +6230,14 @@ class MixstatusProcessor {
6583
6230
  * Records which players have been reported as 'live'
6584
6231
  */
6585
6232
  _MixstatusProcessor_livePlayers.set(this, new Set());
6233
+ /**
6234
+ * Records the last trackId emitted as 'nowPlaying' for each device. Used to
6235
+ * suppress duplicate emissions when a deck is briefly cued/loaded and then
6236
+ * resumed on the same track (a common pattern for House/Techno DJs who
6237
+ * cue-juggle mid-track). The entry is replaced when a different trackId is
6238
+ * emitted on the same deck, so subsequent track loads still emit normally.
6239
+ */
6240
+ _MixstatusProcessor_lastEmittedTrackId.set(this, new Map());
6586
6241
  /**
6587
6242
  * Incidates if we're currentiny in an active DJ set
6588
6243
  */
@@ -6617,6 +6272,14 @@ class MixstatusProcessor {
6617
6272
  if (__classPrivateFieldGet(this, _MixstatusProcessor_livePlayers, "f").has(deviceId)) {
6618
6273
  return;
6619
6274
  }
6275
+ // Suppress duplicate nowPlaying for the same track on the same deck. This
6276
+ // can happen when a DJ cues mid-track and resumes (Cued/Loading/Ended are
6277
+ // all stoppingStates that drop the deck from livePlayers, so the next play
6278
+ // of the same trackId would otherwise re-emit). When a different track is
6279
+ // loaded, the trackId differs and the new emission proceeds normally.
6280
+ if (__classPrivateFieldGet(this, _MixstatusProcessor_lastEmittedTrackId, "f").get(deviceId) === state.trackId) {
6281
+ return;
6282
+ }
6620
6283
  if (!__classPrivateFieldGet(this, _MixstatusProcessor_isSetActive, "f")) {
6621
6284
  __classPrivateFieldSet(this, _MixstatusProcessor_isSetActive, true, "f");
6622
6285
  __classPrivateFieldGet(this, _MixstatusProcessor_emitter, "f").emit('setStarted');
@@ -6625,6 +6288,7 @@ class MixstatusProcessor {
6625
6288
  __classPrivateFieldGet(this, _MixstatusProcessor_cancelSetEnding, "f").call(this);
6626
6289
  }
6627
6290
  __classPrivateFieldGet(this, _MixstatusProcessor_livePlayers, "f").add(deviceId);
6291
+ __classPrivateFieldGet(this, _MixstatusProcessor_lastEmittedTrackId, "f").set(deviceId, state.trackId);
6628
6292
  __classPrivateFieldGet(this, _MixstatusProcessor_emitter, "f").emit('nowPlaying', state);
6629
6293
  });
6630
6294
  /**
@@ -6818,7 +6482,7 @@ class MixstatusProcessor {
6818
6482
  }
6819
6483
  }
6820
6484
  exports.MixstatusProcessor = MixstatusProcessor;
6821
- _MixstatusProcessor_emitter = new WeakMap(), _MixstatusProcessor_lastState = new WeakMap(), _MixstatusProcessor_lastStartTime = new WeakMap(), _MixstatusProcessor_lastStoppedTimes = new WeakMap(), _MixstatusProcessor_livePlayers = new WeakMap(), _MixstatusProcessor_isSetActive = new WeakMap(), _MixstatusProcessor_cancelSetEnding = new WeakMap(), _MixstatusProcessor_config = new WeakMap(), _MixstatusProcessor_onAir = new WeakMap(), _MixstatusProcessor_promotePlayer = new WeakMap(), _MixstatusProcessor_promoteNextPlayer = new WeakMap(), _MixstatusProcessor_markPlayerStopped = new WeakMap(), _MixstatusProcessor_setMayStop = new WeakMap(), _MixstatusProcessor_playerMayBeFirst = new WeakMap(), _MixstatusProcessor_playerMayStop = new WeakMap(), _MixstatusProcessor_handlePlaystateChange = new WeakMap(), _MixstatusProcessor_handleOnairChange = new WeakMap();
6485
+ _MixstatusProcessor_emitter = new WeakMap(), _MixstatusProcessor_lastState = new WeakMap(), _MixstatusProcessor_lastStartTime = new WeakMap(), _MixstatusProcessor_lastStoppedTimes = new WeakMap(), _MixstatusProcessor_livePlayers = new WeakMap(), _MixstatusProcessor_lastEmittedTrackId = new WeakMap(), _MixstatusProcessor_isSetActive = new WeakMap(), _MixstatusProcessor_cancelSetEnding = new WeakMap(), _MixstatusProcessor_config = new WeakMap(), _MixstatusProcessor_onAir = new WeakMap(), _MixstatusProcessor_promotePlayer = new WeakMap(), _MixstatusProcessor_promoteNextPlayer = new WeakMap(), _MixstatusProcessor_markPlayerStopped = new WeakMap(), _MixstatusProcessor_setMayStop = new WeakMap(), _MixstatusProcessor_playerMayBeFirst = new WeakMap(), _MixstatusProcessor_playerMayStop = new WeakMap(), _MixstatusProcessor_handlePlaystateChange = new WeakMap(), _MixstatusProcessor_handleOnairChange = new WeakMap();
6822
6486
 
6823
6487
 
6824
6488
  /***/ },
@@ -6909,7 +6573,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
6909
6573
  var __importDefault = (this && this.__importDefault) || function (mod) {
6910
6574
  return (mod && mod.__esModule) ? mod : { "default": mod };
6911
6575
  };
6912
- var _ProlinkNetwork_state, _ProlinkNetwork_announceSocket, _ProlinkNetwork_beatSocket, _ProlinkNetwork_statusSocket, _ProlinkNetwork_deviceManager, _ProlinkNetwork_statusEmitter, _ProlinkNetwork_positionEmitter, _ProlinkNetwork_config, _ProlinkNetwork_connection, _ProlinkNetwork_mixstatus;
6576
+ var _ProlinkNetwork_state, _ProlinkNetwork_announceSocket, _ProlinkNetwork_beatSocket, _ProlinkNetwork_statusSocket, _ProlinkNetwork_deviceManager, _ProlinkNetwork_statusEmitter, _ProlinkNetwork_positionEmitter, _ProlinkNetwork_logger, _ProlinkNetwork_config, _ProlinkNetwork_connection, _ProlinkNetwork_mixstatus;
6913
6577
  Object.defineProperty(exports, "__esModule", ({ value: true }));
6914
6578
  exports.ProlinkNetwork = void 0;
6915
6579
  exports.bringOnline = bringOnline;
@@ -6919,6 +6583,7 @@ const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts
6919
6583
  const control_1 = __importDefault(__webpack_require__(/*! src/control */ "./src/control/index.ts"));
6920
6584
  const db_1 = __importDefault(__webpack_require__(/*! src/db */ "./src/db/index.ts"));
6921
6585
  const devices_1 = __importDefault(__webpack_require__(/*! src/devices */ "./src/devices/index.ts"));
6586
+ const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
6922
6587
  const localdb_1 = __importDefault(__webpack_require__(/*! src/localdb */ "./src/localdb/index.ts"));
6923
6588
  const mixstatus_1 = __webpack_require__(/*! src/mixstatus */ "./src/mixstatus/index.ts");
6924
6589
  const remotedb_1 = __importDefault(__webpack_require__(/*! src/remotedb */ "./src/remotedb/index.ts"));
@@ -6949,6 +6614,9 @@ async function bringOnline(config) {
6949
6614
  await (0, udp_1.udpBind)(announceSocket, constants_1.ANNOUNCE_PORT, '0.0.0.0');
6950
6615
  await (0, udp_1.udpBind)(beatSocket, constants_1.BEAT_PORT, '0.0.0.0');
6951
6616
  await (0, udp_1.udpBind)(statusSocket, constants_1.STATUS_PORT, '0.0.0.0');
6617
+ // Enable broadcast so the announcer can reach the whole subnet before any
6618
+ // devices have been discovered.
6619
+ announceSocket.setBroadcast(true);
6952
6620
  }
6953
6621
  catch (err) {
6954
6622
  Telemetry.captureException(err);
@@ -6976,6 +6644,7 @@ class ProlinkNetwork {
6976
6644
  * @internal
6977
6645
  */
6978
6646
  constructor({ config, announceSocket, beatSocket, statusSocket, deviceManager, statusEmitter, positionEmitter, }) {
6647
+ var _a;
6979
6648
  _ProlinkNetwork_state.set(this, types_1.NetworkState.Online);
6980
6649
  _ProlinkNetwork_announceSocket.set(this, void 0);
6981
6650
  _ProlinkNetwork_beatSocket.set(this, void 0);
@@ -6983,10 +6652,12 @@ class ProlinkNetwork {
6983
6652
  _ProlinkNetwork_deviceManager.set(this, void 0);
6984
6653
  _ProlinkNetwork_statusEmitter.set(this, void 0);
6985
6654
  _ProlinkNetwork_positionEmitter.set(this, void 0);
6655
+ _ProlinkNetwork_logger.set(this, void 0);
6986
6656
  _ProlinkNetwork_config.set(this, void 0);
6987
6657
  _ProlinkNetwork_connection.set(this, void 0);
6988
6658
  _ProlinkNetwork_mixstatus.set(this, void 0);
6989
6659
  __classPrivateFieldSet(this, _ProlinkNetwork_config, config !== null && config !== void 0 ? config : null, "f");
6660
+ __classPrivateFieldSet(this, _ProlinkNetwork_logger, (_a = config === null || config === void 0 ? void 0 : config.logger) !== null && _a !== void 0 ? _a : logger_1.noopLogger, "f");
6990
6661
  __classPrivateFieldSet(this, _ProlinkNetwork_announceSocket, announceSocket, "f");
6991
6662
  __classPrivateFieldSet(this, _ProlinkNetwork_beatSocket, beatSocket, "f");
6992
6663
  __classPrivateFieldSet(this, _ProlinkNetwork_statusSocket, statusSocket, "f");
@@ -7006,6 +6677,9 @@ class ProlinkNetwork {
7006
6677
  */
7007
6678
  configure(config) {
7008
6679
  __classPrivateFieldSet(this, _ProlinkNetwork_config, { ...__classPrivateFieldGet(this, _ProlinkNetwork_config, "f"), ...config }, "f");
6680
+ if (config.logger !== undefined) {
6681
+ __classPrivateFieldSet(this, _ProlinkNetwork_logger, config.logger, "f");
6682
+ }
7009
6683
  }
7010
6684
  /**
7011
6685
  * Wait for another device to show up on the network to determine which network
@@ -7039,7 +6713,7 @@ class ProlinkNetwork {
7039
6713
  * or manual configuration). This will then initialize all the network services.
7040
6714
  */
7041
6715
  connect() {
7042
- var _a, _b;
6716
+ var _a, _b, _c;
7043
6717
  if (__classPrivateFieldGet(this, _ProlinkNetwork_config, "f") === null) {
7044
6718
  throw new Error(connectErrorHelp);
7045
6719
  }
@@ -7051,13 +6725,13 @@ class ProlinkNetwork {
7051
6725
  __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f").reconfigure({ vcdjName: __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").vcdjName });
7052
6726
  }
7053
6727
  // Start announcing
7054
- const announcer = new virtualcdj_1.Announcer(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_announceSocket, "f"), this.deviceManager, __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").iface, (_a = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").fullStartup) !== null && _a !== void 0 ? _a : false);
6728
+ const announcer = new virtualcdj_1.Announcer(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_announceSocket, "f"), this.deviceManager, __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").iface, (_a = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").fullStartup) !== null && _a !== void 0 ? _a : false, (_b = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").announceToStagehand) !== null && _b !== void 0 ? _b : false, __classPrivateFieldGet(this, _ProlinkNetwork_logger, "f"));
7055
6729
  announcer.start();
7056
6730
  // Create remote and local databases
7057
6731
  const remotedb = new remotedb_1.default(__classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), vcdj);
7058
- const localdb = new localdb_1.default(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), __classPrivateFieldGet(this, _ProlinkNetwork_statusEmitter, "f"), (_b = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").databasePreference) !== null && _b !== void 0 ? _b : 'auto');
6732
+ const localdb = new localdb_1.default(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), __classPrivateFieldGet(this, _ProlinkNetwork_statusEmitter, "f"), (_c = __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").databasePreference) !== null && _c !== void 0 ? _c : 'auto');
7059
6733
  // Create unified database
7060
- const database = new db_1.default(vcdj, localdb, remotedb, __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"));
6734
+ const database = new db_1.default(localdb, remotedb, __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"));
7061
6735
  // Create controller service
7062
6736
  const control = new control_1.default(__classPrivateFieldGet(this, _ProlinkNetwork_beatSocket, "f"), vcdj);
7063
6737
  __classPrivateFieldSet(this, _ProlinkNetwork_state, types_1.NetworkState.Connected, "f");
@@ -7187,6 +6861,14 @@ class ProlinkNetwork {
7187
6861
  var _a, _b;
7188
6862
  return (_b = (_a = __classPrivateFieldGet(this, _ProlinkNetwork_connection, "f")) === null || _a === void 0 ? void 0 : _a.remotedb) !== null && _b !== void 0 ? _b : null;
7189
6863
  }
6864
+ /**
6865
+ * Returns a promise that resolves when the full startup protocol completes.
6866
+ * Resolves immediately if fullStartup is disabled or not connected.
6867
+ */
6868
+ get startupReady() {
6869
+ var _a, _b;
6870
+ return (_b = (_a = __classPrivateFieldGet(this, _ProlinkNetwork_connection, "f")) === null || _a === void 0 ? void 0 : _a.announcer.ready) !== null && _b !== void 0 ? _b : Promise.resolve();
6871
+ }
7190
6872
  /**
7191
6873
  * Get (and initialize) the {@link MixstatusProcessor} service. This service can
7192
6874
  * be used to monitor the 'status' of devices on the network as a whole.
@@ -7205,7 +6887,7 @@ class ProlinkNetwork {
7205
6887
  }
7206
6888
  }
7207
6889
  exports.ProlinkNetwork = ProlinkNetwork;
7208
- _ProlinkNetwork_state = new WeakMap(), _ProlinkNetwork_announceSocket = new WeakMap(), _ProlinkNetwork_beatSocket = new WeakMap(), _ProlinkNetwork_statusSocket = new WeakMap(), _ProlinkNetwork_deviceManager = new WeakMap(), _ProlinkNetwork_statusEmitter = new WeakMap(), _ProlinkNetwork_positionEmitter = new WeakMap(), _ProlinkNetwork_config = new WeakMap(), _ProlinkNetwork_connection = new WeakMap(), _ProlinkNetwork_mixstatus = new WeakMap();
6890
+ _ProlinkNetwork_state = new WeakMap(), _ProlinkNetwork_announceSocket = new WeakMap(), _ProlinkNetwork_beatSocket = new WeakMap(), _ProlinkNetwork_statusSocket = new WeakMap(), _ProlinkNetwork_deviceManager = new WeakMap(), _ProlinkNetwork_statusEmitter = new WeakMap(), _ProlinkNetwork_positionEmitter = new WeakMap(), _ProlinkNetwork_logger = new WeakMap(), _ProlinkNetwork_config = new WeakMap(), _ProlinkNetwork_connection = new WeakMap(), _ProlinkNetwork_mixstatus = new WeakMap();
7209
6891
 
7210
6892
 
7211
6893
  /***/ },
@@ -7252,6 +6934,9 @@ var __importStar = (this && this.__importStar) || (function () {
7252
6934
  };
7253
6935
  })();
7254
6936
  Object.defineProperty(exports, "__esModule", ({ value: true }));
6937
+ exports.parseWindowsPath = parseWindowsPath;
6938
+ exports.resolveNfsPath = resolveNfsPath;
6939
+ exports.getPortmapPort = getPortmapPort;
7255
6940
  exports.fetchFileRange = fetchFileRange;
7256
6941
  exports.getFileInfo = getFileInfo;
7257
6942
  exports.fetchFile = fetchFile;
@@ -7269,8 +6954,54 @@ const xdr_1 = __webpack_require__(/*! ./xdr */ "./src/nfs/xdr.ts");
7269
6954
  const slotMountMapping = {
7270
6955
  [types_1.MediaSlot.USB]: '/C/',
7271
6956
  [types_1.MediaSlot.SD]: '/B/',
7272
- [types_1.MediaSlot.RB]: '/',
7273
6957
  };
6958
+ /**
6959
+ * Parse a Windows absolute path (e.g. `C:\Users\chris\Music\track.mp3`)
6960
+ * into the NFS mount path and relative file path.
6961
+ *
6962
+ * @internal Exported for testing
6963
+ */
6964
+ function parseWindowsPath(filePath) {
6965
+ const match = filePath.match(/^([A-Za-z]):[/\\](.*)$/);
6966
+ if (!match) {
6967
+ return null;
6968
+ }
6969
+ return {
6970
+ mountPath: `/${match[1].toUpperCase()}/`,
6971
+ nfsPath: match[2].replace(/\\/g, '/'),
6972
+ };
6973
+ }
6974
+ /**
6975
+ * Resolve the NFS mount path and file path for a given slot.
6976
+ *
6977
+ * For USB/SD slots, the mount path is well-known (/C/ and /B/).
6978
+ * For the RB slot, the mount path is extracted from the file path returned
6979
+ * by remotedb:
6980
+ * - Windows: `C:\Users\chris\Music\track.mp3` → mount `/C/`, path `Users/chris/Music/track.mp3`
6981
+ * - macOS: `/Users/chris/Music/track.mp3` → mount `/`, path `Users/chris/Music/track.mp3`
6982
+ *
6983
+ * @internal Exported for testing
6984
+ */
6985
+ function resolveNfsPath(slot, filePath) {
6986
+ if (slot === types_1.MediaSlot.RB) {
6987
+ // Windows: C:\Users\chris\Music\track.mp3 → mount /C/, path Users/...
6988
+ const parsed = parseWindowsPath(filePath);
6989
+ if (parsed) {
6990
+ return parsed;
6991
+ }
6992
+ // macOS: /Users/chris/Music/track.mp3 → mount /, path Users/...
6993
+ if (filePath.startsWith('/')) {
6994
+ return {
6995
+ mountPath: '/',
6996
+ nfsPath: filePath.slice(1),
6997
+ };
6998
+ }
6999
+ }
7000
+ return {
7001
+ mountPath: slotMountMapping[slot],
7002
+ nfsPath: filePath,
7003
+ };
7004
+ }
7274
7005
  /**
7275
7006
  * The module-level retry configuration for newly created RpcConnections.
7276
7007
  */
@@ -7281,6 +7012,18 @@ let retryConfig = {};
7281
7012
  * still be connected.
7282
7013
  */
7283
7014
  const clientsCache = new Map();
7015
+ /**
7016
+ * Get the portmapper port for the given device. Rekordbox software uses a
7017
+ * non-standard port (50111) while CDJs and other hardware use the standard
7018
+ * port (111).
7019
+ *
7020
+ * @internal Exported for testing
7021
+ */
7022
+ function getPortmapPort(device) {
7023
+ return device.type === types_1.DeviceType.Rekordbox
7024
+ ? programs_1.REKORDBOX_PORTMAP_PORT
7025
+ : programs_1.STANDARD_PORTMAP_PORT;
7026
+ }
7284
7027
  /**
7285
7028
  * Given a device address running a nfs and mountd RPC server, provide
7286
7029
  * RpcProgram clients that may be used to call these services.
@@ -7288,7 +7031,8 @@ const clientsCache = new Map();
7288
7031
  * NOTE: This function will cache the clients for the address, recreating the
7289
7032
  * connections if the cached clients have disconnected.
7290
7033
  */
7291
- async function getClients(address) {
7034
+ async function getClients(device) {
7035
+ const { address } = device.ip;
7292
7036
  const cachedSet = clientsCache.get(address);
7293
7037
  if (cachedSet !== undefined && cachedSet.conn.connected) {
7294
7038
  return cachedSet;
@@ -7298,49 +7042,50 @@ async function getClients(address) {
7298
7042
  clientsCache.delete(address);
7299
7043
  }
7300
7044
  const conn = new rpc_1.RpcConnection(address, retryConfig);
7045
+ const portmapPort = getPortmapPort(device);
7301
7046
  const mountClient = await (0, programs_1.makeProgramClient)(conn, {
7302
7047
  id: xdr_1.mount.Program,
7303
7048
  version: xdr_1.mount.Version,
7304
- });
7049
+ }, portmapPort);
7305
7050
  const nfsClient = await (0, programs_1.makeProgramClient)(conn, {
7306
7051
  id: xdr_1.nfs.Program,
7307
7052
  version: xdr_1.nfs.Version,
7308
- });
7053
+ }, portmapPort);
7309
7054
  const set = { conn, mountClient, nfsClient };
7310
7055
  clientsCache.set(address, set);
7311
7056
  return set;
7312
7057
  }
7313
7058
  /**
7314
- * This module maintains a singleton cached list of (device address + slot) -> file
7059
+ * This module maintains a singleton cached list of (device address + mount path) -> file
7315
7060
  * handles. The file handles may become stale in this list should the devices
7316
7061
  * connected to the players slot change.
7317
7062
  */
7318
7063
  const rootHandleCache = new Map();
7319
7064
  /**
7320
- * Locate the root filehandle of the given device slot.
7065
+ * Locate the root filehandle of the given device mount path.
7321
7066
  *
7322
- * NOTE: This function will cache the root handle for the device + slot. Should
7067
+ * NOTE: This function will cache the root handle for the device + mount path. Should
7323
7068
  * the device have changed the slot will not longer be valid (TODO,
7324
7069
  * verify this). It is up to the caller to clear the cache and get the
7325
7070
  * new root handle in that case.
7326
7071
  */
7327
- async function getRootHandle({ device, slot, mountClient, span }) {
7072
+ async function getRootHandle({ device, mountPath, mountClient, span, }) {
7328
7073
  var _a;
7329
7074
  const tx = span === null || span === void 0 ? void 0 : span.startChild({ op: 'getRootHandle' });
7330
7075
  const { address } = device.ip;
7331
- const deviceSlotCache = (_a = rootHandleCache.get(address)) !== null && _a !== void 0 ? _a : new Map();
7332
- const cachedRootHandle = deviceSlotCache.get(slot);
7076
+ const deviceMountCache = (_a = rootHandleCache.get(address)) !== null && _a !== void 0 ? _a : new Map();
7077
+ const cachedRootHandle = deviceMountCache.get(mountPath);
7333
7078
  if (cachedRootHandle !== undefined) {
7334
7079
  return cachedRootHandle;
7335
7080
  }
7336
7081
  const exports = await (0, programs_1.getExports)(mountClient, tx);
7337
- const targetExport = exports.find(e => e.filesystem === slotMountMapping[slot]);
7082
+ const targetExport = exports.find(e => e.filesystem === mountPath);
7338
7083
  if (targetExport === undefined) {
7339
7084
  return null;
7340
7085
  }
7341
7086
  const rootHandle = await (0, programs_1.mountFilesystem)(mountClient, targetExport, tx);
7342
- deviceSlotCache.set(slot, rootHandle);
7343
- rootHandleCache.set(address, deviceSlotCache);
7087
+ deviceMountCache.set(mountPath, rootHandle);
7088
+ rootHandleCache.set(address, deviceMountCache);
7344
7089
  tx === null || tx === void 0 ? void 0 : tx.finish();
7345
7090
  return rootHandle;
7346
7091
  }
@@ -7353,22 +7098,23 @@ async function fetchFileRange({ device, slot, path, offset, length, span, }) {
7353
7098
  const tx = span
7354
7099
  ? span.startChild({ op: 'fetchFileRange' })
7355
7100
  : Telemetry.startTransaction({ name: 'fetchFileRange' });
7356
- const { mountClient, nfsClient } = await getClients(device.ip.address);
7357
- const rootHandle = await getRootHandle({ device, slot, mountClient, span: tx });
7101
+ const { mountPath, nfsPath } = resolveNfsPath(slot, path);
7102
+ const { mountClient, nfsClient } = await getClients(device);
7103
+ const rootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
7358
7104
  if (rootHandle === null) {
7359
7105
  throw badRoothandleError(slot, device.id);
7360
7106
  }
7361
7107
  let fileInfo = null;
7362
7108
  try {
7363
- fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, path, tx);
7109
+ fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, nfsPath, tx);
7364
7110
  }
7365
7111
  catch {
7366
7112
  rootHandleCache.delete(device.ip.address);
7367
- const newRootHandle = await getRootHandle({ device, slot, mountClient, span: tx });
7113
+ const newRootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
7368
7114
  if (newRootHandle === null) {
7369
7115
  throw badRoothandleError(slot, device.id);
7370
7116
  }
7371
- fileInfo = await (0, programs_1.lookupPath)(nfsClient, newRootHandle, path, tx);
7117
+ fileInfo = await (0, programs_1.lookupPath)(nfsClient, newRootHandle, nfsPath, tx);
7372
7118
  }
7373
7119
  const actualOffset = Math.min(offset, fileInfo.size);
7374
7120
  const actualLength = Math.min(length, fileInfo.size - actualOffset);
@@ -7392,22 +7138,23 @@ async function getFileInfo({ device, slot, path, span, }) {
7392
7138
  const tx = span
7393
7139
  ? span.startChild({ op: 'getFileInfo' })
7394
7140
  : Telemetry.startTransaction({ name: 'getFileInfo' });
7395
- const { mountClient, nfsClient } = await getClients(device.ip.address);
7396
- const rootHandle = await getRootHandle({ device, slot, mountClient, span: tx });
7141
+ const { mountPath, nfsPath } = resolveNfsPath(slot, path);
7142
+ const { mountClient, nfsClient } = await getClients(device);
7143
+ const rootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
7397
7144
  if (rootHandle === null) {
7398
7145
  throw badRoothandleError(slot, device.id);
7399
7146
  }
7400
7147
  let fileInfo;
7401
7148
  try {
7402
- fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, path, tx);
7149
+ fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, nfsPath, tx);
7403
7150
  }
7404
7151
  catch {
7405
7152
  rootHandleCache.delete(device.ip.address);
7406
- const newRootHandle = await getRootHandle({ device, slot, mountClient, span: tx });
7153
+ const newRootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
7407
7154
  if (newRootHandle === null) {
7408
7155
  throw badRoothandleError(slot, device.id);
7409
7156
  }
7410
- fileInfo = await (0, programs_1.lookupPath)(nfsClient, newRootHandle, path, tx);
7157
+ fileInfo = await (0, programs_1.lookupPath)(nfsClient, newRootHandle, nfsPath, tx);
7411
7158
  }
7412
7159
  tx.setData('path', path);
7413
7160
  tx.setData('slot', (0, utils_1.getSlotName)(slot));
@@ -7427,8 +7174,9 @@ async function fetchFile({ device, slot, path, onProgress, span, chunkSize, }) {
7427
7174
  const tx = span
7428
7175
  ? span.startChild({ op: 'fetchFile' })
7429
7176
  : Telemetry.startTransaction({ name: 'fetchFile' });
7430
- const { mountClient, nfsClient } = await getClients(device.ip.address);
7431
- const rootHandle = await getRootHandle({ device, slot, mountClient, span: tx });
7177
+ const { mountPath, nfsPath } = resolveNfsPath(slot, path);
7178
+ const { mountClient, nfsClient } = await getClients(device);
7179
+ const rootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
7432
7180
  if (rootHandle === null) {
7433
7181
  throw badRoothandleError(slot, device.id);
7434
7182
  }
@@ -7436,16 +7184,16 @@ async function fetchFile({ device, slot, path, onProgress, span, chunkSize, }) {
7436
7184
  // a path lets first try and clear our roothandle cache
7437
7185
  let fileInfo = null;
7438
7186
  try {
7439
- fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, path, tx);
7187
+ fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, nfsPath, tx);
7440
7188
  }
7441
7189
  catch {
7442
7190
  rootHandleCache.delete(device.ip.address);
7443
- const rootHandle = await getRootHandle({ device, slot, mountClient, span: tx });
7191
+ const rootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
7444
7192
  if (rootHandle === null) {
7445
7193
  throw badRoothandleError(slot, device.id);
7446
7194
  }
7447
7195
  // Desperately try once more to lookup the file
7448
- fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, path, tx);
7196
+ fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, nfsPath, tx);
7449
7197
  }
7450
7198
  const file = await (0, programs_1.fetchFile)(nfsClient, fileInfo, onProgress, tx, chunkSize);
7451
7199
  tx.setData('path', path);
@@ -7483,6 +7231,7 @@ function configureRetryStrategy(config) {
7483
7231
  "use strict";
7484
7232
 
7485
7233
  Object.defineProperty(exports, "__esModule", ({ value: true }));
7234
+ exports.REKORDBOX_PORTMAP_PORT = exports.STANDARD_PORTMAP_PORT = void 0;
7486
7235
  exports.makeProgramClient = makeProgramClient;
7487
7236
  exports.getExports = getExports;
7488
7237
  exports.mountFilesystem = mountFilesystem;
@@ -7497,10 +7246,19 @@ const xdr_1 = __webpack_require__(/*! ./xdr */ "./src/nfs/xdr.ts");
7497
7246
  * How many bytes of a file should we read at once.
7498
7247
  */
7499
7248
  const READ_SIZE = 8192;
7249
+ /**
7250
+ * Standard portmapper port used by CDJs and other hardware.
7251
+ */
7252
+ exports.STANDARD_PORTMAP_PORT = 111;
7253
+ /**
7254
+ * Non-standard portmapper port used by rekordbox software.
7255
+ * Rekordbox registers its RPC services on this port instead of 111.
7256
+ */
7257
+ exports.REKORDBOX_PORTMAP_PORT = 50111;
7500
7258
  /**
7501
7259
  * Queries for the listening port of a RPC program
7502
7260
  */
7503
- async function makeProgramClient(conn, program) {
7261
+ async function makeProgramClient(conn, program, portmapPort = exports.STANDARD_PORTMAP_PORT) {
7504
7262
  const getPortData = new xdr_1.portmap.GetPort({
7505
7263
  program: program.id,
7506
7264
  version: program.version,
@@ -7508,7 +7266,7 @@ async function makeProgramClient(conn, program) {
7508
7266
  port: 0,
7509
7267
  });
7510
7268
  const data = await conn.call({
7511
- port: 111,
7269
+ port: portmapPort,
7512
7270
  program: xdr_1.portmap.Program,
7513
7271
  version: xdr_1.portmap.Version,
7514
7272
  procedure: xdr_1.portmap.Procedure.getPort().value,
@@ -8565,10 +8323,14 @@ class QueryInterface {
8565
8323
  const anyArgs = args;
8566
8324
  const handler = queries_1.queryHandlers[query];
8567
8325
  const releaseLock = await __classPrivateFieldGet(this, _QueryInterface_lock, "f").acquire();
8568
- const response = await handler({ conn, lookupDescriptor, span: tx, args: anyArgs });
8569
- releaseLock();
8570
- tx.finish();
8571
- return response;
8326
+ try {
8327
+ const response = await handler({ conn, lookupDescriptor, span: tx, args: anyArgs });
8328
+ tx.finish();
8329
+ return response;
8330
+ }
8331
+ finally {
8332
+ releaseLock();
8333
+ }
8572
8334
  }
8573
8335
  }
8574
8336
  exports.QueryInterface = QueryInterface;
@@ -8596,6 +8358,11 @@ class RemoteDatabase {
8596
8358
  const { ip } = device;
8597
8359
  const dbPort = await getRemoteDBServerPort(ip);
8598
8360
  const socket = new promise_socket_1.default(new net_1.Socket());
8361
+ // Set a connection timeout to prevent hanging forever
8362
+ socket.stream.setTimeout(10000);
8363
+ socket.stream.once('timeout', () => {
8364
+ socket.stream.destroy(new Error(`RemoteDB connection to ${ip.address}:${dbPort} timed out`));
8365
+ });
8599
8366
  await socket.connect(dbPort, ip.address);
8600
8367
  // Send required preamble to open communications with the device
8601
8368
  const preamble = new fields_1.UInt32(0x01);
@@ -8617,6 +8384,9 @@ class RemoteDatabase {
8617
8384
  if (resp.type !== types_1.MessageType.Success) {
8618
8385
  throw new Error(`Failed to introduce self to device ID: ${device.id}`);
8619
8386
  }
8387
+ // Clear socket timeout after successful connection — the per-device mutex
8388
+ // and application-level Promise.race timeouts handle query timeouts
8389
+ socket.stream.setTimeout(0);
8620
8390
  __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").set(device.id, new Connection(device, socket));
8621
8391
  tx.finish();
8622
8392
  };
@@ -8658,15 +8428,19 @@ class RemoteDatabase {
8658
8428
  }
8659
8429
  const lock = (_a = __classPrivateFieldGet(this, _RemoteDatabase_deviceLocks, "f").get(device.id)) !== null && _a !== void 0 ? _a : __classPrivateFieldGet(this, _RemoteDatabase_deviceLocks, "f").set(device.id, new async_mutex_1.Mutex()).get(device.id);
8660
8430
  const releaseLock = await lock.acquire();
8661
- let conn = __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").get(deviceId);
8662
- if (conn === undefined) {
8663
- await this.connectToDevice(device);
8431
+ try {
8432
+ let conn = __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").get(deviceId);
8433
+ if (conn === undefined) {
8434
+ await this.connectToDevice(device);
8435
+ }
8436
+ conn = __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").get(deviceId);
8437
+ // NOTE: We pass the same lock we use for this device to the query
8438
+ // interface to ensure all query interfaces use the same lock.
8439
+ return new QueryInterface(conn, lock, __classPrivateFieldGet(this, _RemoteDatabase_hostDevice, "f"));
8440
+ }
8441
+ finally {
8442
+ releaseLock();
8664
8443
  }
8665
- conn = __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").get(deviceId);
8666
- releaseLock();
8667
- // NOTE: We pass the same lock we use for this device to the query
8668
- // interface to ensure all query interfaces use the same lock.
8669
- return new QueryInterface(conn, lock, __classPrivateFieldGet(this, _RemoteDatabase_hostDevice, "f"));
8670
8444
  }
8671
8445
  }
8672
8446
  _RemoteDatabase_hostDevice = new WeakMap(), _RemoteDatabase_deviceManager = new WeakMap(), _RemoteDatabase_connections = new WeakMap(), _RemoteDatabase_deviceLocks = new WeakMap();
@@ -9368,7 +9142,7 @@ const utils_1 = __webpack_require__(/*! ./utils */ "./src/remotedb/utils.ts");
9368
9142
  * Lookup track metadata from rekordbox and coerce it into a Track entity
9369
9143
  */
9370
9144
  async function getMetadata(opts) {
9371
- var _a, _b, _c, _d, _e;
9145
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
9372
9146
  const { conn, lookupDescriptor, span, args } = opts;
9373
9147
  const { trackId } = args;
9374
9148
  const request = new message_1.Message({
@@ -9385,24 +9159,25 @@ async function getMetadata(opts) {
9385
9159
  trackItems[item.type] = item;
9386
9160
  }
9387
9161
  // Translate our trackItems into a (partial) Track entity.
9162
+ // Use optional chaining for all fields since streaming tracks may omit some.
9388
9163
  const track = {
9389
- id: trackItems[item_1.ItemType.TrackTitle].id,
9390
- title: trackItems[item_1.ItemType.TrackTitle].title,
9391
- duration: trackItems[item_1.ItemType.Duration].duration,
9392
- tempo: trackItems[item_1.ItemType.Tempo].bpm,
9393
- comment: trackItems[item_1.ItemType.Comment].comment,
9394
- rating: trackItems[item_1.ItemType.Rating].rating,
9395
- year: (_a = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.Year]) === null || _a === void 0 ? void 0 : _a.year,
9396
- bitrate: (_b = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.BitRate]) === null || _b === void 0 ? void 0 : _b.bitrate,
9397
- artwork: { id: trackItems[item_1.ItemType.TrackTitle].artworkId },
9398
- album: trackItems[item_1.ItemType.AlbumTitle],
9399
- artist: trackItems[item_1.ItemType.Artist],
9400
- genre: trackItems[item_1.ItemType.Genre],
9401
- key: trackItems[item_1.ItemType.Key],
9402
- color: (0, utils_1.findColor)(Object.values(trackItems)),
9403
- label: (_c = trackItems[item_1.ItemType.Label]) !== null && _c !== void 0 ? _c : null,
9404
- remixer: (_d = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.Remixer]) !== null && _d !== void 0 ? _d : null,
9405
- originalArtist: (_e = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.OriginalArtist]) !== null && _e !== void 0 ? _e : null,
9164
+ id: (_b = (_a = trackItems[item_1.ItemType.TrackTitle]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : 0,
9165
+ title: (_d = (_c = trackItems[item_1.ItemType.TrackTitle]) === null || _c === void 0 ? void 0 : _c.title) !== null && _d !== void 0 ? _d : '',
9166
+ duration: (_f = (_e = trackItems[item_1.ItemType.Duration]) === null || _e === void 0 ? void 0 : _e.duration) !== null && _f !== void 0 ? _f : 0,
9167
+ tempo: (_h = (_g = trackItems[item_1.ItemType.Tempo]) === null || _g === void 0 ? void 0 : _g.bpm) !== null && _h !== void 0 ? _h : 0,
9168
+ comment: (_k = (_j = trackItems[item_1.ItemType.Comment]) === null || _j === void 0 ? void 0 : _j.comment) !== null && _k !== void 0 ? _k : '',
9169
+ rating: (_m = (_l = trackItems[item_1.ItemType.Rating]) === null || _l === void 0 ? void 0 : _l.rating) !== null && _m !== void 0 ? _m : 0,
9170
+ year: (_o = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.Year]) === null || _o === void 0 ? void 0 : _o.year,
9171
+ bitrate: (_p = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.BitRate]) === null || _p === void 0 ? void 0 : _p.bitrate,
9172
+ artwork: { id: (_r = (_q = trackItems[item_1.ItemType.TrackTitle]) === null || _q === void 0 ? void 0 : _q.artworkId) !== null && _r !== void 0 ? _r : 0 },
9173
+ album: (_s = trackItems[item_1.ItemType.AlbumTitle]) !== null && _s !== void 0 ? _s : null,
9174
+ artist: (_t = trackItems[item_1.ItemType.Artist]) !== null && _t !== void 0 ? _t : null,
9175
+ genre: (_u = trackItems[item_1.ItemType.Genre]) !== null && _u !== void 0 ? _u : null,
9176
+ key: (_v = trackItems[item_1.ItemType.Key]) !== null && _v !== void 0 ? _v : null,
9177
+ color: (_w = (0, utils_1.findColor)(Object.values(trackItems))) !== null && _w !== void 0 ? _w : null,
9178
+ label: (_x = trackItems[item_1.ItemType.Label]) !== null && _x !== void 0 ? _x : null,
9179
+ remixer: (_y = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.Remixer]) !== null && _y !== void 0 ? _y : null,
9180
+ originalArtist: (_z = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.OriginalArtist]) !== null && _z !== void 0 ? _z : null,
9406
9181
  composer: null,
9407
9182
  fileName: '',
9408
9183
  filePath: '',
@@ -10010,7 +9785,7 @@ function mediaSlotFromPacket(packet) {
10010
9785
  return undefined;
10011
9786
  }
10012
9787
  const name = packet
10013
- .slice(0x2c, 0x0c + 40)
9788
+ .slice(0x2c, 0x2c + 40)
10014
9789
  .toString()
10015
9790
  .replace(/\0/g, '');
10016
9791
  const createdDate = new Date(packet
@@ -10187,7 +9962,7 @@ var __importStar = (this && this.__importStar) || (function () {
10187
9962
  };
10188
9963
  })();
10189
9964
  Object.defineProperty(exports, "__esModule", ({ value: true }));
10190
- exports.MixstatusMode = exports.NetworkState = exports.CueColor = exports.HotcueButton = exports.TrackType = exports.MediaSlot = exports.MediaColor = exports.DeviceType = exports.CDJStatus = void 0;
9965
+ exports.MixstatusMode = exports.NetworkState = exports.HotcueButton = exports.CueColor = exports.TrackType = exports.MediaSlot = exports.MediaColor = exports.DeviceType = exports.CDJStatus = void 0;
10191
9966
  exports.CDJStatus = __importStar(__webpack_require__(/*! src/status/types */ "./src/status/types.ts"));
10192
9967
  /**
10193
9968
  * Known device types on the network
@@ -10220,6 +9995,11 @@ var MediaSlot;
10220
9995
  MediaSlot[MediaSlot["SD"] = 2] = "SD";
10221
9996
  MediaSlot[MediaSlot["USB"] = 3] = "USB";
10222
9997
  MediaSlot[MediaSlot["RB"] = 4] = "RB";
9998
+ MediaSlot[MediaSlot["Unknown05"] = 5] = "Unknown05";
9999
+ MediaSlot[MediaSlot["StreamingDirectPlay"] = 6] = "StreamingDirectPlay";
10000
+ MediaSlot[MediaSlot["Unknown07"] = 7] = "Unknown07";
10001
+ MediaSlot[MediaSlot["Unknown08"] = 8] = "Unknown08";
10002
+ MediaSlot[MediaSlot["Beatport"] = 9] = "Beatport";
10223
10003
  })(MediaSlot || (exports.MediaSlot = MediaSlot = {}));
10224
10004
  /**
10225
10005
  * Track type flags
@@ -10230,46 +10010,14 @@ var TrackType;
10230
10010
  TrackType[TrackType["RB"] = 1] = "RB";
10231
10011
  TrackType[TrackType["Unanalyzed"] = 2] = "Unanalyzed";
10232
10012
  TrackType[TrackType["AudioCD"] = 5] = "AudioCD";
10013
+ TrackType[TrackType["Streaming"] = 6] = "Streaming";
10233
10014
  })(TrackType || (exports.TrackType = TrackType = {}));
10234
10015
  /**
10235
- * A hotcue button label
10236
- */
10237
- var HotcueButton;
10238
- (function (HotcueButton) {
10239
- HotcueButton[HotcueButton["A"] = 1] = "A";
10240
- HotcueButton[HotcueButton["B"] = 2] = "B";
10241
- HotcueButton[HotcueButton["C"] = 3] = "C";
10242
- HotcueButton[HotcueButton["D"] = 4] = "D";
10243
- HotcueButton[HotcueButton["E"] = 5] = "E";
10244
- HotcueButton[HotcueButton["F"] = 6] = "F";
10245
- HotcueButton[HotcueButton["G"] = 7] = "G";
10246
- HotcueButton[HotcueButton["H"] = 8] = "H";
10247
- })(HotcueButton || (exports.HotcueButton = HotcueButton = {}));
10248
- /**
10249
- * When a custom color is not configured the cue point will be one of these
10250
- * colors.
10016
+ * Re-export cue types from onelibrary-connect
10251
10017
  */
10252
- var CueColor;
10253
- (function (CueColor) {
10254
- CueColor[CueColor["None"] = 0] = "None";
10255
- CueColor[CueColor["Blank"] = 21] = "Blank";
10256
- CueColor[CueColor["Magenta"] = 49] = "Magenta";
10257
- CueColor[CueColor["Violet"] = 56] = "Violet";
10258
- CueColor[CueColor["Fuchsia"] = 60] = "Fuchsia";
10259
- CueColor[CueColor["LightSlateBlue"] = 62] = "LightSlateBlue";
10260
- CueColor[CueColor["Blue"] = 1] = "Blue";
10261
- CueColor[CueColor["SteelBlue"] = 5] = "SteelBlue";
10262
- CueColor[CueColor["Aqua"] = 9] = "Aqua";
10263
- CueColor[CueColor["SeaGreen"] = 14] = "SeaGreen";
10264
- CueColor[CueColor["Teal"] = 18] = "Teal";
10265
- CueColor[CueColor["Green"] = 22] = "Green";
10266
- CueColor[CueColor["Lime"] = 26] = "Lime";
10267
- CueColor[CueColor["Olive"] = 30] = "Olive";
10268
- CueColor[CueColor["Yellow"] = 32] = "Yellow";
10269
- CueColor[CueColor["Orange"] = 38] = "Orange";
10270
- CueColor[CueColor["Red"] = 42] = "Red";
10271
- CueColor[CueColor["Pink"] = 45] = "Pink";
10272
- })(CueColor || (exports.CueColor = CueColor = {}));
10018
+ var onelibrary_connect_1 = __webpack_require__(/*! onelibrary-connect */ "onelibrary-connect");
10019
+ Object.defineProperty(exports, "CueColor", ({ enumerable: true, get: function () { return onelibrary_connect_1.CueColor; } }));
10020
+ Object.defineProperty(exports, "HotcueButton", ({ enumerable: true, get: function () { return onelibrary_connect_1.HotcueButton; } }));
10273
10021
  var NetworkState;
10274
10022
  (function (NetworkState) {
10275
10023
  /**
@@ -10427,6 +10175,7 @@ var __importStar = (this && this.__importStar) || (function () {
10427
10175
  Object.defineProperty(exports, "__esModule", ({ value: true }));
10428
10176
  exports.buildName = buildName;
10429
10177
  exports.getMatchingInterface = getMatchingInterface;
10178
+ exports.getBroadcastAddress = getBroadcastAddress;
10430
10179
  exports.bpmToSeconds = bpmToSeconds;
10431
10180
  exports.getSlotName = getSlotName;
10432
10181
  exports.getTrackTypeName = getTrackTypeName;
@@ -10464,6 +10213,18 @@ function getMatchingInterface(ipAddr) {
10464
10213
  }
10465
10214
  return matchedIface;
10466
10215
  }
10216
+ /**
10217
+ * Computes the IPv4 subnet broadcast address for a network interface.
10218
+ *
10219
+ * Builds the address from the interface CIDR so the broadcast covers the whole
10220
+ * subnet (e.g. x.x.x.255 for a /24). Falls back to the bare interface address
10221
+ * when no CIDR is available, which only reaches the host itself but avoids
10222
+ * throwing.
10223
+ */
10224
+ function getBroadcastAddress(iface) {
10225
+ var _a;
10226
+ return new ip.Address4((_a = iface.cidr) !== null && _a !== void 0 ? _a : iface.address).endAddress().address;
10227
+ }
10467
10228
  /**
10468
10229
  * Given a BPM and pitch value, compute how many seconds per beat
10469
10230
  */
@@ -10721,13 +10482,14 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10721
10482
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10722
10483
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
10723
10484
  };
10724
- var _Announcer_instances, _Announcer_announceSocket, _Announcer_deviceManager, _Announcer_vcdj, _Announcer_intervalHandle, _Announcer_fullStartup, _Announcer_currentStage, _Announcer_stageCounter, _Announcer_conflictListener, _Announcer_iface, _Announcer_startFullStartup, _Announcer_handleConflict, _Announcer_sendStagePackets, _Announcer_advanceStage, _Announcer_startKeepAlive;
10485
+ var _Announcer_instances, _Announcer_announceSocket, _Announcer_deviceManager, _Announcer_vcdj, _Announcer_intervalHandle, _Announcer_fullStartup, _Announcer_announceToStagehand, _Announcer_currentStage, _Announcer_stageCounter, _Announcer_conflictListener, _Announcer_iface, _Announcer_logger, _Announcer_onStartupComplete, _Announcer_startFullStartup, _Announcer_handleConflict, _Announcer_sendPacket, _Announcer_sendStagePackets, _Announcer_advanceStage, _Announcer_startKeepAlive;
10725
10486
  Object.defineProperty(exports, "__esModule", ({ value: true }));
10726
10487
  exports.Announcer = exports.getVirtualCDJ = void 0;
10727
10488
  exports.makeStatusPacket = makeStatusPacket;
10728
10489
  exports.makeAnnouncePacket = makeAnnouncePacket;
10729
10490
  const ip = __importStar(__webpack_require__(/*! ip-address */ "ip-address"));
10730
10491
  const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
10492
+ const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
10731
10493
  const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
10732
10494
  const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
10733
10495
  /**
@@ -10975,7 +10737,7 @@ function parseConflictPacket(packet) {
10975
10737
  * as if it was a real CDJ.
10976
10738
  */
10977
10739
  class Announcer {
10978
- constructor(vcdj, announceSocket, deviceManager, iface, fullStartup = false) {
10740
+ constructor(vcdj, announceSocket, deviceManager, iface, fullStartup = false, announceToStagehand = false, logger = logger_1.noopLogger) {
10979
10741
  _Announcer_instances.add(this);
10980
10742
  /**
10981
10743
  * The announce socket to use to make the announcements
@@ -10998,6 +10760,11 @@ class Announcer {
10998
10760
  * Whether to use full startup protocol
10999
10761
  */
11000
10762
  _Announcer_fullStartup.set(this, void 0);
10763
+ /**
10764
+ * Whether to send announcer packets to Pioneer Stagehand devices, which are
10765
+ * normally excluded because they crash on our packets
10766
+ */
10767
+ _Announcer_announceToStagehand.set(this, void 0);
11001
10768
  /**
11002
10769
  * Current startup stage (only used when fullStartup is enabled)
11003
10770
  */
@@ -11014,11 +10781,36 @@ class Announcer {
11014
10781
  * Network interface for the virtual CDJ
11015
10782
  */
11016
10783
  _Announcer_iface.set(this, void 0);
10784
+ /**
10785
+ * Logger instance for diagnostic output
10786
+ */
10787
+ _Announcer_logger.set(this, void 0);
10788
+ /**
10789
+ * Callback invoked when the full startup protocol completes
10790
+ */
10791
+ _Announcer_onStartupComplete.set(this, void 0);
11017
10792
  __classPrivateFieldSet(this, _Announcer_vcdj, vcdj, "f");
11018
10793
  __classPrivateFieldSet(this, _Announcer_announceSocket, announceSocket, "f");
11019
10794
  __classPrivateFieldSet(this, _Announcer_deviceManager, deviceManager, "f");
11020
10795
  __classPrivateFieldSet(this, _Announcer_iface, iface, "f");
11021
10796
  __classPrivateFieldSet(this, _Announcer_fullStartup, fullStartup, "f");
10797
+ __classPrivateFieldSet(this, _Announcer_announceToStagehand, announceToStagehand, "f");
10798
+ __classPrivateFieldSet(this, _Announcer_logger, logger, "f");
10799
+ }
10800
+ /**
10801
+ * Returns a promise that resolves when the startup protocol completes.
10802
+ * Resolves immediately if fullStartup is disabled.
10803
+ */
10804
+ get ready() {
10805
+ if (!__classPrivateFieldGet(this, _Announcer_fullStartup, "f")) {
10806
+ return Promise.resolve();
10807
+ }
10808
+ if (__classPrivateFieldGet(this, _Announcer_currentStage, "f") === StartupStage.KeepAlive) {
10809
+ return Promise.resolve();
10810
+ }
10811
+ return new Promise(resolve => {
10812
+ __classPrivateFieldSet(this, _Announcer_onStartupComplete, resolve, "f");
10813
+ });
11022
10814
  }
11023
10815
  start() {
11024
10816
  if (__classPrivateFieldGet(this, _Announcer_fullStartup, "f")) {
@@ -11042,14 +10834,14 @@ class Announcer {
11042
10834
  }
11043
10835
  }
11044
10836
  exports.Announcer = Announcer;
11045
- _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMap(), _Announcer_vcdj = new WeakMap(), _Announcer_intervalHandle = new WeakMap(), _Announcer_fullStartup = new WeakMap(), _Announcer_currentStage = new WeakMap(), _Announcer_stageCounter = new WeakMap(), _Announcer_conflictListener = new WeakMap(), _Announcer_iface = new WeakMap(), _Announcer_instances = new WeakSet(), _Announcer_startFullStartup = function _Announcer_startFullStartup() {
10837
+ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMap(), _Announcer_vcdj = new WeakMap(), _Announcer_intervalHandle = new WeakMap(), _Announcer_fullStartup = new WeakMap(), _Announcer_announceToStagehand = new WeakMap(), _Announcer_currentStage = new WeakMap(), _Announcer_stageCounter = new WeakMap(), _Announcer_conflictListener = new WeakMap(), _Announcer_iface = new WeakMap(), _Announcer_logger = new WeakMap(), _Announcer_onStartupComplete = new WeakMap(), _Announcer_instances = new WeakSet(), _Announcer_startFullStartup = function _Announcer_startFullStartup() {
11046
10838
  __classPrivateFieldSet(this, _Announcer_currentStage, StartupStage.InitialAnnounce, "f");
11047
10839
  __classPrivateFieldSet(this, _Announcer_stageCounter, 0, "f");
11048
10840
  // Set up conflict detection listener
11049
10841
  __classPrivateFieldSet(this, _Announcer_conflictListener, (msg) => {
11050
10842
  const conflictDeviceId = parseConflictPacket(msg);
11051
10843
  if (conflictDeviceId === __classPrivateFieldGet(this, _Announcer_vcdj, "f").id) {
11052
- console.warn(`Device ID ${__classPrivateFieldGet(this, _Announcer_vcdj, "f").id} is already in use. Finding alternative...`);
10844
+ __classPrivateFieldGet(this, _Announcer_logger, "f").warn(`Device ID ${__classPrivateFieldGet(this, _Announcer_vcdj, "f").id} is already in use. Finding alternative...`);
11053
10845
  __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_handleConflict).call(this);
11054
10846
  }
11055
10847
  }, "f");
@@ -11080,20 +10872,29 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
11080
10872
  }
11081
10873
  }
11082
10874
  if (newId === null) {
11083
- console.error('No available device IDs. All 32 slots are occupied.');
10875
+ __classPrivateFieldGet(this, _Announcer_logger, "f").error('No available device IDs. All 32 slots are occupied.');
11084
10876
  this.stop();
11085
10877
  return;
11086
10878
  }
11087
- console.log(`Switching to device ID ${newId}`);
10879
+ __classPrivateFieldGet(this, _Announcer_logger, "f").info(`Switching to device ID ${newId}`);
11088
10880
  // Update virtual CDJ with new ID
11089
10881
  __classPrivateFieldSet(this, _Announcer_vcdj, (0, exports.getVirtualCDJ)(__classPrivateFieldGet(this, _Announcer_iface, "f"), newId), "f");
11090
10882
  // Restart startup sequence
11091
10883
  __classPrivateFieldSet(this, _Announcer_currentStage, StartupStage.InitialAnnounce, "f");
11092
10884
  __classPrivateFieldSet(this, _Announcer_stageCounter, 0, "f");
11093
10885
  __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_sendStagePackets).call(this);
10886
+ }, _Announcer_sendPacket = function _Announcer_sendPacket(packet) {
10887
+ const devices = [...__classPrivateFieldGet(this, _Announcer_deviceManager, "f").devices.values()].filter(d => __classPrivateFieldGet(this, _Announcer_announceToStagehand, "f") || !d.name.toLowerCase().includes('stagehand'));
10888
+ devices.forEach(device => __classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, device.ip.address));
10889
+ // Cold-start discovery broadcast — gated; see the method doc above.
10890
+ if (devices.length === 0 && __classPrivateFieldGet(this, _Announcer_announceToStagehand, "f")) {
10891
+ const broadcastAddr = (0, utils_1.getBroadcastAddress)(__classPrivateFieldGet(this, _Announcer_iface, "f"));
10892
+ __classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, broadcastAddr);
10893
+ }
11094
10894
  }, _Announcer_sendStagePackets = function _Announcer_sendStagePackets() {
11095
10895
  var _a;
11096
- __classPrivateFieldSet(this, _Announcer_stageCounter, (_a = __classPrivateFieldGet(this, _Announcer_stageCounter, "f"), _a++, _a), "f");
10896
+ var _b;
10897
+ __classPrivateFieldSet(this, _Announcer_stageCounter, (_b = __classPrivateFieldGet(this, _Announcer_stageCounter, "f"), _b++, _b), "f");
11097
10898
  // Build packet for current stage
11098
10899
  let packet;
11099
10900
  switch (__classPrivateFieldGet(this, _Announcer_currentStage, "f")) {
@@ -11112,16 +10913,11 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
11112
10913
  case StartupStage.KeepAlive:
11113
10914
  // Transition to keep-alive mode
11114
10915
  __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_startKeepAlive).call(this);
10916
+ (_a = __classPrivateFieldGet(this, _Announcer_onStartupComplete, "f")) === null || _a === void 0 ? void 0 : _a.call(this);
10917
+ __classPrivateFieldSet(this, _Announcer_onStartupComplete, undefined, "f");
11115
10918
  return;
11116
10919
  }
11117
- // Broadcast packet to all known devices
11118
- const devices = [...__classPrivateFieldGet(this, _Announcer_deviceManager, "f").devices.values()];
11119
- devices.forEach(device => __classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, device.ip.address));
11120
- // Also broadcast to network if no devices yet
11121
- if (devices.length === 0) {
11122
- const broadcastAddr = __classPrivateFieldGet(this, _Announcer_vcdj, "f").ip.endAddress().address;
11123
- __classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, broadcastAddr);
11124
- }
10920
+ __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_sendPacket).call(this, packet);
11125
10921
  // Progress to next packet or stage
11126
10922
  if (__classPrivateFieldGet(this, _Announcer_stageCounter, "f") >= 3) {
11127
10923
  // Move to next stage
@@ -11160,13 +10956,7 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
11160
10956
  const packet = __classPrivateFieldGet(this, _Announcer_fullStartup, "f")
11161
10957
  ? makeStage06Packet(__classPrivateFieldGet(this, _Announcer_vcdj, "f"), peerCount)
11162
10958
  : makeAnnouncePacket(__classPrivateFieldGet(this, _Announcer_vcdj, "f"));
11163
- const devices = [...__classPrivateFieldGet(this, _Announcer_deviceManager, "f").devices.values()];
11164
- devices.forEach(device => __classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, device.ip.address));
11165
- // Also broadcast if no devices
11166
- if (devices.length === 0) {
11167
- const broadcastAddr = __classPrivateFieldGet(this, _Announcer_vcdj, "f").ip.endAddress().address;
11168
- __classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, broadcastAddr);
11169
- }
10959
+ __classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_sendPacket).call(this, packet);
11170
10960
  };
11171
10961
  // Send first keep-alive immediately
11172
10962
  sendKeepAlive();
@@ -11309,6 +11099,17 @@ module.exports = require("net");
11309
11099
 
11310
11100
  /***/ },
11311
11101
 
11102
+ /***/ "onelibrary-connect"
11103
+ /*!*************************************!*\
11104
+ !*** external "onelibrary-connect" ***!
11105
+ \*************************************/
11106
+ (module) {
11107
+
11108
+ "use strict";
11109
+ module.exports = require("onelibrary-connect");
11110
+
11111
+ /***/ },
11112
+
11312
11113
  /***/ "os"
11313
11114
  /*!*********************!*\
11314
11115
  !*** external "os" ***!
@@ -11373,17 +11174,6 @@ module.exports = require("promise-timeout");
11373
11174
  "use strict";
11374
11175
  module.exports = require("signale");
11375
11176
 
11376
- /***/ },
11377
-
11378
- /***/ "zlib"
11379
- /*!***********************!*\
11380
- !*** external "zlib" ***!
11381
- \***********************/
11382
- (module) {
11383
-
11384
- "use strict";
11385
- module.exports = require("zlib");
11386
-
11387
11177
  /***/ }
11388
11178
 
11389
11179
  /******/ });