alphatheta-connect 0.15.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -3
- package/lib/artwork/index.d.ts +2 -1
- package/lib/cli.js +1122 -1332
- package/lib/cli.js.map +1 -1
- package/lib/constants.d.ts +7 -1
- package/lib/db/getArtworkFromFile.d.ts +2 -0
- package/lib/db/getFile.d.ts +7 -2
- package/lib/db/getMetadata.d.ts +3 -2
- package/lib/db/getTrackAnalysis.d.ts +28 -0
- package/lib/db/index.d.ts +8 -8
- package/lib/entities.d.ts +13 -111
- package/lib/index.d.ts +10 -2
- package/lib/index.js +1356 -1352
- package/lib/index.js.map +1 -1
- package/lib/localdb/database-adapter.d.ts +2 -47
- package/lib/localdb/index.d.ts +1 -1
- package/lib/localdb/onelibrary/adapter.d.ts +110 -0
- package/lib/localdb/onelibrary/connection.d.ts +8 -0
- package/lib/localdb/onelibrary/encryption.d.ts +10 -0
- package/lib/localdb/onelibrary/index.d.ts +5 -0
- package/lib/localdb/onelibrary/types.d.ts +67 -0
- package/lib/localdb/onelibrary.d.ts +3 -181
- package/lib/localdb/rekordbox/anlz-parsers.d.ts +37 -0
- package/lib/localdb/rekordbox/entity-creators.d.ts +27 -0
- package/lib/localdb/rekordbox/hydrator.d.ts +20 -0
- package/lib/localdb/rekordbox/index.d.ts +13 -0
- package/lib/localdb/rekordbox/table-mappings.d.ts +10 -0
- package/lib/localdb/rekordbox/types.d.ts +118 -0
- package/lib/localdb/rekordbox.d.ts +6 -110
- package/lib/logger.d.ts +13 -0
- package/lib/metadata.d.ts +45 -0
- package/lib/network.d.ts +25 -0
- package/lib/nfs/index.d.ts +35 -10
- package/lib/nfs/index.test.d.ts +1 -0
- package/lib/nfs/programs.d.ts +10 -1
- package/lib/passive/index.d.ts +1 -1
- package/lib/passive/remotedb.d.ts +12 -1
- package/lib/remotedb/queries.d.ts +2 -2
- package/lib/types.d.ts +55 -78
- package/lib/types.js +21 -38
- package/lib/types.js.map +1 -1
- package/lib/utils/index.d.ts +9 -0
- package/lib/virtualcdj/index.d.ts +7 -1
- package/package.json +23 -2
package/lib/index.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
|
-
|
|
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
|
|
|
@@ -563,7 +568,7 @@ var PictureType;
|
|
|
563
568
|
"use strict";
|
|
564
569
|
|
|
565
570
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
566
|
-
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;
|
|
571
|
+
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;
|
|
567
572
|
/**
|
|
568
573
|
* The default virtual CDJ ID to use.
|
|
569
574
|
*
|
|
@@ -604,7 +609,13 @@ exports.VIRTUAL_CDJ_NAME = 'ProLink-Connect';
|
|
|
604
609
|
* VirtualCDJFirmware is a string indicating the firmware version reported with
|
|
605
610
|
* status packets.
|
|
606
611
|
*/
|
|
607
|
-
exports.VIRTUAL_CDJ_FIRMWARE = '
|
|
612
|
+
exports.VIRTUAL_CDJ_FIRMWARE = '3.20';
|
|
613
|
+
/**
|
|
614
|
+
* CDJs use device IDs 1-6. Devices outside this range (e.g. Stagehand at 154)
|
|
615
|
+
* should not be queried for media slots or databases as they may crash.
|
|
616
|
+
*/
|
|
617
|
+
exports.MIN_CDJ_DEVICE_ID = 1;
|
|
618
|
+
exports.MAX_CDJ_DEVICE_ID = 6;
|
|
608
619
|
|
|
609
620
|
|
|
610
621
|
/***/ },
|
|
@@ -722,37 +733,46 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
722
733
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
723
734
|
exports.viaFileExtraction = viaFileExtraction;
|
|
724
735
|
const artwork_1 = __webpack_require__(/*! src/artwork */ "./src/artwork/index.ts");
|
|
736
|
+
const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
|
|
725
737
|
const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
|
|
738
|
+
const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
|
|
726
739
|
const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
|
|
727
740
|
/**
|
|
728
741
|
* Extract artwork directly from an audio file via NFS.
|
|
729
742
|
*/
|
|
730
743
|
async function viaFileExtraction(device, opts) {
|
|
731
|
-
var _a, _b;
|
|
744
|
+
var _a, _b, _c;
|
|
732
745
|
const { trackSlot, track, span } = opts;
|
|
746
|
+
const logger = (_a = opts.logger) !== null && _a !== void 0 ? _a : logger_1.noopLogger;
|
|
733
747
|
if (trackSlot !== types_1.MediaSlot.USB &&
|
|
734
748
|
trackSlot !== types_1.MediaSlot.SD &&
|
|
735
749
|
trackSlot !== types_1.MediaSlot.RB) {
|
|
750
|
+
logger.debug(`[artwork-nfs] Skipping: unsupported slot ${(0, utils_1.getSlotName)(trackSlot)} (device ${device.name})`);
|
|
736
751
|
return null;
|
|
737
752
|
}
|
|
738
753
|
const slot = trackSlot;
|
|
739
754
|
if (!track.filePath) {
|
|
755
|
+
logger.debug('[artwork-nfs] Skipping: no filePath on track');
|
|
740
756
|
return null;
|
|
741
757
|
}
|
|
742
|
-
const extension = (
|
|
758
|
+
const extension = (_c = (_b = track.filePath.split('.').pop()) === null || _b === void 0 ? void 0 : _b.toLowerCase()) !== null && _c !== void 0 ? _c : '';
|
|
743
759
|
if (!(0, artwork_1.isArtworkExtractionSupported)(extension)) {
|
|
760
|
+
logger.debug(`[artwork-nfs] Skipping: unsupported extension ".${extension}" (${track.filePath})`);
|
|
744
761
|
return null;
|
|
745
762
|
}
|
|
763
|
+
logger.debug(`[artwork-nfs] Extracting from ${track.filePath} (slot=${(0, utils_1.getSlotName)(trackSlot)}, device=${device.name} @ ${device.ip.address})`);
|
|
746
764
|
const tx = span
|
|
747
765
|
? span.startChild({ op: 'getArtworkFromFile' })
|
|
748
766
|
: Telemetry.startTransaction({ name: 'getArtworkFromFile' });
|
|
749
767
|
try {
|
|
750
|
-
const artwork = await (0, artwork_1.extractArtworkFromDevice)(device, slot, track.filePath);
|
|
768
|
+
const artwork = await (0, artwork_1.extractArtworkFromDevice)(device, slot, track.filePath, logger);
|
|
751
769
|
if (!artwork) {
|
|
770
|
+
logger.debug('[artwork-nfs] No embedded artwork found in file');
|
|
752
771
|
tx.setData('result', 'no_artwork');
|
|
753
772
|
tx.finish();
|
|
754
773
|
return null;
|
|
755
774
|
}
|
|
775
|
+
logger.debug(`[artwork-nfs] Success: ${artwork.mimeType} (${artwork.data.length} bytes)`);
|
|
756
776
|
tx.setData('result', 'success');
|
|
757
777
|
tx.setData('mimeType', artwork.mimeType);
|
|
758
778
|
tx.setData('size', artwork.data.length);
|
|
@@ -760,6 +780,8 @@ async function viaFileExtraction(device, opts) {
|
|
|
760
780
|
return artwork.data;
|
|
761
781
|
}
|
|
762
782
|
catch (error) {
|
|
783
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
784
|
+
logger.warn(`[artwork-nfs] NFS extraction failed: ${msg}`);
|
|
763
785
|
tx.setData('result', 'error');
|
|
764
786
|
tx.finish();
|
|
765
787
|
Telemetry.captureException(error);
|
|
@@ -907,12 +929,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
907
929
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
908
930
|
exports.viaRemote = viaRemote;
|
|
909
931
|
exports.viaLocal = viaLocal;
|
|
932
|
+
const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
|
|
910
933
|
const nfs_1 = __webpack_require__(/*! src/nfs */ "./src/nfs/index.ts");
|
|
911
934
|
const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
|
|
912
935
|
const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
|
|
913
936
|
const CHUNK_SIZE = 8192; // Maximum allowed XDR read size
|
|
914
937
|
function viaRemote(_remote, _device, _opts) {
|
|
915
|
-
|
|
938
|
+
var _a;
|
|
939
|
+
const logger = (_a = _opts.logger) !== null && _a !== void 0 ? _a : logger_1.noopLogger;
|
|
940
|
+
logger.error('Getting a file from Rekordbox via ProDJ-Link is not yet supported.');
|
|
916
941
|
return null;
|
|
917
942
|
// const conn = await remote.get(deviceId);
|
|
918
943
|
// if (conn === null) {
|
|
@@ -934,7 +959,9 @@ function viaRemote(_remote, _device, _opts) {
|
|
|
934
959
|
// });
|
|
935
960
|
}
|
|
936
961
|
async function viaLocal(local, device, opts) {
|
|
962
|
+
var _a;
|
|
937
963
|
const { deviceId, trackSlot, track } = opts;
|
|
964
|
+
const logger = (_a = opts.logger) !== null && _a !== void 0 ? _a : logger_1.noopLogger;
|
|
938
965
|
if (trackSlot !== types_1.MediaSlot.USB && trackSlot !== types_1.MediaSlot.SD) {
|
|
939
966
|
throw new Error('Expected USB or SD or RB slot for remote database query');
|
|
940
967
|
}
|
|
@@ -948,7 +975,7 @@ async function viaLocal(local, device, opts) {
|
|
|
948
975
|
slot: trackSlot,
|
|
949
976
|
path: track.filePath,
|
|
950
977
|
onProgress: progress => {
|
|
951
|
-
|
|
978
|
+
logger.trace('%d %d', progress.read, progress.total);
|
|
952
979
|
},
|
|
953
980
|
chunkSize: CHUNK_SIZE,
|
|
954
981
|
});
|
|
@@ -988,24 +1015,41 @@ async function viaRemote(remote, opts) {
|
|
|
988
1015
|
trackType,
|
|
989
1016
|
menuTarget: remotedb_1.MenuTarget.Main,
|
|
990
1017
|
};
|
|
1018
|
+
const isUnanalyzed = trackType === types_1.TrackType.Unanalyzed || trackType === types_1.TrackType.AudioCD;
|
|
1019
|
+
const isStreaming = trackType === types_1.TrackType.Streaming;
|
|
1020
|
+
const skipLocalFileLookups = isUnanalyzed || isStreaming;
|
|
1021
|
+
// Unanalyzed tracks use GetGenericMetadata (reads ID3 tags from the audio file).
|
|
1022
|
+
// Streaming tracks (Beatport) use the regular GetMetadata query.
|
|
991
1023
|
const track = await conn.query({
|
|
992
1024
|
queryDescriptor,
|
|
993
|
-
query: remotedb_1.Query.GetMetadata,
|
|
994
|
-
args: { trackId },
|
|
995
|
-
span,
|
|
996
|
-
});
|
|
997
|
-
track.filePath = await conn.query({
|
|
998
|
-
queryDescriptor,
|
|
999
|
-
query: remotedb_1.Query.GetTrackInfo,
|
|
1000
|
-
args: { trackId },
|
|
1001
|
-
span,
|
|
1002
|
-
});
|
|
1003
|
-
track.beatGrid = await conn.query({
|
|
1004
|
-
queryDescriptor,
|
|
1005
|
-
query: remotedb_1.Query.GetBeatGrid,
|
|
1025
|
+
query: isUnanalyzed ? remotedb_1.Query.GetGenericMetadata : remotedb_1.Query.GetMetadata,
|
|
1006
1026
|
args: { trackId },
|
|
1007
1027
|
span,
|
|
1008
1028
|
});
|
|
1029
|
+
// Try to get file path — for streaming tracks this returns the Beatport track ID
|
|
1030
|
+
// (e.g. "/26883657.m4a") which we use for Beatport API lookups
|
|
1031
|
+
try {
|
|
1032
|
+
track.filePath = await conn.query({
|
|
1033
|
+
queryDescriptor,
|
|
1034
|
+
query: remotedb_1.Query.GetTrackInfo,
|
|
1035
|
+
args: { trackId },
|
|
1036
|
+
span,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
catch (err) {
|
|
1040
|
+
if (!skipLocalFileLookups) {
|
|
1041
|
+
throw err;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
// Beat grid is only available for analyzed local tracks
|
|
1045
|
+
if (!skipLocalFileLookups) {
|
|
1046
|
+
track.beatGrid = await conn.query({
|
|
1047
|
+
queryDescriptor,
|
|
1048
|
+
query: remotedb_1.Query.GetBeatGrid,
|
|
1049
|
+
args: { trackId },
|
|
1050
|
+
span,
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1009
1053
|
return track;
|
|
1010
1054
|
}
|
|
1011
1055
|
async function viaLocal(local, device, opts) {
|
|
@@ -1017,13 +1061,16 @@ async function viaLocal(local, device, opts) {
|
|
|
1017
1061
|
if (adapter === null) {
|
|
1018
1062
|
return null;
|
|
1019
1063
|
}
|
|
1020
|
-
const
|
|
1021
|
-
if (
|
|
1064
|
+
const dbTrack = adapter.findTrack(trackId);
|
|
1065
|
+
if (dbTrack === null) {
|
|
1022
1066
|
return null;
|
|
1023
1067
|
}
|
|
1024
|
-
const anlz = await (0, rekordbox_1.loadAnlz)(
|
|
1025
|
-
track
|
|
1026
|
-
|
|
1068
|
+
const anlz = await (0, rekordbox_1.loadAnlz)(dbTrack, 'DAT', (0, utils_1.anlzLoader)({ device, slot: trackSlot }));
|
|
1069
|
+
const track = {
|
|
1070
|
+
...dbTrack,
|
|
1071
|
+
beatGrid: anlz.beatGrid,
|
|
1072
|
+
waveformHd: null,
|
|
1073
|
+
};
|
|
1027
1074
|
return track;
|
|
1028
1075
|
}
|
|
1029
1076
|
|
|
@@ -1104,6 +1151,48 @@ async function viaLocal(local, opts) {
|
|
|
1104
1151
|
}
|
|
1105
1152
|
|
|
1106
1153
|
|
|
1154
|
+
/***/ },
|
|
1155
|
+
|
|
1156
|
+
/***/ "./src/db/getTrackAnalysis.ts"
|
|
1157
|
+
/*!************************************!*\
|
|
1158
|
+
!*** ./src/db/getTrackAnalysis.ts ***!
|
|
1159
|
+
\************************************/
|
|
1160
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
1161
|
+
|
|
1162
|
+
"use strict";
|
|
1163
|
+
|
|
1164
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1165
|
+
exports.viaLocal = viaLocal;
|
|
1166
|
+
const rekordbox_1 = __webpack_require__(/*! src/localdb/rekordbox */ "./src/localdb/rekordbox.ts");
|
|
1167
|
+
const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
|
|
1168
|
+
const utils_1 = __webpack_require__(/*! ./utils */ "./src/db/utils.ts");
|
|
1169
|
+
async function viaLocal(local, device, opts) {
|
|
1170
|
+
var _a, _b, _c, _d;
|
|
1171
|
+
const { deviceId, trackSlot, track } = opts;
|
|
1172
|
+
if (trackSlot !== types_1.MediaSlot.USB && trackSlot !== types_1.MediaSlot.SD) {
|
|
1173
|
+
throw new Error('Expected USB or SD slot for local database query');
|
|
1174
|
+
}
|
|
1175
|
+
const conn = await local.get(deviceId, trackSlot);
|
|
1176
|
+
if (conn === null) {
|
|
1177
|
+
return null;
|
|
1178
|
+
}
|
|
1179
|
+
const resolver = (0, utils_1.anlzLoader)({ device, slot: trackSlot });
|
|
1180
|
+
const [extResult, twoxResult] = await Promise.all([
|
|
1181
|
+
(0, rekordbox_1.loadAnlz)(track, 'EXT', resolver),
|
|
1182
|
+
(0, rekordbox_1.loadAnlz)(track, '2EX', resolver).catch(() => null),
|
|
1183
|
+
]);
|
|
1184
|
+
return {
|
|
1185
|
+
extendedCues: extResult.extendedCues,
|
|
1186
|
+
songStructure: extResult.songStructure,
|
|
1187
|
+
waveformColorPreview: (_a = extResult.waveformColorPreview) !== null && _a !== void 0 ? _a : undefined,
|
|
1188
|
+
waveformHd: extResult.waveformHd,
|
|
1189
|
+
waveform3BandPreview: (_b = twoxResult === null || twoxResult === void 0 ? void 0 : twoxResult.waveform3BandPreview) !== null && _b !== void 0 ? _b : null,
|
|
1190
|
+
waveform3BandDetail: (_c = twoxResult === null || twoxResult === void 0 ? void 0 : twoxResult.waveform3BandDetail) !== null && _c !== void 0 ? _c : null,
|
|
1191
|
+
vocalConfig: (_d = twoxResult === null || twoxResult === void 0 ? void 0 : twoxResult.vocalConfig) !== null && _d !== void 0 ? _d : null,
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
|
|
1107
1196
|
/***/ },
|
|
1108
1197
|
|
|
1109
1198
|
/***/ "./src/db/getWaveforms.ts"
|
|
@@ -1138,9 +1227,30 @@ async function viaRemote(remote, opts) {
|
|
|
1138
1227
|
args: { trackId: track.id },
|
|
1139
1228
|
span,
|
|
1140
1229
|
});
|
|
1141
|
-
|
|
1230
|
+
const isStreaming = trackType === types_1.TrackType.Streaming;
|
|
1231
|
+
if (!isStreaming) {
|
|
1232
|
+
return { waveformHd };
|
|
1233
|
+
}
|
|
1234
|
+
// Streaming tracks (e.g. Beatport LINK) have no local ANLZ file but the CDJ
|
|
1235
|
+
// serves waveform preview and detailed via remotedb.
|
|
1236
|
+
const [waveformPreview, waveformDetailed] = await Promise.all([
|
|
1237
|
+
conn.query({
|
|
1238
|
+
queryDescriptor,
|
|
1239
|
+
query: remotedb_1.Query.GetWaveformPreview,
|
|
1240
|
+
args: { trackId: track.id },
|
|
1241
|
+
span,
|
|
1242
|
+
}),
|
|
1243
|
+
conn.query({
|
|
1244
|
+
queryDescriptor,
|
|
1245
|
+
query: remotedb_1.Query.GetWaveformDetailed,
|
|
1246
|
+
args: { trackId: track.id },
|
|
1247
|
+
span,
|
|
1248
|
+
}),
|
|
1249
|
+
]);
|
|
1250
|
+
return { waveformHd, waveformPreview, waveformDetailed };
|
|
1142
1251
|
}
|
|
1143
1252
|
async function viaLocal(local, device, opts) {
|
|
1253
|
+
var _a;
|
|
1144
1254
|
const { deviceId, trackSlot, track } = opts;
|
|
1145
1255
|
if (trackSlot !== types_1.MediaSlot.USB && trackSlot !== types_1.MediaSlot.SD) {
|
|
1146
1256
|
throw new Error('Expected USB or SD slot for remote database query');
|
|
@@ -1150,7 +1260,10 @@ async function viaLocal(local, device, opts) {
|
|
|
1150
1260
|
return null;
|
|
1151
1261
|
}
|
|
1152
1262
|
const anlz = await (0, rekordbox_1.loadAnlz)(track, 'EXT', (0, utils_1.anlzLoader)({ device, slot: trackSlot }));
|
|
1153
|
-
return {
|
|
1263
|
+
return {
|
|
1264
|
+
waveformHd: anlz.waveformHd,
|
|
1265
|
+
waveformColorPreview: (_a = anlz.waveformColorPreview) !== null && _a !== void 0 ? _a : undefined,
|
|
1266
|
+
};
|
|
1154
1267
|
}
|
|
1155
1268
|
|
|
1156
1269
|
|
|
@@ -1208,7 +1321,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
1208
1321
|
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");
|
|
1209
1322
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
1210
1323
|
};
|
|
1211
|
-
var
|
|
1324
|
+
var _Database_deviceManager, _Database_localDatabase, _Database_remoteDatabase, _Database_getTrackLookupStrategy, _Database_getMediaLookupStrategy;
|
|
1212
1325
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1213
1326
|
const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
|
|
1214
1327
|
const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
|
|
@@ -1219,6 +1332,7 @@ const GetArtworkThumbnail = __importStar(__webpack_require__(/*! ./getArtworkThu
|
|
|
1219
1332
|
const GetFile = __importStar(__webpack_require__(/*! ./getFile */ "./src/db/getFile.ts"));
|
|
1220
1333
|
const GetMetadata = __importStar(__webpack_require__(/*! ./getMetadata */ "./src/db/getMetadata.ts"));
|
|
1221
1334
|
const GetPlaylist = __importStar(__webpack_require__(/*! ./getPlaylist */ "./src/db/getPlaylist.ts"));
|
|
1335
|
+
const GetTrackAnalysis = __importStar(__webpack_require__(/*! ./getTrackAnalysis */ "./src/db/getTrackAnalysis.ts"));
|
|
1222
1336
|
const GetWaveforms = __importStar(__webpack_require__(/*! ./getWaveforms */ "./src/db/getWaveforms.ts"));
|
|
1223
1337
|
var LookupStrategy;
|
|
1224
1338
|
(function (LookupStrategy) {
|
|
@@ -1231,8 +1345,7 @@ var LookupStrategy;
|
|
|
1231
1345
|
* network for information from their databases.
|
|
1232
1346
|
*/
|
|
1233
1347
|
class Database {
|
|
1234
|
-
constructor(
|
|
1235
|
-
_Database_hostDevice.set(this, void 0);
|
|
1348
|
+
constructor(local, remote, deviceManager) {
|
|
1236
1349
|
_Database_deviceManager.set(this, void 0);
|
|
1237
1350
|
/**
|
|
1238
1351
|
* The local database service, used when querying media devices connected
|
|
@@ -1246,8 +1359,13 @@ class Database {
|
|
|
1246
1359
|
_Database_remoteDatabase.set(this, void 0);
|
|
1247
1360
|
_Database_getTrackLookupStrategy.set(this, (device, type) => {
|
|
1248
1361
|
const isUnanalyzed = type === types_1.TrackType.AudioCD || type === types_1.TrackType.Unanalyzed;
|
|
1249
|
-
const
|
|
1250
|
-
|
|
1362
|
+
const isStreaming = type === types_1.TrackType.Streaming;
|
|
1363
|
+
// Unanalyzed and streaming tracks on CDJs must use RemoteDB
|
|
1364
|
+
// (streaming services like Beatport have no local database)
|
|
1365
|
+
if (device.type === types_1.DeviceType.CDJ && (isUnanalyzed || isStreaming)) {
|
|
1366
|
+
return LookupStrategy.Remote;
|
|
1367
|
+
}
|
|
1368
|
+
return device.type === types_1.DeviceType.Rekordbox
|
|
1251
1369
|
? LookupStrategy.Remote
|
|
1252
1370
|
: device.type === types_1.DeviceType.CDJ && type === types_1.TrackType.RB
|
|
1253
1371
|
? LookupStrategy.Local
|
|
@@ -1258,19 +1376,10 @@ class Database {
|
|
|
1258
1376
|
: device.type === types_1.DeviceType.Rekordbox
|
|
1259
1377
|
? LookupStrategy.NoneAvailable
|
|
1260
1378
|
: LookupStrategy.Local);
|
|
1261
|
-
__classPrivateFieldSet(this, _Database_hostDevice, hostDevice, "f");
|
|
1262
1379
|
__classPrivateFieldSet(this, _Database_localDatabase, local, "f");
|
|
1263
1380
|
__classPrivateFieldSet(this, _Database_remoteDatabase, remote, "f");
|
|
1264
1381
|
__classPrivateFieldSet(this, _Database_deviceManager, deviceManager, "f");
|
|
1265
1382
|
}
|
|
1266
|
-
/**
|
|
1267
|
-
* Reports weather or not the CDJs can be communicated to over the remote
|
|
1268
|
-
* database protocol. This is important when trying to query for unanalyzed or
|
|
1269
|
-
* compact disc tracks.
|
|
1270
|
-
*/
|
|
1271
|
-
get cdjSupportsRemotedb() {
|
|
1272
|
-
return __classPrivateFieldGet(this, _Database_hostDevice, "f").id > 0 && __classPrivateFieldGet(this, _Database_hostDevice, "f").id < 7;
|
|
1273
|
-
}
|
|
1274
1383
|
/**
|
|
1275
1384
|
* Get the database type (oneLibrary or pdb) for a loaded device slot.
|
|
1276
1385
|
* Returns null if the slot uses remote database or no database is loaded.
|
|
@@ -1431,6 +1540,35 @@ class Database {
|
|
|
1431
1540
|
tx.finish();
|
|
1432
1541
|
return waveforms;
|
|
1433
1542
|
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Retrieves all analysis data from the EXT file for a track.
|
|
1545
|
+
* Returns extended cues, song structure, waveform color preview, and HD waveform.
|
|
1546
|
+
*/
|
|
1547
|
+
async getTrackAnalysis(opts) {
|
|
1548
|
+
const { deviceId, trackType, trackSlot, span } = opts;
|
|
1549
|
+
const tx = span
|
|
1550
|
+
? span.startChild({ op: 'dbGetTrackAnalysis' })
|
|
1551
|
+
: Telemetry.startTransaction({ name: 'dbGetTrackAnalysis' });
|
|
1552
|
+
tx.setTag('deviceId', deviceId.toString());
|
|
1553
|
+
tx.setTag('trackType', (0, utils_1.getTrackTypeName)(trackType));
|
|
1554
|
+
tx.setTag('trackSlot', (0, utils_1.getSlotName)(trackSlot));
|
|
1555
|
+
const callOpts = { ...opts, span: tx };
|
|
1556
|
+
const device = await __classPrivateFieldGet(this, _Database_deviceManager, "f").getDeviceEnsured(deviceId);
|
|
1557
|
+
if (device === null) {
|
|
1558
|
+
tx.finish();
|
|
1559
|
+
return null;
|
|
1560
|
+
}
|
|
1561
|
+
const strategy = __classPrivateFieldGet(this, _Database_getTrackLookupStrategy, "f").call(this, device, trackType);
|
|
1562
|
+
let analysis = null;
|
|
1563
|
+
if (strategy === LookupStrategy.Local) {
|
|
1564
|
+
analysis = await GetTrackAnalysis.viaLocal(__classPrivateFieldGet(this, _Database_localDatabase, "f"), device, callOpts);
|
|
1565
|
+
}
|
|
1566
|
+
if (strategy === LookupStrategy.NoneAvailable || strategy === LookupStrategy.Remote) {
|
|
1567
|
+
tx.setStatus(telemetry_1.SpanStatus.Unavailable);
|
|
1568
|
+
}
|
|
1569
|
+
tx.finish();
|
|
1570
|
+
return analysis;
|
|
1571
|
+
}
|
|
1434
1572
|
/**
|
|
1435
1573
|
* Retrieve folders, playlists, and tracks within the playlist tree. The id
|
|
1436
1574
|
* may be left undefined to query the root of the playlist tree.
|
|
@@ -1465,7 +1603,7 @@ class Database {
|
|
|
1465
1603
|
return contents;
|
|
1466
1604
|
}
|
|
1467
1605
|
}
|
|
1468
|
-
|
|
1606
|
+
_Database_deviceManager = new WeakMap(), _Database_localDatabase = new WeakMap(), _Database_remoteDatabase = new WeakMap(), _Database_getTrackLookupStrategy = new WeakMap(), _Database_getMediaLookupStrategy = new WeakMap();
|
|
1469
1607
|
exports["default"] = Database;
|
|
1470
1608
|
|
|
1471
1609
|
|
|
@@ -1696,21 +1834,18 @@ function deviceFromPacket(packet) {
|
|
|
1696
1834
|
/*!*************************!*\
|
|
1697
1835
|
!*** ./src/entities.ts ***!
|
|
1698
1836
|
\*************************/
|
|
1699
|
-
(__unused_webpack_module, exports) {
|
|
1837
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
1700
1838
|
|
|
1701
1839
|
"use strict";
|
|
1702
1840
|
|
|
1703
1841
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1704
1842
|
exports.EntityFK = void 0;
|
|
1705
1843
|
/**
|
|
1706
|
-
*
|
|
1707
|
-
*
|
|
1844
|
+
* Re-export entity types from onelibrary-connect.
|
|
1845
|
+
* These types represent what is stored in the rekordbox database.
|
|
1708
1846
|
*/
|
|
1709
|
-
var
|
|
1710
|
-
(function (
|
|
1711
|
-
EntityFK[EntityFK["WithFKs"] = 0] = "WithFKs";
|
|
1712
|
-
EntityFK[EntityFK["WithRelations"] = 1] = "WithRelations";
|
|
1713
|
-
})(EntityFK || (exports.EntityFK = EntityFK = {}));
|
|
1847
|
+
var onelibrary_connect_1 = __webpack_require__(/*! onelibrary-connect */ "onelibrary-connect");
|
|
1848
|
+
Object.defineProperty(exports, "EntityFK", ({ enumerable: true, get: function () { return onelibrary_connect_1.EntityFK; } }));
|
|
1714
1849
|
|
|
1715
1850
|
|
|
1716
1851
|
/***/ },
|
|
@@ -1741,7 +1876,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
1741
1876
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
1742
1877
|
};
|
|
1743
1878
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1744
|
-
exports.OneLibraryAdapter = exports.PictureType = exports.isArtworkExtractionSupported = exports.extractArtworkFromDevice = exports.extractArtwork = exports.PositionEmitter = void 0;
|
|
1879
|
+
exports.noopLogger = exports.OneLibraryAdapter = exports.fetchFile = exports.loadAnlz = exports.isMetadataExtractionSupported = exports.extractMetadataFromDevice = exports.extractFullMetadata = exports.PictureType = exports.isArtworkExtractionSupported = exports.extractArtworkFromDevice = exports.extractArtwork = exports.PositionEmitter = void 0;
|
|
1745
1880
|
__exportStar(__webpack_require__(/*! ./entities */ "./src/entities.ts"), exports);
|
|
1746
1881
|
__exportStar(__webpack_require__(/*! ./mixstatus */ "./src/mixstatus/index.ts"), exports);
|
|
1747
1882
|
__exportStar(__webpack_require__(/*! ./network */ "./src/network.ts"), exports);
|
|
@@ -1755,8 +1890,21 @@ Object.defineProperty(exports, "extractArtwork", ({ enumerable: true, get: funct
|
|
|
1755
1890
|
Object.defineProperty(exports, "extractArtworkFromDevice", ({ enumerable: true, get: function () { return artwork_1.extractArtworkFromDevice; } }));
|
|
1756
1891
|
Object.defineProperty(exports, "isArtworkExtractionSupported", ({ enumerable: true, get: function () { return artwork_1.isArtworkExtractionSupported; } }));
|
|
1757
1892
|
Object.defineProperty(exports, "PictureType", ({ enumerable: true, get: function () { return artwork_1.PictureType; } }));
|
|
1758
|
-
|
|
1759
|
-
|
|
1893
|
+
// Full metadata extraction (title, artist, album, BPM, key, genre, artwork)
|
|
1894
|
+
var metadata_1 = __webpack_require__(/*! ./metadata */ "./src/metadata.ts");
|
|
1895
|
+
Object.defineProperty(exports, "extractFullMetadata", ({ enumerable: true, get: function () { return metadata_1.extractFullMetadata; } }));
|
|
1896
|
+
Object.defineProperty(exports, "extractMetadataFromDevice", ({ enumerable: true, get: function () { return metadata_1.extractMetadataFromDevice; } }));
|
|
1897
|
+
Object.defineProperty(exports, "isMetadataExtractionSupported", ({ enumerable: true, get: function () { return metadata_1.isMetadataExtractionSupported; } }));
|
|
1898
|
+
// ANLZ file loading (for analysis data: beat grid, cues, phrases, waveforms)
|
|
1899
|
+
var rekordbox_1 = __webpack_require__(/*! ./localdb/rekordbox */ "./src/localdb/rekordbox.ts");
|
|
1900
|
+
Object.defineProperty(exports, "loadAnlz", ({ enumerable: true, get: function () { return rekordbox_1.loadAnlz; } }));
|
|
1901
|
+
var nfs_1 = __webpack_require__(/*! ./nfs */ "./src/nfs/index.ts");
|
|
1902
|
+
Object.defineProperty(exports, "fetchFile", ({ enumerable: true, get: function () { return nfs_1.fetchFile; } }));
|
|
1903
|
+
var onelibrary_connect_1 = __webpack_require__(/*! onelibrary-connect */ "onelibrary-connect");
|
|
1904
|
+
Object.defineProperty(exports, "OneLibraryAdapter", ({ enumerable: true, get: function () { return onelibrary_connect_1.OneLibraryAdapter; } }));
|
|
1905
|
+
// Logger interface for pluggable logging
|
|
1906
|
+
var logger_1 = __webpack_require__(/*! ./logger */ "./src/logger.ts");
|
|
1907
|
+
Object.defineProperty(exports, "noopLogger", ({ enumerable: true, get: function () { return logger_1.noopLogger; } }));
|
|
1760
1908
|
// Types are exported last to avoid overwriting values with type-only exports
|
|
1761
1909
|
__exportStar(__webpack_require__(/*! ./types */ "./src/types.ts"), exports);
|
|
1762
1910
|
|
|
@@ -1818,16 +1966,17 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
1818
1966
|
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;
|
|
1819
1967
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
1820
1968
|
const async_mutex_1 = __webpack_require__(/*! async-mutex */ "async-mutex");
|
|
1969
|
+
const onelibrary_connect_1 = __webpack_require__(/*! onelibrary-connect */ "onelibrary-connect");
|
|
1821
1970
|
const crypto_1 = __webpack_require__(/*! crypto */ "crypto");
|
|
1822
1971
|
const events_1 = __webpack_require__(/*! events */ "events");
|
|
1823
1972
|
const fs = __importStar(__webpack_require__(/*! fs */ "fs"));
|
|
1824
1973
|
const os = __importStar(__webpack_require__(/*! os */ "os"));
|
|
1825
1974
|
const path = __importStar(__webpack_require__(/*! path */ "path"));
|
|
1975
|
+
const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
|
|
1826
1976
|
const nfs_1 = __webpack_require__(/*! src/nfs */ "./src/nfs/index.ts");
|
|
1827
1977
|
const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
|
|
1828
1978
|
const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
|
|
1829
1979
|
const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
|
|
1830
|
-
const onelibrary_1 = __webpack_require__(/*! ./onelibrary */ "./src/localdb/onelibrary.ts");
|
|
1831
1980
|
const orm_1 = __webpack_require__(/*! ./orm */ "./src/localdb/orm.ts");
|
|
1832
1981
|
const rekordbox_1 = __webpack_require__(/*! ./rekordbox */ "./src/localdb/rekordbox.ts");
|
|
1833
1982
|
/**
|
|
@@ -1904,7 +2053,9 @@ class LocalDatabase {
|
|
|
1904
2053
|
* Helper to fetch a file from device, trying both dotted and non-dotted paths
|
|
1905
2054
|
*/
|
|
1906
2055
|
_LocalDatabase_fetchFileWithFallback.set(this, async (device, slot, basePath, tx) => {
|
|
1907
|
-
const attemptOrder = process.platform === 'win32'
|
|
2056
|
+
const attemptOrder = process.platform === 'win32'
|
|
2057
|
+
? [basePath, `.${basePath}`]
|
|
2058
|
+
: [`.${basePath}`, basePath];
|
|
1908
2059
|
try {
|
|
1909
2060
|
return await (0, nfs_1.fetchFile)({
|
|
1910
2061
|
device,
|
|
@@ -1915,7 +2066,7 @@ class LocalDatabase {
|
|
|
1915
2066
|
});
|
|
1916
2067
|
}
|
|
1917
2068
|
catch {
|
|
1918
|
-
return
|
|
2069
|
+
return (0, nfs_1.fetchFile)({
|
|
1919
2070
|
device,
|
|
1920
2071
|
slot,
|
|
1921
2072
|
path: attemptOrder[1],
|
|
@@ -1936,7 +2087,7 @@ class LocalDatabase {
|
|
|
1936
2087
|
const tempDir = os.tmpdir();
|
|
1937
2088
|
const tempFile = path.join(tempDir, `prolink-onelibrary-${device.id}-${slot}-${Date.now()}.db`);
|
|
1938
2089
|
fs.writeFileSync(tempFile, dbData);
|
|
1939
|
-
const adapter = new
|
|
2090
|
+
const adapter = new onelibrary_connect_1.OneLibraryAdapter(tempFile);
|
|
1940
2091
|
return { adapter, tempFile };
|
|
1941
2092
|
}
|
|
1942
2093
|
catch {
|
|
@@ -2049,8 +2200,10 @@ class LocalDatabase {
|
|
|
2049
2200
|
if (device === undefined) {
|
|
2050
2201
|
return null;
|
|
2051
2202
|
}
|
|
2052
|
-
if (device.type !== types_1.DeviceType.CDJ
|
|
2053
|
-
|
|
2203
|
+
if (device.type !== types_1.DeviceType.CDJ ||
|
|
2204
|
+
device.id < constants_1.MIN_CDJ_DEVICE_ID ||
|
|
2205
|
+
device.id > constants_1.MAX_CDJ_DEVICE_ID) {
|
|
2206
|
+
return null;
|
|
2054
2207
|
}
|
|
2055
2208
|
let media;
|
|
2056
2209
|
try {
|
|
@@ -2093,7 +2246,9 @@ class LocalDatabase {
|
|
|
2093
2246
|
*/
|
|
2094
2247
|
async preload() {
|
|
2095
2248
|
const allDevices = [...__classPrivateFieldGet(this, _LocalDatabase_deviceManager, "f").devices.values()];
|
|
2096
|
-
const cdjDevices = allDevices.filter(device => device.type === types_1.DeviceType.CDJ
|
|
2249
|
+
const cdjDevices = allDevices.filter(device => device.type === types_1.DeviceType.CDJ &&
|
|
2250
|
+
device.id >= constants_1.MIN_CDJ_DEVICE_ID &&
|
|
2251
|
+
device.id <= constants_1.MAX_CDJ_DEVICE_ID);
|
|
2097
2252
|
if (cdjDevices.length === 0) {
|
|
2098
2253
|
return;
|
|
2099
2254
|
}
|
|
@@ -2214,6 +2369,9 @@ var RekordboxAnlz = (function() {
|
|
|
2214
2369
|
WAVE_SCROLL: 1347900979,
|
|
2215
2370
|
WAVE_COLOR_PREVIEW: 1347900980,
|
|
2216
2371
|
WAVE_COLOR_SCROLL: 1347900981,
|
|
2372
|
+
WAVE_COLOR_3BAND_PREVIEW: 1347900982,
|
|
2373
|
+
WAVE_COLOR_3BAND_DETAIL: 1347900983,
|
|
2374
|
+
VOCAL_CONFIG: 1347900995,
|
|
2217
2375
|
|
|
2218
2376
|
1346588466: "CUES_2",
|
|
2219
2377
|
1346588482: "CUES",
|
|
@@ -2226,6 +2384,9 @@ var RekordboxAnlz = (function() {
|
|
|
2226
2384
|
1347900979: "WAVE_SCROLL",
|
|
2227
2385
|
1347900980: "WAVE_COLOR_PREVIEW",
|
|
2228
2386
|
1347900981: "WAVE_COLOR_SCROLL",
|
|
2387
|
+
1347900982: "WAVE_COLOR_3BAND_PREVIEW",
|
|
2388
|
+
1347900983: "WAVE_COLOR_3BAND_DETAIL",
|
|
2389
|
+
1347900995: "VOCAL_CONFIG",
|
|
2229
2390
|
});
|
|
2230
2391
|
|
|
2231
2392
|
RekordboxAnlz.TrackMood = Object.freeze({
|
|
@@ -2347,6 +2508,39 @@ var RekordboxAnlz = (function() {
|
|
|
2347
2508
|
return PathTag;
|
|
2348
2509
|
})();
|
|
2349
2510
|
|
|
2511
|
+
/**
|
|
2512
|
+
* A 3-band color waveform preview (PWV6, found in .2EX files).
|
|
2513
|
+
* Same resolution as PWV4 (typically 1200 entries) but with 3 bytes
|
|
2514
|
+
* per entry representing low, mid, and high frequency band amplitudes.
|
|
2515
|
+
* Layout is identical to wave_color_preview_tag (PWV4).
|
|
2516
|
+
* @see {@link https://djl-analysis.deepsymmetry.org/djl-analysis/track-metadata.html#color-3band-preview-waveform|Source}
|
|
2517
|
+
*/
|
|
2518
|
+
|
|
2519
|
+
var WaveColor3bandPreviewTag = RekordboxAnlz.WaveColor3bandPreviewTag = (function() {
|
|
2520
|
+
function WaveColor3bandPreviewTag(_io, _parent, _root) {
|
|
2521
|
+
this._io = _io;
|
|
2522
|
+
this._parent = _parent;
|
|
2523
|
+
this._root = _root || this;
|
|
2524
|
+
|
|
2525
|
+
this._read();
|
|
2526
|
+
}
|
|
2527
|
+
WaveColor3bandPreviewTag.prototype._read = function() {
|
|
2528
|
+
this.lenEntryBytes = this._io.readU4be();
|
|
2529
|
+
this.lenEntries = this._io.readU4be();
|
|
2530
|
+
this.entries = this._io.readBytes((this.lenEntries * this.lenEntryBytes));
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
/**
|
|
2534
|
+
* The size of each entry, in bytes. Always 3 (low, mid, high).
|
|
2535
|
+
*/
|
|
2536
|
+
|
|
2537
|
+
/**
|
|
2538
|
+
* The number of waveform data points. Typically 1200.
|
|
2539
|
+
*/
|
|
2540
|
+
|
|
2541
|
+
return WaveColor3bandPreviewTag;
|
|
2542
|
+
})();
|
|
2543
|
+
|
|
2350
2544
|
/**
|
|
2351
2545
|
* Stores a waveform preview image suitable for display above
|
|
2352
2546
|
* the touch strip for jumping to a track position.
|
|
@@ -2492,6 +2686,76 @@ var RekordboxAnlz = (function() {
|
|
|
2492
2686
|
return WaveColorPreviewTag;
|
|
2493
2687
|
})();
|
|
2494
2688
|
|
|
2689
|
+
/**
|
|
2690
|
+
* A 3-band color detail waveform (PWV7, found in .2EX files).
|
|
2691
|
+
* Higher resolution than PWV6. Each entry is 3 bytes (low, mid, high).
|
|
2692
|
+
* Layout is identical to wave_color_scroll_tag (PWV5).
|
|
2693
|
+
* @see {@link https://djl-analysis.deepsymmetry.org/djl-analysis/track-metadata.html#color-3band-detail-waveform|Source}
|
|
2694
|
+
*/
|
|
2695
|
+
|
|
2696
|
+
var WaveColor3bandDetailTag = RekordboxAnlz.WaveColor3bandDetailTag = (function() {
|
|
2697
|
+
function WaveColor3bandDetailTag(_io, _parent, _root) {
|
|
2698
|
+
this._io = _io;
|
|
2699
|
+
this._parent = _parent;
|
|
2700
|
+
this._root = _root || this;
|
|
2701
|
+
|
|
2702
|
+
this._read();
|
|
2703
|
+
}
|
|
2704
|
+
WaveColor3bandDetailTag.prototype._read = function() {
|
|
2705
|
+
this.lenEntryBytes = this._io.readU4be();
|
|
2706
|
+
this.lenEntries = this._io.readU4be();
|
|
2707
|
+
this._unnamed2 = this._io.readU4be();
|
|
2708
|
+
this.entries = this._io.readBytes((this.lenEntries * this.lenEntryBytes));
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
/**
|
|
2712
|
+
* The size of each entry, in bytes. Always 3 (low, mid, high).
|
|
2713
|
+
*/
|
|
2714
|
+
|
|
2715
|
+
/**
|
|
2716
|
+
* The number of columns of waveform data.
|
|
2717
|
+
*/
|
|
2718
|
+
|
|
2719
|
+
return WaveColor3bandDetailTag;
|
|
2720
|
+
})();
|
|
2721
|
+
|
|
2722
|
+
/**
|
|
2723
|
+
* Vocal detection configuration (PWVC, found in .2EX files).
|
|
2724
|
+
* Contains threshold values used to classify frequency content
|
|
2725
|
+
* as vocal or non-vocal. Body is 8 bytes: unknown(2) + 3 thresholds(2 each).
|
|
2726
|
+
* Observed threshold ranges: low 80-114, mid 80-146, high 98-159.
|
|
2727
|
+
*/
|
|
2728
|
+
|
|
2729
|
+
var VocalConfigTag = RekordboxAnlz.VocalConfigTag = (function() {
|
|
2730
|
+
function VocalConfigTag(_io, _parent, _root) {
|
|
2731
|
+
this._io = _io;
|
|
2732
|
+
this._parent = _parent;
|
|
2733
|
+
this._root = _root || this;
|
|
2734
|
+
|
|
2735
|
+
this._read();
|
|
2736
|
+
}
|
|
2737
|
+
VocalConfigTag.prototype._read = function() {
|
|
2738
|
+
this._unnamed0 = this._io.readU2be();
|
|
2739
|
+
this.thresholdLow = this._io.readU2be();
|
|
2740
|
+
this.thresholdMid = this._io.readU2be();
|
|
2741
|
+
this.thresholdHigh = this._io.readU2be();
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
/**
|
|
2745
|
+
* Low frequency vocal detection threshold. Observed range: 80-114.
|
|
2746
|
+
*/
|
|
2747
|
+
|
|
2748
|
+
/**
|
|
2749
|
+
* Mid frequency vocal detection threshold. Observed range: 80-146.
|
|
2750
|
+
*/
|
|
2751
|
+
|
|
2752
|
+
/**
|
|
2753
|
+
* High frequency vocal detection threshold. Observed range: 98-159.
|
|
2754
|
+
*/
|
|
2755
|
+
|
|
2756
|
+
return VocalConfigTag;
|
|
2757
|
+
})();
|
|
2758
|
+
|
|
2495
2759
|
var PhraseHigh = RekordboxAnlz.PhraseHigh = (function() {
|
|
2496
2760
|
function PhraseHigh(_io, _parent, _root) {
|
|
2497
2761
|
this._io = _io;
|
|
@@ -2999,6 +3263,11 @@ var RekordboxAnlz = (function() {
|
|
|
2999
3263
|
var _io__raw_body = new KaitaiStream(this._raw_body);
|
|
3000
3264
|
this.body = new BeatGridTag(_io__raw_body, this, this._root);
|
|
3001
3265
|
break;
|
|
3266
|
+
case RekordboxAnlz.SectionTags.VOCAL_CONFIG:
|
|
3267
|
+
this._raw_body = this._io.readBytes((this.lenTag - 12));
|
|
3268
|
+
var _io__raw_body = new KaitaiStream(this._raw_body);
|
|
3269
|
+
this.body = new VocalConfigTag(_io__raw_body, this, this._root);
|
|
3270
|
+
break;
|
|
3002
3271
|
case RekordboxAnlz.SectionTags.WAVE_PREVIEW:
|
|
3003
3272
|
this._raw_body = this._io.readBytes((this.lenTag - 12));
|
|
3004
3273
|
var _io__raw_body = new KaitaiStream(this._raw_body);
|
|
@@ -3019,6 +3288,16 @@ var RekordboxAnlz = (function() {
|
|
|
3019
3288
|
var _io__raw_body = new KaitaiStream(this._raw_body);
|
|
3020
3289
|
this.body = new WavePreviewTag(_io__raw_body, this, this._root);
|
|
3021
3290
|
break;
|
|
3291
|
+
case RekordboxAnlz.SectionTags.WAVE_COLOR_3BAND_DETAIL:
|
|
3292
|
+
this._raw_body = this._io.readBytes((this.lenTag - 12));
|
|
3293
|
+
var _io__raw_body = new KaitaiStream(this._raw_body);
|
|
3294
|
+
this.body = new WaveColor3bandDetailTag(_io__raw_body, this, this._root);
|
|
3295
|
+
break;
|
|
3296
|
+
case RekordboxAnlz.SectionTags.WAVE_COLOR_3BAND_PREVIEW:
|
|
3297
|
+
this._raw_body = this._io.readBytes((this.lenTag - 12));
|
|
3298
|
+
var _io__raw_body = new KaitaiStream(this._raw_body);
|
|
3299
|
+
this.body = new WaveColor3bandPreviewTag(_io__raw_body, this, this._root);
|
|
3300
|
+
break;
|
|
3022
3301
|
default:
|
|
3023
3302
|
this._raw_body = this._io.readBytes((this.lenTag - 12));
|
|
3024
3303
|
var _io__raw_body = new KaitaiStream(this._raw_body);
|
|
@@ -4834,141 +5113,37 @@ return RekordboxPdb;
|
|
|
4834
5113
|
|
|
4835
5114
|
/***/ },
|
|
4836
5115
|
|
|
4837
|
-
/***/ "./src/localdb/onelibrary
|
|
4838
|
-
|
|
4839
|
-
!*** ./src/localdb/onelibrary
|
|
4840
|
-
|
|
4841
|
-
(__unused_webpack_module, exports) {
|
|
5116
|
+
/***/ "./src/localdb/onelibrary.ts"
|
|
5117
|
+
/*!***********************************!*\
|
|
5118
|
+
!*** ./src/localdb/onelibrary.ts ***!
|
|
5119
|
+
\***********************************/
|
|
5120
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
4842
5121
|
|
|
4843
5122
|
"use strict";
|
|
4844
5123
|
|
|
4845
5124
|
/**
|
|
4846
|
-
*
|
|
4847
|
-
*
|
|
4848
|
-
* These match the actual column names in the SQLite database,
|
|
4849
|
-
* which differ from the legacy PDB format.
|
|
5125
|
+
* OneLibrary Database Adapter
|
|
4850
5126
|
*
|
|
4851
|
-
*
|
|
5127
|
+
* Re-exports from onelibrary-connect for backward compatibility.
|
|
4852
5128
|
*/
|
|
4853
5129
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
4854
|
-
exports.
|
|
4855
|
-
|
|
4856
|
-
|
|
4857
|
-
|
|
4858
|
-
|
|
4859
|
-
* Cue point types based on the 'kind' field in the cue table.
|
|
4860
|
-
*
|
|
4861
|
-
* Note: These values may vary. Verify with actual data.
|
|
4862
|
-
*/
|
|
4863
|
-
exports.CueKind = {
|
|
4864
|
-
MEMORY_CUE: 0,
|
|
4865
|
-
HOT_CUE: 1,
|
|
4866
|
-
// Additional types may exist
|
|
4867
|
-
};
|
|
4868
|
-
/**
|
|
4869
|
-
* Playlist attribute types based on the 'attribute' field.
|
|
4870
|
-
*/
|
|
4871
|
-
exports.PlaylistAttribute = {
|
|
4872
|
-
PLAYLIST: 0,
|
|
4873
|
-
FOLDER: 1,
|
|
4874
|
-
};
|
|
4875
|
-
/**
|
|
4876
|
-
* MyTag attribute types based on the 'attribute' field.
|
|
4877
|
-
*/
|
|
4878
|
-
exports.MyTagAttribute = {
|
|
4879
|
-
TAG: 0,
|
|
4880
|
-
FOLDER: 1,
|
|
4881
|
-
};
|
|
4882
|
-
// ============================================================================
|
|
4883
|
-
// Menu Item Kind Constants
|
|
4884
|
-
// ============================================================================
|
|
4885
|
-
/**
|
|
4886
|
-
* Menu item kinds for browsing categories.
|
|
4887
|
-
* These match the 'kind' field in the menuItem table.
|
|
4888
|
-
*/
|
|
4889
|
-
exports.MenuItemKind = {
|
|
4890
|
-
GENRE: 128,
|
|
4891
|
-
ARTIST: 129,
|
|
4892
|
-
ALBUM: 130,
|
|
4893
|
-
TRACK: 131,
|
|
4894
|
-
PLAYLIST: 132,
|
|
4895
|
-
BPM: 133,
|
|
4896
|
-
RATING: 134,
|
|
4897
|
-
YEAR: 135,
|
|
4898
|
-
REMIXER: 136,
|
|
4899
|
-
LABEL: 137,
|
|
4900
|
-
ORIGINAL_ARTIST: 138,
|
|
4901
|
-
KEY: 139,
|
|
4902
|
-
DATE_ADDED: 140,
|
|
4903
|
-
CUE: 141,
|
|
4904
|
-
COLOR: 142,
|
|
4905
|
-
FOLDER: 144,
|
|
4906
|
-
SEARCH: 145,
|
|
4907
|
-
TIME: 146,
|
|
4908
|
-
BITRATE: 147,
|
|
4909
|
-
FILE_NAME: 148,
|
|
4910
|
-
HISTORY: 149,
|
|
4911
|
-
COMMENTS: 150,
|
|
4912
|
-
DJ_PLAY_COUNT: 151,
|
|
4913
|
-
HOT_CUE_BANK: 152,
|
|
4914
|
-
DEFAULT: 161,
|
|
4915
|
-
ALPHABET: 162,
|
|
4916
|
-
MATCHING: 170,
|
|
4917
|
-
};
|
|
5130
|
+
exports.openOneLibraryDb = exports.OneLibraryAdapter = exports.getEncryptionKey = void 0;
|
|
5131
|
+
var onelibrary_connect_1 = __webpack_require__(/*! onelibrary-connect */ "onelibrary-connect");
|
|
5132
|
+
Object.defineProperty(exports, "getEncryptionKey", ({ enumerable: true, get: function () { return onelibrary_connect_1.getEncryptionKey; } }));
|
|
5133
|
+
Object.defineProperty(exports, "OneLibraryAdapter", ({ enumerable: true, get: function () { return onelibrary_connect_1.OneLibraryAdapter; } }));
|
|
5134
|
+
Object.defineProperty(exports, "openOneLibraryDb", ({ enumerable: true, get: function () { return onelibrary_connect_1.openOneLibraryDb; } }));
|
|
4918
5135
|
|
|
4919
5136
|
|
|
4920
5137
|
/***/ },
|
|
4921
5138
|
|
|
4922
|
-
/***/ "./src/localdb/
|
|
4923
|
-
|
|
4924
|
-
!*** ./src/localdb/
|
|
4925
|
-
|
|
5139
|
+
/***/ "./src/localdb/orm.ts"
|
|
5140
|
+
/*!****************************!*\
|
|
5141
|
+
!*** ./src/localdb/orm.ts ***!
|
|
5142
|
+
\****************************/
|
|
4926
5143
|
(__unused_webpack_module, exports, __webpack_require__) {
|
|
4927
5144
|
|
|
4928
5145
|
"use strict";
|
|
4929
5146
|
|
|
4930
|
-
/**
|
|
4931
|
-
* OneLibrary Database Adapter
|
|
4932
|
-
*
|
|
4933
|
-
* Provides an interface for reading the OneLibrary (exportLibrary.db) SQLite database
|
|
4934
|
-
* used by modern rekordbox versions and Pioneer DJ devices.
|
|
4935
|
-
*
|
|
4936
|
-
* The database is encrypted with SQLCipher 4. The encryption key is derived from
|
|
4937
|
-
* a hardcoded obfuscated blob.
|
|
4938
|
-
*/
|
|
4939
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4940
|
-
if (k2 === undefined) k2 = k;
|
|
4941
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
4942
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
4943
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
4944
|
-
}
|
|
4945
|
-
Object.defineProperty(o, k2, desc);
|
|
4946
|
-
}) : (function(o, m, k, k2) {
|
|
4947
|
-
if (k2 === undefined) k2 = k;
|
|
4948
|
-
o[k2] = m[k];
|
|
4949
|
-
}));
|
|
4950
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
4951
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
4952
|
-
}) : function(o, v) {
|
|
4953
|
-
o["default"] = v;
|
|
4954
|
-
});
|
|
4955
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
4956
|
-
var ownKeys = function(o) {
|
|
4957
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
4958
|
-
var ar = [];
|
|
4959
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
4960
|
-
return ar;
|
|
4961
|
-
};
|
|
4962
|
-
return ownKeys(o);
|
|
4963
|
-
};
|
|
4964
|
-
return function (mod) {
|
|
4965
|
-
if (mod && mod.__esModule) return mod;
|
|
4966
|
-
var result = {};
|
|
4967
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
4968
|
-
__setModuleDefault(result, mod);
|
|
4969
|
-
return result;
|
|
4970
|
-
};
|
|
4971
|
-
})();
|
|
4972
5147
|
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
4973
5148
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4974
5149
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
@@ -4983,673 +5158,14 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
4983
5158
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4984
5159
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4985
5160
|
};
|
|
4986
|
-
var
|
|
5161
|
+
var _MetadataORM_conn, _MetadataORM_stmtCache, _MetadataORM_inTransaction;
|
|
4987
5162
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
4988
|
-
exports.
|
|
4989
|
-
exports.getEncryptionKey = getEncryptionKey;
|
|
4990
|
-
exports.openOneLibraryDb = openOneLibraryDb;
|
|
5163
|
+
exports.MetadataORM = exports.Table = void 0;
|
|
4991
5164
|
const better_sqlite3_multiple_ciphers_1 = __importDefault(__webpack_require__(/*! better-sqlite3-multiple-ciphers */ "better-sqlite3-multiple-ciphers"));
|
|
4992
|
-
const
|
|
4993
|
-
const
|
|
4994
|
-
// ============================================================================
|
|
4995
|
-
// Encryption Key Derivation
|
|
4996
|
-
// ============================================================================
|
|
4997
|
-
/**
|
|
4998
|
-
* The obfuscated encryption key blob from pyrekordbox
|
|
4999
|
-
*/
|
|
5000
|
-
const BLOB = Buffer.from('PN_1dH8$oLJY)16j_RvM6qphWw`476>;C1cWmI#se(PG`j}~xAjlufj?`#0i{;=glh(SkW)y0>n?YEiD`l%t(', 'ascii');
|
|
5001
|
-
/**
|
|
5002
|
-
* XOR key used for deobfuscation
|
|
5003
|
-
*/
|
|
5004
|
-
const BLOB_KEY = Buffer.from('657f48f84c437cc1', 'ascii');
|
|
5005
|
-
/**
|
|
5006
|
-
* Base85 (RFC 1924) decode
|
|
5007
|
-
*/
|
|
5008
|
-
function base85Decode(input) {
|
|
5009
|
-
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~';
|
|
5010
|
-
const charToValue = new Map();
|
|
5011
|
-
for (let i = 0; i < alphabet.length; i++) {
|
|
5012
|
-
charToValue.set(alphabet[i], i);
|
|
5013
|
-
}
|
|
5014
|
-
const inputStr = input.toString('ascii');
|
|
5015
|
-
const result = [];
|
|
5016
|
-
for (let i = 0; i < inputStr.length; i += 5) {
|
|
5017
|
-
const chunk = inputStr.slice(i, i + 5);
|
|
5018
|
-
let value = 0;
|
|
5019
|
-
for (const char of chunk) {
|
|
5020
|
-
const v = charToValue.get(char);
|
|
5021
|
-
if (v === undefined) {
|
|
5022
|
-
throw new Error(`Invalid base85 character: ${char}`);
|
|
5023
|
-
}
|
|
5024
|
-
value = value * 85 + v;
|
|
5025
|
-
}
|
|
5026
|
-
const bytes = [
|
|
5027
|
-
(value >> 24) & 0xff,
|
|
5028
|
-
(value >> 16) & 0xff,
|
|
5029
|
-
(value >> 8) & 0xff,
|
|
5030
|
-
value & 0xff,
|
|
5031
|
-
];
|
|
5032
|
-
const numBytes = chunk.length === 5 ? 4 : chunk.length - 1;
|
|
5033
|
-
result.push(...bytes.slice(0, numBytes));
|
|
5034
|
-
}
|
|
5035
|
-
return Buffer.from(result);
|
|
5036
|
-
}
|
|
5037
|
-
/**
|
|
5038
|
-
* Deobfuscate the blob to get the encryption key
|
|
5039
|
-
*/
|
|
5040
|
-
function deobfuscate(blob) {
|
|
5041
|
-
const decoded = base85Decode(blob);
|
|
5042
|
-
const xored = Buffer.alloc(decoded.length);
|
|
5043
|
-
for (let i = 0; i < decoded.length; i++) {
|
|
5044
|
-
xored[i] = decoded[i] ^ BLOB_KEY[i % BLOB_KEY.length];
|
|
5045
|
-
}
|
|
5046
|
-
const decompressed = zlib.inflateSync(xored);
|
|
5047
|
-
return decompressed.toString('utf-8');
|
|
5048
|
-
}
|
|
5165
|
+
const lodash_1 = __webpack_require__(/*! lodash */ "lodash");
|
|
5166
|
+
const schema_1 = __webpack_require__(/*! ./schema */ "./src/localdb/schema.ts");
|
|
5049
5167
|
/**
|
|
5050
|
-
*
|
|
5051
|
-
*/
|
|
5052
|
-
function getEncryptionKey() {
|
|
5053
|
-
const key = deobfuscate(BLOB);
|
|
5054
|
-
if (!key.startsWith('r8gd')) {
|
|
5055
|
-
throw new Error('Invalid encryption key derived');
|
|
5056
|
-
}
|
|
5057
|
-
return key;
|
|
5058
|
-
}
|
|
5059
|
-
// ============================================================================
|
|
5060
|
-
// Database Connection
|
|
5061
|
-
// ============================================================================
|
|
5062
|
-
/**
|
|
5063
|
-
* Open a OneLibrary database with SQLCipher decryption
|
|
5064
|
-
*/
|
|
5065
|
-
function openOneLibraryDb(dbPath) {
|
|
5066
|
-
const key = getEncryptionKey();
|
|
5067
|
-
const db = new better_sqlite3_multiple_ciphers_1.default(dbPath, { readonly: true });
|
|
5068
|
-
db.pragma('cipher = sqlcipher');
|
|
5069
|
-
db.pragma('legacy = 4');
|
|
5070
|
-
db.pragma(`key = '${key}'`);
|
|
5071
|
-
return db;
|
|
5072
|
-
}
|
|
5073
|
-
// ============================================================================
|
|
5074
|
-
// OneLibrary Adapter
|
|
5075
|
-
// ============================================================================
|
|
5076
|
-
/**
|
|
5077
|
-
* Adapter for OneLibrary database that matches the ORM interface.
|
|
5078
|
-
* Queries the SQLite file directly instead of hydrating into memory.
|
|
5079
|
-
*/
|
|
5080
|
-
class OneLibraryAdapter {
|
|
5081
|
-
constructor(dbPath) {
|
|
5082
|
-
_OneLibraryAdapter_instances.add(this);
|
|
5083
|
-
this.type = 'oneLibrary';
|
|
5084
|
-
_OneLibraryAdapter_db.set(this, void 0);
|
|
5085
|
-
_OneLibraryAdapter_stmtCache.set(this, new Map());
|
|
5086
|
-
__classPrivateFieldSet(this, _OneLibraryAdapter_db, openOneLibraryDb(dbPath), "f");
|
|
5087
|
-
}
|
|
5088
|
-
/**
|
|
5089
|
-
* Close the database connection
|
|
5090
|
-
*/
|
|
5091
|
-
close() {
|
|
5092
|
-
__classPrivateFieldGet(this, _OneLibraryAdapter_stmtCache, "f").clear();
|
|
5093
|
-
__classPrivateFieldGet(this, _OneLibraryAdapter_db, "f").close();
|
|
5094
|
-
}
|
|
5095
|
-
// ==========================================================================
|
|
5096
|
-
// Track Queries
|
|
5097
|
-
// ==========================================================================
|
|
5098
|
-
/**
|
|
5099
|
-
* Find a track by ID
|
|
5100
|
-
*/
|
|
5101
|
-
findTrack(id) {
|
|
5102
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5103
|
-
SELECT c.*,
|
|
5104
|
-
a.name as artistName,
|
|
5105
|
-
al.name as albumName,
|
|
5106
|
-
g.name as genreName,
|
|
5107
|
-
k.name as keyName,
|
|
5108
|
-
col.name as colorName,
|
|
5109
|
-
lbl.name as labelName,
|
|
5110
|
-
img.path as artworkPath,
|
|
5111
|
-
remix.name as remixerName,
|
|
5112
|
-
orig.name as originalArtistName,
|
|
5113
|
-
comp.name as composerName
|
|
5114
|
-
FROM content c
|
|
5115
|
-
LEFT JOIN artist a ON c.artist_id_artist = a.artist_id
|
|
5116
|
-
LEFT JOIN album al ON c.album_id = al.album_id
|
|
5117
|
-
LEFT JOIN genre g ON c.genre_id = g.genre_id
|
|
5118
|
-
LEFT JOIN key k ON c.key_id = k.key_id
|
|
5119
|
-
LEFT JOIN color col ON c.color_id = col.color_id
|
|
5120
|
-
LEFT JOIN label lbl ON c.label_id = lbl.label_id
|
|
5121
|
-
LEFT JOIN image img ON c.image_id = img.image_id
|
|
5122
|
-
LEFT JOIN artist remix ON c.artist_id_remixer = remix.artist_id
|
|
5123
|
-
LEFT JOIN artist orig ON c.artist_id_originalArtist = orig.artist_id
|
|
5124
|
-
LEFT JOIN artist comp ON c.artist_id_composer = comp.artist_id
|
|
5125
|
-
WHERE c.content_id = ?
|
|
5126
|
-
`).get(id);
|
|
5127
|
-
if (!row) {
|
|
5128
|
-
return null;
|
|
5129
|
-
}
|
|
5130
|
-
return __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_contentToTrack).call(this, row);
|
|
5131
|
-
}
|
|
5132
|
-
/**
|
|
5133
|
-
* Find all tracks in the database
|
|
5134
|
-
*/
|
|
5135
|
-
findAllTracks() {
|
|
5136
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5137
|
-
SELECT c.*,
|
|
5138
|
-
a.name as artistName,
|
|
5139
|
-
al.name as albumName,
|
|
5140
|
-
g.name as genreName,
|
|
5141
|
-
k.name as keyName,
|
|
5142
|
-
col.name as colorName,
|
|
5143
|
-
lbl.name as labelName,
|
|
5144
|
-
img.path as artworkPath,
|
|
5145
|
-
remix.name as remixerName,
|
|
5146
|
-
orig.name as originalArtistName,
|
|
5147
|
-
comp.name as composerName
|
|
5148
|
-
FROM content c
|
|
5149
|
-
LEFT JOIN artist a ON c.artist_id_artist = a.artist_id
|
|
5150
|
-
LEFT JOIN album al ON c.album_id = al.album_id
|
|
5151
|
-
LEFT JOIN genre g ON c.genre_id = g.genre_id
|
|
5152
|
-
LEFT JOIN key k ON c.key_id = k.key_id
|
|
5153
|
-
LEFT JOIN color col ON c.color_id = col.color_id
|
|
5154
|
-
LEFT JOIN label lbl ON c.label_id = lbl.label_id
|
|
5155
|
-
LEFT JOIN image img ON c.image_id = img.image_id
|
|
5156
|
-
LEFT JOIN artist remix ON c.artist_id_remixer = remix.artist_id
|
|
5157
|
-
LEFT JOIN artist orig ON c.artist_id_originalArtist = orig.artist_id
|
|
5158
|
-
LEFT JOIN artist comp ON c.artist_id_composer = comp.artist_id
|
|
5159
|
-
`).all();
|
|
5160
|
-
return rows.map(row => __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_contentToTrack).call(this, row));
|
|
5161
|
-
}
|
|
5162
|
-
// ==========================================================================
|
|
5163
|
-
// Cue Queries
|
|
5164
|
-
// ==========================================================================
|
|
5165
|
-
/**
|
|
5166
|
-
* Find cue points for a track
|
|
5167
|
-
*/
|
|
5168
|
-
findCues(trackId) {
|
|
5169
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5170
|
-
SELECT * FROM cue WHERE content_id = ?
|
|
5171
|
-
`).all(trackId);
|
|
5172
|
-
return rows.map(row => __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_cueToCueAndLoop).call(this, row));
|
|
5173
|
-
}
|
|
5174
|
-
// ==========================================================================
|
|
5175
|
-
// Playlist Queries
|
|
5176
|
-
// ==========================================================================
|
|
5177
|
-
/**
|
|
5178
|
-
* Find a playlist by ID
|
|
5179
|
-
*/
|
|
5180
|
-
findPlaylistById(playlistId) {
|
|
5181
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5182
|
-
SELECT * FROM playlist WHERE playlist_id = ?
|
|
5183
|
-
`).get(playlistId);
|
|
5184
|
-
return row ? __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_playlistRowToPlaylist).call(this, row) : null;
|
|
5185
|
-
}
|
|
5186
|
-
/**
|
|
5187
|
-
* Query for a list of {folders, playlists, tracks} given a playlist ID.
|
|
5188
|
-
* If no ID is provided the root list is queried.
|
|
5189
|
-
*/
|
|
5190
|
-
findPlaylist(playlistId) {
|
|
5191
|
-
const parentCondition = playlistId === undefined ? 'playlist_id_parent IS NULL' : 'playlist_id_parent = ?';
|
|
5192
|
-
// Lookup playlists / folders for this playlist ID
|
|
5193
|
-
const playlistRows = (playlistId === undefined
|
|
5194
|
-
? __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `SELECT * FROM playlist WHERE ${parentCondition}`).all()
|
|
5195
|
-
: __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `SELECT * FROM playlist WHERE ${parentCondition}`).all(playlistId));
|
|
5196
|
-
const folders = [];
|
|
5197
|
-
const playlists = [];
|
|
5198
|
-
for (const row of playlistRows) {
|
|
5199
|
-
const playlist = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_playlistRowToPlaylist).call(this, row);
|
|
5200
|
-
if (playlist.isFolder) {
|
|
5201
|
-
folders.push(playlist);
|
|
5202
|
-
}
|
|
5203
|
-
else {
|
|
5204
|
-
playlists.push(playlist);
|
|
5205
|
-
}
|
|
5206
|
-
}
|
|
5207
|
-
// Get track entries for this playlist
|
|
5208
|
-
const entryRows = playlistId === undefined
|
|
5209
|
-
? []
|
|
5210
|
-
: __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5211
|
-
SELECT * FROM playlist_content WHERE playlist_id = ? ORDER BY sequenceNo
|
|
5212
|
-
`).all(playlistId);
|
|
5213
|
-
const trackEntries = entryRows.map((row, index) => ({
|
|
5214
|
-
id: index, // playlist_content doesn't have a unique ID, use index
|
|
5215
|
-
sortIndex: row.sequenceNo,
|
|
5216
|
-
playlistId: row.playlist_id,
|
|
5217
|
-
trackId: row.content_id,
|
|
5218
|
-
}));
|
|
5219
|
-
return { folders, playlists, trackEntries };
|
|
5220
|
-
}
|
|
5221
|
-
/**
|
|
5222
|
-
* Get track IDs for a playlist in order
|
|
5223
|
-
*/
|
|
5224
|
-
findPlaylistContents(playlistId) {
|
|
5225
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5226
|
-
SELECT content_id FROM playlist_content
|
|
5227
|
-
WHERE playlist_id = ? ORDER BY sequenceNo
|
|
5228
|
-
`).all(playlistId);
|
|
5229
|
-
return rows.map(r => r.content_id);
|
|
5230
|
-
}
|
|
5231
|
-
// ==========================================================================
|
|
5232
|
-
// Reference Table Queries
|
|
5233
|
-
// ==========================================================================
|
|
5234
|
-
findArtist(artistId) {
|
|
5235
|
-
var _a;
|
|
5236
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5237
|
-
SELECT * FROM artist WHERE artist_id = ?
|
|
5238
|
-
`).get(artistId);
|
|
5239
|
-
return row ? { id: row.artist_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
|
|
5240
|
-
}
|
|
5241
|
-
findAlbum(albumId) {
|
|
5242
|
-
var _a;
|
|
5243
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5244
|
-
SELECT * FROM album WHERE album_id = ?
|
|
5245
|
-
`).get(albumId);
|
|
5246
|
-
return row ? { id: row.album_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
|
|
5247
|
-
}
|
|
5248
|
-
findGenre(genreId) {
|
|
5249
|
-
var _a;
|
|
5250
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5251
|
-
SELECT * FROM genre WHERE genre_id = ?
|
|
5252
|
-
`).get(genreId);
|
|
5253
|
-
return row ? { id: row.genre_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
|
|
5254
|
-
}
|
|
5255
|
-
findKey(keyId) {
|
|
5256
|
-
var _a;
|
|
5257
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5258
|
-
SELECT * FROM key WHERE key_id = ?
|
|
5259
|
-
`).get(keyId);
|
|
5260
|
-
return row ? { id: row.key_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
|
|
5261
|
-
}
|
|
5262
|
-
findColor(colorId) {
|
|
5263
|
-
var _a;
|
|
5264
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5265
|
-
SELECT * FROM color WHERE color_id = ?
|
|
5266
|
-
`).get(colorId);
|
|
5267
|
-
return row ? { id: row.color_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
|
|
5268
|
-
}
|
|
5269
|
-
findLabel(labelId) {
|
|
5270
|
-
var _a;
|
|
5271
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5272
|
-
SELECT * FROM label WHERE label_id = ?
|
|
5273
|
-
`).get(labelId);
|
|
5274
|
-
return row ? { id: row.label_id, name: (_a = row.name) !== null && _a !== void 0 ? _a : '' } : null;
|
|
5275
|
-
}
|
|
5276
|
-
findArtwork(imageId) {
|
|
5277
|
-
var _a;
|
|
5278
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5279
|
-
SELECT * FROM image WHERE image_id = ?
|
|
5280
|
-
`).get(imageId);
|
|
5281
|
-
return row ? { id: row.image_id, path: (_a = row.path) !== null && _a !== void 0 ? _a : undefined } : null;
|
|
5282
|
-
}
|
|
5283
|
-
// ==========================================================================
|
|
5284
|
-
// MyTag (User Tags) Queries
|
|
5285
|
-
// ==========================================================================
|
|
5286
|
-
/**
|
|
5287
|
-
* Find all root-level MyTags (folders and tags with no parent)
|
|
5288
|
-
*/
|
|
5289
|
-
findMyTags(parentId) {
|
|
5290
|
-
const parentCondition = parentId === undefined
|
|
5291
|
-
? 'myTag_id_parent IS NULL OR myTag_id_parent = 0'
|
|
5292
|
-
: 'myTag_id_parent = ?';
|
|
5293
|
-
const rows = (parentId === undefined
|
|
5294
|
-
? __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `SELECT * FROM myTag WHERE ${parentCondition} ORDER BY sequenceNo`).all()
|
|
5295
|
-
: __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `SELECT * FROM myTag WHERE ${parentCondition} ORDER BY sequenceNo`).all(parentId));
|
|
5296
|
-
const folders = [];
|
|
5297
|
-
const tags = [];
|
|
5298
|
-
for (const row of rows) {
|
|
5299
|
-
const tag = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_myTagRowToMyTag).call(this, row);
|
|
5300
|
-
if (tag.isFolder) {
|
|
5301
|
-
folders.push(tag);
|
|
5302
|
-
}
|
|
5303
|
-
else {
|
|
5304
|
-
tags.push(tag);
|
|
5305
|
-
}
|
|
5306
|
-
}
|
|
5307
|
-
return { folders, tags };
|
|
5308
|
-
}
|
|
5309
|
-
/**
|
|
5310
|
-
* Find a MyTag by ID
|
|
5311
|
-
*/
|
|
5312
|
-
findMyTagById(myTagId) {
|
|
5313
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5314
|
-
SELECT * FROM myTag WHERE myTag_id = ?
|
|
5315
|
-
`).get(myTagId);
|
|
5316
|
-
return row ? __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_myTagRowToMyTag).call(this, row) : null;
|
|
5317
|
-
}
|
|
5318
|
-
/**
|
|
5319
|
-
* Get track IDs for a MyTag
|
|
5320
|
-
*/
|
|
5321
|
-
findMyTagContents(myTagId) {
|
|
5322
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5323
|
-
SELECT content_id FROM myTag_content WHERE myTag_id = ?
|
|
5324
|
-
`).all(myTagId);
|
|
5325
|
-
return rows.map(r => r.content_id);
|
|
5326
|
-
}
|
|
5327
|
-
/**
|
|
5328
|
-
* Get all MyTags assigned to a track
|
|
5329
|
-
*/
|
|
5330
|
-
findMyTagsForTrack(trackId) {
|
|
5331
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5332
|
-
SELECT t.* FROM myTag t
|
|
5333
|
-
INNER JOIN myTag_content tc ON t.myTag_id = tc.myTag_id
|
|
5334
|
-
WHERE tc.content_id = ?
|
|
5335
|
-
`).all(trackId);
|
|
5336
|
-
return rows.map(row => __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_myTagRowToMyTag).call(this, row));
|
|
5337
|
-
}
|
|
5338
|
-
// ==========================================================================
|
|
5339
|
-
// History Queries
|
|
5340
|
-
// ==========================================================================
|
|
5341
|
-
/**
|
|
5342
|
-
* Find all history sessions
|
|
5343
|
-
*/
|
|
5344
|
-
findHistorySessions() {
|
|
5345
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5346
|
-
SELECT * FROM history ORDER BY sequenceNo
|
|
5347
|
-
`).all();
|
|
5348
|
-
return rows.map(row => {
|
|
5349
|
-
var _a;
|
|
5350
|
-
return ({
|
|
5351
|
-
id: row.history_id,
|
|
5352
|
-
name: (_a = row.name) !== null && _a !== void 0 ? _a : '',
|
|
5353
|
-
parentId: row.history_id_parent,
|
|
5354
|
-
});
|
|
5355
|
-
});
|
|
5356
|
-
}
|
|
5357
|
-
/**
|
|
5358
|
-
* Get track IDs for a history session in order
|
|
5359
|
-
*/
|
|
5360
|
-
findHistoryContents(historyId) {
|
|
5361
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5362
|
-
SELECT content_id FROM history_content
|
|
5363
|
-
WHERE history_id = ? ORDER BY sequenceNo
|
|
5364
|
-
`).all(historyId);
|
|
5365
|
-
return rows.map(r => r.content_id);
|
|
5366
|
-
}
|
|
5367
|
-
// ==========================================================================
|
|
5368
|
-
// Hot Cue Bank Queries
|
|
5369
|
-
// ==========================================================================
|
|
5370
|
-
/**
|
|
5371
|
-
* Find all hot cue bank lists
|
|
5372
|
-
*/
|
|
5373
|
-
findHotCueBankLists() {
|
|
5374
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5375
|
-
SELECT * FROM hotCueBankList ORDER BY sequenceNo
|
|
5376
|
-
`).all();
|
|
5377
|
-
return rows.map(row => {
|
|
5378
|
-
var _a;
|
|
5379
|
-
return ({
|
|
5380
|
-
id: row.hotCueBankList_id,
|
|
5381
|
-
name: (_a = row.name) !== null && _a !== void 0 ? _a : '',
|
|
5382
|
-
parentId: row.hotCueBankList_id_parent,
|
|
5383
|
-
});
|
|
5384
|
-
});
|
|
5385
|
-
}
|
|
5386
|
-
/**
|
|
5387
|
-
* Get cue IDs for a hot cue bank list
|
|
5388
|
-
*/
|
|
5389
|
-
findHotCueBankListCues(bankListId) {
|
|
5390
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5391
|
-
SELECT cue_id FROM hotCueBankList_cue
|
|
5392
|
-
WHERE hotCueBankList_id = ? ORDER BY sequenceNo
|
|
5393
|
-
`).all(bankListId);
|
|
5394
|
-
return rows.map(r => r.cue_id);
|
|
5395
|
-
}
|
|
5396
|
-
// ==========================================================================
|
|
5397
|
-
// Menu Configuration Queries
|
|
5398
|
-
// ==========================================================================
|
|
5399
|
-
/**
|
|
5400
|
-
* Get all menu items (browse categories)
|
|
5401
|
-
*/
|
|
5402
|
-
findMenuItems() {
|
|
5403
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5404
|
-
SELECT * FROM menuItem ORDER BY menuItem_id
|
|
5405
|
-
`).all();
|
|
5406
|
-
return rows.map(row => {
|
|
5407
|
-
var _a, _b;
|
|
5408
|
-
return ({
|
|
5409
|
-
id: row.menuItem_id,
|
|
5410
|
-
kind: (_a = row.kind) !== null && _a !== void 0 ? _a : 0,
|
|
5411
|
-
name: (_b = row.name) !== null && _b !== void 0 ? _b : '',
|
|
5412
|
-
});
|
|
5413
|
-
});
|
|
5414
|
-
}
|
|
5415
|
-
/**
|
|
5416
|
-
* Get visible categories with their menu item info
|
|
5417
|
-
*/
|
|
5418
|
-
findVisibleCategories() {
|
|
5419
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5420
|
-
SELECT c.*, m.name as menuName, m.kind as menuKind
|
|
5421
|
-
FROM category c
|
|
5422
|
-
LEFT JOIN menuItem m ON c.menuItem_id = m.menuItem_id
|
|
5423
|
-
WHERE c.isVisible = 1
|
|
5424
|
-
ORDER BY c.sequenceNo
|
|
5425
|
-
`).all();
|
|
5426
|
-
return rows.map(row => {
|
|
5427
|
-
var _a, _b, _c;
|
|
5428
|
-
return ({
|
|
5429
|
-
id: row.category_id,
|
|
5430
|
-
menuItemId: (_a = row.menuItem_id) !== null && _a !== void 0 ? _a : 0,
|
|
5431
|
-
name: (_b = row.menuName) !== null && _b !== void 0 ? _b : '',
|
|
5432
|
-
kind: (_c = row.menuKind) !== null && _c !== void 0 ? _c : 0,
|
|
5433
|
-
isVisible: !!row.isVisible,
|
|
5434
|
-
});
|
|
5435
|
-
});
|
|
5436
|
-
}
|
|
5437
|
-
/**
|
|
5438
|
-
* Get visible sort options
|
|
5439
|
-
*/
|
|
5440
|
-
findVisibleSortOptions() {
|
|
5441
|
-
const rows = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5442
|
-
SELECT s.*, m.name as menuName, m.kind as menuKind
|
|
5443
|
-
FROM sort s
|
|
5444
|
-
LEFT JOIN menuItem m ON s.menuItem_id = m.menuItem_id
|
|
5445
|
-
WHERE s.isVisible = 1
|
|
5446
|
-
ORDER BY s.sequenceNo
|
|
5447
|
-
`).all();
|
|
5448
|
-
return rows.map(row => {
|
|
5449
|
-
var _a, _b, _c;
|
|
5450
|
-
return ({
|
|
5451
|
-
id: row.sort_id,
|
|
5452
|
-
menuItemId: (_a = row.menuItem_id) !== null && _a !== void 0 ? _a : 0,
|
|
5453
|
-
name: (_b = row.menuName) !== null && _b !== void 0 ? _b : '',
|
|
5454
|
-
kind: (_c = row.menuKind) !== null && _c !== void 0 ? _c : 0,
|
|
5455
|
-
isVisible: !!row.isVisible,
|
|
5456
|
-
isSelectedAsSubColumn: !!row.isSelectedAsSubColumn,
|
|
5457
|
-
});
|
|
5458
|
-
});
|
|
5459
|
-
}
|
|
5460
|
-
// ==========================================================================
|
|
5461
|
-
// Device Property Queries
|
|
5462
|
-
// ==========================================================================
|
|
5463
|
-
/**
|
|
5464
|
-
* Get device properties
|
|
5465
|
-
*/
|
|
5466
|
-
getProperty() {
|
|
5467
|
-
var _a, _b, _c, _d, _e;
|
|
5468
|
-
const row = __classPrivateFieldGet(this, _OneLibraryAdapter_instances, "m", _OneLibraryAdapter_getStmt).call(this, `
|
|
5469
|
-
SELECT * FROM property LIMIT 1
|
|
5470
|
-
`).get();
|
|
5471
|
-
if (!row) {
|
|
5472
|
-
return null;
|
|
5473
|
-
}
|
|
5474
|
-
return {
|
|
5475
|
-
deviceName: (_a = row.deviceName) !== null && _a !== void 0 ? _a : '',
|
|
5476
|
-
dbVersion: (_b = row.dbVersion) !== null && _b !== void 0 ? _b : '',
|
|
5477
|
-
numberOfContents: (_c = row.numberOfContents) !== null && _c !== void 0 ? _c : 0,
|
|
5478
|
-
createdDate: (_d = row.createdDate) !== null && _d !== void 0 ? _d : '',
|
|
5479
|
-
backgroundColorType: (_e = row.backGroundColorType) !== null && _e !== void 0 ? _e : 0,
|
|
5480
|
-
};
|
|
5481
|
-
}
|
|
5482
|
-
}
|
|
5483
|
-
exports.OneLibraryAdapter = OneLibraryAdapter;
|
|
5484
|
-
_OneLibraryAdapter_db = new WeakMap(), _OneLibraryAdapter_stmtCache = new WeakMap(), _OneLibraryAdapter_instances = new WeakSet(), _OneLibraryAdapter_getStmt = function _OneLibraryAdapter_getStmt(sql) {
|
|
5485
|
-
let stmt = __classPrivateFieldGet(this, _OneLibraryAdapter_stmtCache, "f").get(sql);
|
|
5486
|
-
if (!stmt) {
|
|
5487
|
-
stmt = __classPrivateFieldGet(this, _OneLibraryAdapter_db, "f").prepare(sql);
|
|
5488
|
-
__classPrivateFieldGet(this, _OneLibraryAdapter_stmtCache, "f").set(sql, stmt);
|
|
5489
|
-
}
|
|
5490
|
-
return stmt;
|
|
5491
|
-
}, _OneLibraryAdapter_contentToTrack = function _OneLibraryAdapter_contentToTrack(row) {
|
|
5492
|
-
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;
|
|
5493
|
-
const track = {
|
|
5494
|
-
id: row.content_id,
|
|
5495
|
-
title: (_a = row.title) !== null && _a !== void 0 ? _a : '',
|
|
5496
|
-
duration: row.length ? row.length / 1000 : 0, // ms to seconds
|
|
5497
|
-
bitrate: (_b = row.bitrate) !== null && _b !== void 0 ? _b : undefined,
|
|
5498
|
-
tempo: row.bpmx100 ? row.bpmx100 / 100 : 0,
|
|
5499
|
-
rating: (_c = row.rating) !== null && _c !== void 0 ? _c : 0,
|
|
5500
|
-
comment: (_d = row.djComment) !== null && _d !== void 0 ? _d : '',
|
|
5501
|
-
filePath: (_e = row.path) !== null && _e !== void 0 ? _e : '',
|
|
5502
|
-
fileName: (_f = row.fileName) !== null && _f !== void 0 ? _f : '',
|
|
5503
|
-
trackNumber: (_g = row.trackNo) !== null && _g !== void 0 ? _g : undefined,
|
|
5504
|
-
discNumber: (_h = row.discNo) !== null && _h !== void 0 ? _h : undefined,
|
|
5505
|
-
sampleRate: (_j = row.samplingRate) !== null && _j !== void 0 ? _j : undefined,
|
|
5506
|
-
sampleDepth: (_k = row.bitDepth) !== null && _k !== void 0 ? _k : undefined,
|
|
5507
|
-
playCount: (_l = row.djPlayCount) !== null && _l !== void 0 ? _l : undefined,
|
|
5508
|
-
year: (_m = row.releaseYear) !== null && _m !== void 0 ? _m : undefined,
|
|
5509
|
-
mixName: (_o = row.subtitle) !== null && _o !== void 0 ? _o : undefined,
|
|
5510
|
-
autoloadHotcues: !!row.isHotCueAutoLoadOn,
|
|
5511
|
-
kuvoPublic: !!row.isKuvoDeliverStatusOn,
|
|
5512
|
-
fileSize: (_p = row.fileSize) !== null && _p !== void 0 ? _p : undefined,
|
|
5513
|
-
// Normalize analyzePath by trimming the .DAT extension (same as rekordbox.ts)
|
|
5514
|
-
// loadAnlz() will append the appropriate extension (.DAT or .EXT) when loading
|
|
5515
|
-
analyzePath: row.analysisDataFilePath
|
|
5516
|
-
? row.analysisDataFilePath.substring(0, row.analysisDataFilePath.length - 4)
|
|
5517
|
-
: undefined,
|
|
5518
|
-
releaseDate: (_q = row.releaseDate) !== null && _q !== void 0 ? _q : undefined,
|
|
5519
|
-
dateAdded: row.dateAdded ? new Date(row.dateAdded) : undefined,
|
|
5520
|
-
beatGrid: null,
|
|
5521
|
-
cueAndLoops: null,
|
|
5522
|
-
waveformHd: null,
|
|
5523
|
-
artwork: row.image_id
|
|
5524
|
-
? { id: row.image_id, path: (_r = row.artworkPath) !== null && _r !== void 0 ? _r : undefined }
|
|
5525
|
-
: null,
|
|
5526
|
-
artist: row.artist_id_artist
|
|
5527
|
-
? { id: row.artist_id_artist, name: (_s = row.artistName) !== null && _s !== void 0 ? _s : '' }
|
|
5528
|
-
: null,
|
|
5529
|
-
originalArtist: row.artist_id_originalArtist
|
|
5530
|
-
? {
|
|
5531
|
-
id: row.artist_id_originalArtist,
|
|
5532
|
-
name: (_t = row.originalArtistName) !== null && _t !== void 0 ? _t : '',
|
|
5533
|
-
}
|
|
5534
|
-
: null,
|
|
5535
|
-
remixer: row.artist_id_remixer
|
|
5536
|
-
? { id: row.artist_id_remixer, name: (_u = row.remixerName) !== null && _u !== void 0 ? _u : '' }
|
|
5537
|
-
: null,
|
|
5538
|
-
composer: row.artist_id_composer
|
|
5539
|
-
? { id: row.artist_id_composer, name: (_v = row.composerName) !== null && _v !== void 0 ? _v : '' }
|
|
5540
|
-
: null,
|
|
5541
|
-
album: row.album_id
|
|
5542
|
-
? { id: row.album_id, name: (_w = row.albumName) !== null && _w !== void 0 ? _w : '' }
|
|
5543
|
-
: null,
|
|
5544
|
-
label: row.label_id
|
|
5545
|
-
? { id: row.label_id, name: (_x = row.labelName) !== null && _x !== void 0 ? _x : '' }
|
|
5546
|
-
: null,
|
|
5547
|
-
genre: row.genre_id
|
|
5548
|
-
? { id: row.genre_id, name: (_y = row.genreName) !== null && _y !== void 0 ? _y : '' }
|
|
5549
|
-
: null,
|
|
5550
|
-
color: row.color_id
|
|
5551
|
-
? { id: row.color_id, name: (_z = row.colorName) !== null && _z !== void 0 ? _z : '' }
|
|
5552
|
-
: null,
|
|
5553
|
-
key: row.key_id ? { id: row.key_id, name: (_0 = row.keyName) !== null && _0 !== void 0 ? _0 : '' } : null,
|
|
5554
|
-
};
|
|
5555
|
-
return track;
|
|
5556
|
-
}, _OneLibraryAdapter_cueToCueAndLoop = function _OneLibraryAdapter_cueToCueAndLoop(row) {
|
|
5557
|
-
var _a, _b, _c, _d, _e, _f;
|
|
5558
|
-
const offset = row.inUsec ? row.inUsec / 1000 : 0; // microseconds to ms
|
|
5559
|
-
const outOffset = (_a = row.outUsec) !== null && _a !== void 0 ? _a : null;
|
|
5560
|
-
const isLoop = outOffset !== null && outOffset > 0;
|
|
5561
|
-
const length = isLoop ? (outOffset - ((_b = row.inUsec) !== null && _b !== void 0 ? _b : 0)) / 1000 : 0;
|
|
5562
|
-
// Determine if this is a hot cue (kind value mapping may vary)
|
|
5563
|
-
// Based on observation: kind 0 = memory cue, kind >= 1 may be hot cue
|
|
5564
|
-
const isHotCue = ((_c = row.kind) !== null && _c !== void 0 ? _c : 0) >= 1 && ((_d = row.kind) !== null && _d !== void 0 ? _d : 0) <= 8;
|
|
5565
|
-
const button = isHotCue ? ((_e = row.kind) !== null && _e !== void 0 ? _e : 0) : undefined;
|
|
5566
|
-
const label = (_f = row.cueComment) !== null && _f !== void 0 ? _f : undefined;
|
|
5567
|
-
const color = row.colorTableIndex;
|
|
5568
|
-
if (isLoop && isHotCue) {
|
|
5569
|
-
return {
|
|
5570
|
-
type: 'hot_loop',
|
|
5571
|
-
offset,
|
|
5572
|
-
length,
|
|
5573
|
-
button: button,
|
|
5574
|
-
label,
|
|
5575
|
-
color,
|
|
5576
|
-
};
|
|
5577
|
-
}
|
|
5578
|
-
if (isLoop) {
|
|
5579
|
-
return {
|
|
5580
|
-
type: 'loop',
|
|
5581
|
-
offset,
|
|
5582
|
-
length,
|
|
5583
|
-
label,
|
|
5584
|
-
color,
|
|
5585
|
-
};
|
|
5586
|
-
}
|
|
5587
|
-
if (isHotCue) {
|
|
5588
|
-
return {
|
|
5589
|
-
type: 'hot_cue',
|
|
5590
|
-
offset,
|
|
5591
|
-
button: button,
|
|
5592
|
-
label,
|
|
5593
|
-
color,
|
|
5594
|
-
};
|
|
5595
|
-
}
|
|
5596
|
-
return {
|
|
5597
|
-
type: 'cue_point',
|
|
5598
|
-
offset,
|
|
5599
|
-
label,
|
|
5600
|
-
color,
|
|
5601
|
-
};
|
|
5602
|
-
}, _OneLibraryAdapter_playlistRowToPlaylist = function _OneLibraryAdapter_playlistRowToPlaylist(row) {
|
|
5603
|
-
var _a;
|
|
5604
|
-
return {
|
|
5605
|
-
id: row.playlist_id,
|
|
5606
|
-
name: (_a = row.name) !== null && _a !== void 0 ? _a : '',
|
|
5607
|
-
isFolder: row.attribute === onelibrary_schema_1.PlaylistAttribute.FOLDER,
|
|
5608
|
-
parentId: row.playlist_id_parent,
|
|
5609
|
-
};
|
|
5610
|
-
}, _OneLibraryAdapter_myTagRowToMyTag = function _OneLibraryAdapter_myTagRowToMyTag(row) {
|
|
5611
|
-
var _a;
|
|
5612
|
-
return {
|
|
5613
|
-
id: row.myTag_id,
|
|
5614
|
-
name: (_a = row.name) !== null && _a !== void 0 ? _a : '',
|
|
5615
|
-
isFolder: row.attribute === onelibrary_schema_1.MyTagAttribute.FOLDER,
|
|
5616
|
-
parentId: row.myTag_id_parent,
|
|
5617
|
-
};
|
|
5618
|
-
};
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
/***/ },
|
|
5622
|
-
|
|
5623
|
-
/***/ "./src/localdb/orm.ts"
|
|
5624
|
-
/*!****************************!*\
|
|
5625
|
-
!*** ./src/localdb/orm.ts ***!
|
|
5626
|
-
\****************************/
|
|
5627
|
-
(__unused_webpack_module, exports, __webpack_require__) {
|
|
5628
|
-
|
|
5629
|
-
"use strict";
|
|
5630
|
-
|
|
5631
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
5632
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
5633
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5634
|
-
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");
|
|
5635
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
5636
|
-
};
|
|
5637
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
5638
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
5639
|
-
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");
|
|
5640
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5641
|
-
};
|
|
5642
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5643
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5644
|
-
};
|
|
5645
|
-
var _MetadataORM_conn, _MetadataORM_stmtCache, _MetadataORM_inTransaction;
|
|
5646
|
-
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
5647
|
-
exports.MetadataORM = exports.Table = void 0;
|
|
5648
|
-
const better_sqlite3_multiple_ciphers_1 = __importDefault(__webpack_require__(/*! better-sqlite3-multiple-ciphers */ "better-sqlite3-multiple-ciphers"));
|
|
5649
|
-
const lodash_1 = __webpack_require__(/*! lodash */ "lodash");
|
|
5650
|
-
const schema_1 = __webpack_require__(/*! ./schema */ "./src/localdb/schema.ts");
|
|
5651
|
-
/**
|
|
5652
|
-
* Table names available
|
|
5168
|
+
* Table names available
|
|
5653
5169
|
*/
|
|
5654
5170
|
var Table;
|
|
5655
5171
|
(function (Table) {
|
|
@@ -5823,331 +5339,53 @@ _MetadataORM_conn = new WeakMap(), _MetadataORM_stmtCache = new WeakMap(), _Meta
|
|
|
5823
5339
|
|
|
5824
5340
|
"use strict";
|
|
5825
5341
|
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
Object.defineProperty(o, k2, desc);
|
|
5833
|
-
}) : (function(o, m, k, k2) {
|
|
5834
|
-
if (k2 === undefined) k2 = k;
|
|
5835
|
-
o[k2] = m[k];
|
|
5836
|
-
}));
|
|
5837
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
5838
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
5839
|
-
}) : function(o, v) {
|
|
5840
|
-
o["default"] = v;
|
|
5841
|
-
});
|
|
5842
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
5843
|
-
var ownKeys = function(o) {
|
|
5844
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
5845
|
-
var ar = [];
|
|
5846
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
5847
|
-
return ar;
|
|
5848
|
-
};
|
|
5849
|
-
return ownKeys(o);
|
|
5850
|
-
};
|
|
5851
|
-
return function (mod) {
|
|
5852
|
-
if (mod && mod.__esModule) return mod;
|
|
5853
|
-
var result = {};
|
|
5854
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
5855
|
-
__setModuleDefault(result, mod);
|
|
5856
|
-
return result;
|
|
5857
|
-
};
|
|
5858
|
-
})();
|
|
5859
|
-
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
5860
|
-
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
5861
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5862
|
-
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");
|
|
5863
|
-
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
5864
|
-
};
|
|
5865
|
-
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
5866
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
5867
|
-
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");
|
|
5868
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5869
|
-
};
|
|
5870
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5871
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5872
|
-
};
|
|
5873
|
-
var _RekordboxHydrator_orm, _RekordboxHydrator_onProgress;
|
|
5342
|
+
/**
|
|
5343
|
+
* Rekordbox Database Utilities
|
|
5344
|
+
*
|
|
5345
|
+
* Re-exports from the rekordbox/ folder for backward compatibility.
|
|
5346
|
+
* @see ./rekordbox/index.ts for the implementation
|
|
5347
|
+
*/
|
|
5874
5348
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
5875
|
-
exports.hydrateDatabase =
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5349
|
+
exports.loadAnlz = exports.hydrateDatabase = void 0;
|
|
5350
|
+
var index_1 = __webpack_require__(/*! ./rekordbox/index */ "./src/localdb/rekordbox/index.ts");
|
|
5351
|
+
Object.defineProperty(exports, "hydrateDatabase", ({ enumerable: true, get: function () { return index_1.hydrateDatabase; } }));
|
|
5352
|
+
Object.defineProperty(exports, "loadAnlz", ({ enumerable: true, get: function () { return index_1.loadAnlz; } }));
|
|
5353
|
+
|
|
5354
|
+
|
|
5355
|
+
/***/ },
|
|
5356
|
+
|
|
5357
|
+
/***/ "./src/localdb/rekordbox/anlz-parsers.ts"
|
|
5358
|
+
/*!***********************************************!*\
|
|
5359
|
+
!*** ./src/localdb/rekordbox/anlz-parsers.ts ***!
|
|
5360
|
+
\***********************************************/
|
|
5361
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
5362
|
+
|
|
5363
|
+
"use strict";
|
|
5364
|
+
|
|
5365
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
5366
|
+
exports.makeBeatGrid = makeBeatGrid;
|
|
5367
|
+
exports.makeCueAndLoop = makeCueAndLoop;
|
|
5368
|
+
exports.makeWaveformHd = makeWaveformHd;
|
|
5369
|
+
exports.makeExtendedCues = makeExtendedCues;
|
|
5370
|
+
exports.makeSongStructure = makeSongStructure;
|
|
5371
|
+
exports.makeWaveformPreview = makeWaveformPreview;
|
|
5372
|
+
exports.makeWaveform3BandPreview = makeWaveform3BandPreview;
|
|
5373
|
+
exports.makeWaveform3BandDetail = makeWaveform3BandDetail;
|
|
5374
|
+
exports.makeVocalConfig = makeVocalConfig;
|
|
5881
5375
|
const utils_1 = __webpack_require__(/*! src/localdb/utils */ "./src/localdb/utils.ts");
|
|
5882
5376
|
const converters_1 = __webpack_require__(/*! src/utils/converters */ "./src/utils/converters.ts");
|
|
5883
|
-
const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
|
|
5884
5377
|
/**
|
|
5885
|
-
*
|
|
5886
|
-
* database with all entities from the Rekordbox database. This includes all
|
|
5887
|
-
* track metadata, including analyzed metadata (such as beatgrids and waveforms).
|
|
5378
|
+
* Fill beatgrid data from the ANLZ section
|
|
5888
5379
|
*/
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5380
|
+
function makeBeatGrid(data) {
|
|
5381
|
+
return data.body.beats.map((beat) => ({
|
|
5382
|
+
offset: beat.time,
|
|
5383
|
+
bpm: beat.tempo / 100,
|
|
5384
|
+
count: beat.beatNumber,
|
|
5385
|
+
}));
|
|
5892
5386
|
}
|
|
5893
5387
|
/**
|
|
5894
|
-
*
|
|
5895
|
-
*/
|
|
5896
|
-
async function loadAnlz(track, type, anlzResolver) {
|
|
5897
|
-
const path = `${track.analyzePath}.${type}`;
|
|
5898
|
-
const anlzData = await anlzResolver(path);
|
|
5899
|
-
const stream = new kaitai_struct_1.KaitaiStream(anlzData);
|
|
5900
|
-
const anlz = new rekordbox_anlz_ksy_1.default(stream);
|
|
5901
|
-
const result = {};
|
|
5902
|
-
const resultDat = result;
|
|
5903
|
-
const resultExt = result;
|
|
5904
|
-
for (const section of anlz.sections) {
|
|
5905
|
-
switch (section.fourcc) {
|
|
5906
|
-
case SectionTags.BEAT_GRID:
|
|
5907
|
-
resultDat.beatGrid = makeBeatGrid(section);
|
|
5908
|
-
break;
|
|
5909
|
-
case SectionTags.CUES:
|
|
5910
|
-
resultDat.cueAndLoops = makeCueAndLoop(section);
|
|
5911
|
-
break;
|
|
5912
|
-
case SectionTags.CUES_2:
|
|
5913
|
-
resultExt.extendedCues = makeExtendedCues(section);
|
|
5914
|
-
break;
|
|
5915
|
-
case SectionTags.WAVE_PREVIEW:
|
|
5916
|
-
resultDat.waveformPreview = makeWaveformPreview(section);
|
|
5917
|
-
break;
|
|
5918
|
-
case SectionTags.WAVE_TINY:
|
|
5919
|
-
resultDat.waveformTiny = makeWaveformPreview(section);
|
|
5920
|
-
break;
|
|
5921
|
-
case SectionTags.WAVE_SCROLL:
|
|
5922
|
-
resultExt.waveformDetail = Buffer.from(section.body.entries);
|
|
5923
|
-
break;
|
|
5924
|
-
case SectionTags.WAVE_COLOR_PREVIEW:
|
|
5925
|
-
resultExt.waveformColorPreview = Buffer.from(section.body.entries);
|
|
5926
|
-
break;
|
|
5927
|
-
case SectionTags.WAVE_COLOR_SCROLL:
|
|
5928
|
-
resultExt.waveformHd = makeWaveformHd(section);
|
|
5929
|
-
break;
|
|
5930
|
-
case SectionTags.SONG_STRUCTURE:
|
|
5931
|
-
resultExt.songStructure = makeSongStructure(section);
|
|
5932
|
-
break;
|
|
5933
|
-
// VBR and PATH tags are defined but not currently extracted
|
|
5934
|
-
// as they're not commonly needed in the application
|
|
5935
|
-
case SectionTags.VBR:
|
|
5936
|
-
case SectionTags.PATH:
|
|
5937
|
-
break;
|
|
5938
|
-
}
|
|
5939
|
-
}
|
|
5940
|
-
return result;
|
|
5941
|
-
}
|
|
5942
|
-
/**
|
|
5943
|
-
* This service provides utilities for translating rekordbox database (pdb_ and
|
|
5944
|
-
* analysis (ANLZ) files into the common entity types used in this library.
|
|
5945
|
-
*/
|
|
5946
|
-
class RekordboxHydrator {
|
|
5947
|
-
constructor({ orm, onProgress }) {
|
|
5948
|
-
_RekordboxHydrator_orm.set(this, void 0);
|
|
5949
|
-
_RekordboxHydrator_onProgress.set(this, void 0);
|
|
5950
|
-
__classPrivateFieldSet(this, _RekordboxHydrator_orm, orm, "f");
|
|
5951
|
-
__classPrivateFieldSet(this, _RekordboxHydrator_onProgress, onProgress !== null && onProgress !== void 0 ? onProgress : (() => null), "f");
|
|
5952
|
-
}
|
|
5953
|
-
/**
|
|
5954
|
-
* Extract entries from a rekordbox pdb file and hydrate the passed database
|
|
5955
|
-
* connection with entities derived from the rekordbox entries.
|
|
5956
|
-
*/
|
|
5957
|
-
async hydrateFromPdb(pdbData, span) {
|
|
5958
|
-
const tx = span
|
|
5959
|
-
? span.startChild({ op: 'hydrateFromPdb' })
|
|
5960
|
-
: Telemetry.startTransaction({ name: 'hydrateFromPdb' });
|
|
5961
|
-
const parseTx = tx.startChild({ op: 'parsePdbData', data: { size: pdbData.length } });
|
|
5962
|
-
const stream = new kaitai_struct_1.KaitaiStream(pdbData);
|
|
5963
|
-
const db = new rekordbox_pdb_ksy_1.default(stream);
|
|
5964
|
-
parseTx.finish();
|
|
5965
|
-
const hydrateTx = tx.startChild({ op: 'hydration' });
|
|
5966
|
-
await Promise.all(db.tables.map((table) => this.hydrateFromTable(table, hydrateTx)));
|
|
5967
|
-
hydrateTx.finish();
|
|
5968
|
-
tx.finish();
|
|
5969
|
-
}
|
|
5970
|
-
/**
|
|
5971
|
-
* Hydrate the database with entities from the provided RekordboxPdb table.
|
|
5972
|
-
* See pdbEntityCreators for how tables are mapped into database entities.
|
|
5973
|
-
*/
|
|
5974
|
-
async hydrateFromTable(table, span) {
|
|
5975
|
-
const tableName = pdbTables[table.type];
|
|
5976
|
-
const createObject = pdbEntityCreators[table.type];
|
|
5977
|
-
const tx = span.startChild({ op: 'hydrateFromTable', description: tableName });
|
|
5978
|
-
if (createObject === undefined) {
|
|
5979
|
-
return;
|
|
5980
|
-
}
|
|
5981
|
-
let totalSaved = 0;
|
|
5982
|
-
let totalItems = 0;
|
|
5983
|
-
for (const _row of tableRows(table)) {
|
|
5984
|
-
void _row; // Intentionally unused - just counting
|
|
5985
|
-
totalItems++;
|
|
5986
|
-
}
|
|
5987
|
-
tx.setData('items', totalItems);
|
|
5988
|
-
// Use transaction for bulk inserts (10-100x faster)
|
|
5989
|
-
__classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").beginTransaction();
|
|
5990
|
-
try {
|
|
5991
|
-
for (const row of tableRows(table)) {
|
|
5992
|
-
const entity = createObject(row);
|
|
5993
|
-
__classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").insertEntity(tableName, entity);
|
|
5994
|
-
totalSaved++;
|
|
5995
|
-
// Report progress and yield every 100 rows (instead of every row)
|
|
5996
|
-
if (totalSaved % 100 === 0 || totalSaved === totalItems) {
|
|
5997
|
-
__classPrivateFieldGet(this, _RekordboxHydrator_onProgress, "f").call(this, { complete: totalSaved, table: tableName, total: totalItems });
|
|
5998
|
-
// Yield to event loop periodically to keep UI responsive
|
|
5999
|
-
await new Promise(r => setTimeout(r, 0));
|
|
6000
|
-
}
|
|
6001
|
-
}
|
|
6002
|
-
}
|
|
6003
|
-
finally {
|
|
6004
|
-
__classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").commit();
|
|
6005
|
-
}
|
|
6006
|
-
tx.finish();
|
|
6007
|
-
}
|
|
6008
|
-
}
|
|
6009
|
-
_RekordboxHydrator_orm = new WeakMap(), _RekordboxHydrator_onProgress = new WeakMap();
|
|
6010
|
-
/**
|
|
6011
|
-
* Utility generator that pages through a table and yields every present row.
|
|
6012
|
-
* This flattens the concept of rowGroups and refs.
|
|
6013
|
-
*/
|
|
6014
|
-
function* tableRows(table) {
|
|
6015
|
-
const { firstPage, lastPage } = table;
|
|
6016
|
-
let pageRef = firstPage;
|
|
6017
|
-
do {
|
|
6018
|
-
const page = pageRef.body;
|
|
6019
|
-
// Adjust our page ref for the next iteration. We do this early in our loop
|
|
6020
|
-
// so we can break without having to remember to update for the next iter.
|
|
6021
|
-
pageRef = page.nextPage;
|
|
6022
|
-
// Ignore non-data pages. Not sure what these are for?
|
|
6023
|
-
if (!page.isDataPage) {
|
|
6024
|
-
continue;
|
|
6025
|
-
}
|
|
6026
|
-
const rows = page.rowGroups
|
|
6027
|
-
.map((group) => group.rows)
|
|
6028
|
-
.flat()
|
|
6029
|
-
.filter((row) => row.present);
|
|
6030
|
-
for (const row of rows) {
|
|
6031
|
-
yield row.body;
|
|
6032
|
-
}
|
|
6033
|
-
} while (pageRef.index <= lastPage.index);
|
|
6034
|
-
}
|
|
6035
|
-
const ensureDate = (date) => date instanceof Date && !isNaN(date.valueOf()) ? date : undefined;
|
|
6036
|
-
/**
|
|
6037
|
-
* Utility to create a hydrator that hydrates the provided entity with the id
|
|
6038
|
-
* and name properties from the row.
|
|
6039
|
-
*/
|
|
6040
|
-
const makeIdNameHydrator = () => (row) => {
|
|
6041
|
-
var _a;
|
|
6042
|
-
return ({
|
|
6043
|
-
id: row.id,
|
|
6044
|
-
name: (_a = row.name.body.text) !== null && _a !== void 0 ? _a : '',
|
|
6045
|
-
});
|
|
6046
|
-
};
|
|
6047
|
-
/**
|
|
6048
|
-
* Translates a pdb track row entry to a {@link Track} entity.
|
|
6049
|
-
*/
|
|
6050
|
-
function createTrack(trackRow) {
|
|
6051
|
-
const analyzePath = trackRow.analyzePath.body.text;
|
|
6052
|
-
const track = {
|
|
6053
|
-
id: trackRow.id,
|
|
6054
|
-
title: trackRow.title.body.text,
|
|
6055
|
-
trackNumber: trackRow.trackNumber,
|
|
6056
|
-
discNumber: trackRow.discNumber,
|
|
6057
|
-
duration: trackRow.duration,
|
|
6058
|
-
sampleRate: trackRow.sampleRate,
|
|
6059
|
-
sampleDepth: trackRow.sampleDepth,
|
|
6060
|
-
bitrate: trackRow.bitrate,
|
|
6061
|
-
tempo: trackRow.tempo / 100,
|
|
6062
|
-
playCount: trackRow.playCount,
|
|
6063
|
-
year: trackRow.year,
|
|
6064
|
-
rating: trackRow.rating,
|
|
6065
|
-
mixName: trackRow.mixName.body.text,
|
|
6066
|
-
comment: trackRow.comment.body.text,
|
|
6067
|
-
autoloadHotcues: trackRow.autoloadHotcues.body.text === 'ON',
|
|
6068
|
-
kuvoPublic: trackRow.kuvoPublic.body.text === 'ON',
|
|
6069
|
-
filePath: trackRow.filePath.body.text,
|
|
6070
|
-
fileName: trackRow.filename.body.text,
|
|
6071
|
-
fileSize: trackRow.fileSize,
|
|
6072
|
-
releaseDate: trackRow.releaseDate.body.text,
|
|
6073
|
-
analyzeDate: ensureDate(new Date(trackRow.analyzeDate.body.text)),
|
|
6074
|
-
dateAdded: ensureDate(new Date(trackRow.dateAdded.body.text)),
|
|
6075
|
-
// The analyze file comes in 3 forms
|
|
6076
|
-
//
|
|
6077
|
-
// 1. A `DAT` file, which is missing some extended information, for the older
|
|
6078
|
-
// Pioneer equipment (likely due to memory constraints).
|
|
6079
|
-
//
|
|
6080
|
-
// 2. A `EXT` file which includes colored waveforms and other extended data.
|
|
6081
|
-
//
|
|
6082
|
-
// 3. A `EX2` file -- currently unknown
|
|
6083
|
-
//
|
|
6084
|
-
// We noramlize this path by trimming the DAT extension off. Later we will
|
|
6085
|
-
// try and read whatever is available.
|
|
6086
|
-
analyzePath: analyzePath === null || analyzePath === void 0 ? void 0 : analyzePath.substring(0, analyzePath.length - 4),
|
|
6087
|
-
artworkId: trackRow.artworkId || null,
|
|
6088
|
-
artistId: trackRow.artistId || null,
|
|
6089
|
-
originalArtistId: trackRow.originalArtistId || null,
|
|
6090
|
-
remixerId: trackRow.remixerId || null,
|
|
6091
|
-
composerId: trackRow.composerId || null,
|
|
6092
|
-
albumId: trackRow.albumId || null,
|
|
6093
|
-
labelId: trackRow.labelId || null,
|
|
6094
|
-
genreId: trackRow.genreId || null,
|
|
6095
|
-
colorId: trackRow.colorId || null,
|
|
6096
|
-
keyId: trackRow.keyId || null,
|
|
6097
|
-
// NOTE: There are a few additional columns that will be hydrated through
|
|
6098
|
-
// the analyze files (given the analyzePath) which we do not assign here.
|
|
6099
|
-
beatGrid: null,
|
|
6100
|
-
cueAndLoops: null,
|
|
6101
|
-
waveformHd: null,
|
|
6102
|
-
};
|
|
6103
|
-
return track;
|
|
6104
|
-
}
|
|
6105
|
-
/**
|
|
6106
|
-
* Translates a pdb playlist row entry into a {@link Playlist} entity.
|
|
6107
|
-
*/
|
|
6108
|
-
function createPlaylist(playlistRow) {
|
|
6109
|
-
const playlist = {
|
|
6110
|
-
id: playlistRow.id,
|
|
6111
|
-
name: playlistRow.name.body.text,
|
|
6112
|
-
isFolder: playlistRow.rawIsFolder !== 0,
|
|
6113
|
-
parentId: playlistRow.parentId || null,
|
|
6114
|
-
};
|
|
6115
|
-
return playlist;
|
|
6116
|
-
}
|
|
6117
|
-
/**
|
|
6118
|
-
* Translates a pdb playlist track entry into a {@link PlaylistTrack} entity.
|
|
6119
|
-
*/
|
|
6120
|
-
function createPlaylistEntry(playlistTrackRow) {
|
|
6121
|
-
const entry = {
|
|
6122
|
-
id: playlistTrackRow.id,
|
|
6123
|
-
sortIndex: playlistTrackRow.entryIndex,
|
|
6124
|
-
playlistId: playlistTrackRow.playlistId,
|
|
6125
|
-
trackId: playlistTrackRow.trackId,
|
|
6126
|
-
};
|
|
6127
|
-
return entry;
|
|
6128
|
-
}
|
|
6129
|
-
/**
|
|
6130
|
-
* Translates a pdb artwork entry into a {@link Artwork} entity.
|
|
6131
|
-
*/
|
|
6132
|
-
function createArtworkEntry(artworkRow) {
|
|
6133
|
-
const art = {
|
|
6134
|
-
id: artworkRow.id,
|
|
6135
|
-
path: artworkRow.path.body.text,
|
|
6136
|
-
};
|
|
6137
|
-
return art;
|
|
6138
|
-
}
|
|
6139
|
-
/**
|
|
6140
|
-
* Fill beatgrid data from the ANLZ section
|
|
6141
|
-
*/
|
|
6142
|
-
function makeBeatGrid(data) {
|
|
6143
|
-
return data.body.beats.map((beat) => ({
|
|
6144
|
-
offset: beat.time,
|
|
6145
|
-
bpm: beat.tempo / 100,
|
|
6146
|
-
count: beat.beatNumber,
|
|
6147
|
-
}));
|
|
6148
|
-
}
|
|
6149
|
-
/**
|
|
6150
|
-
* Fill cue and loop data from the ANLZ section
|
|
5388
|
+
* Fill cue and loop data from the ANLZ section
|
|
6151
5389
|
*/
|
|
6152
5390
|
function makeCueAndLoop(data) {
|
|
6153
5391
|
return data.body.cues.map((entry) => {
|
|
@@ -6294,12 +5532,433 @@ function makeWaveformPreview(data) {
|
|
|
6294
5532
|
data: Buffer.from(data.body.data),
|
|
6295
5533
|
};
|
|
6296
5534
|
}
|
|
5535
|
+
/**
|
|
5536
|
+
* Parse 3-band color waveform preview (PWV6 from .2EX files)
|
|
5537
|
+
*/
|
|
5538
|
+
function makeWaveform3BandPreview(data) {
|
|
5539
|
+
return {
|
|
5540
|
+
numEntries: data.body.lenEntries,
|
|
5541
|
+
data: Buffer.from(data.body.entries),
|
|
5542
|
+
};
|
|
5543
|
+
}
|
|
5544
|
+
/**
|
|
5545
|
+
* Parse 3-band color detail waveform (PWV7 from .2EX files)
|
|
5546
|
+
*/
|
|
5547
|
+
function makeWaveform3BandDetail(data) {
|
|
5548
|
+
return {
|
|
5549
|
+
numEntries: data.body.lenEntries,
|
|
5550
|
+
data: Buffer.from(data.body.entries),
|
|
5551
|
+
};
|
|
5552
|
+
}
|
|
5553
|
+
/**
|
|
5554
|
+
* Parse vocal detection config (PWVC from .2EX files)
|
|
5555
|
+
*/
|
|
5556
|
+
function makeVocalConfig(data) {
|
|
5557
|
+
return {
|
|
5558
|
+
thresholdLow: data.body.thresholdLow,
|
|
5559
|
+
thresholdMid: data.body.thresholdMid,
|
|
5560
|
+
thresholdHigh: data.body.thresholdHigh,
|
|
5561
|
+
};
|
|
5562
|
+
}
|
|
5563
|
+
|
|
5564
|
+
|
|
5565
|
+
/***/ },
|
|
5566
|
+
|
|
5567
|
+
/***/ "./src/localdb/rekordbox/entity-creators.ts"
|
|
5568
|
+
/*!**************************************************!*\
|
|
5569
|
+
!*** ./src/localdb/rekordbox/entity-creators.ts ***!
|
|
5570
|
+
\**************************************************/
|
|
5571
|
+
(__unused_webpack_module, exports) {
|
|
5572
|
+
|
|
5573
|
+
"use strict";
|
|
5574
|
+
|
|
5575
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
5576
|
+
exports.makeIdNameHydrator = void 0;
|
|
5577
|
+
exports.createTrack = createTrack;
|
|
5578
|
+
exports.createPlaylist = createPlaylist;
|
|
5579
|
+
exports.createPlaylistEntry = createPlaylistEntry;
|
|
5580
|
+
exports.createArtworkEntry = createArtworkEntry;
|
|
5581
|
+
const ensureDate = (date) => date instanceof Date && !isNaN(date.valueOf()) ? date : undefined;
|
|
5582
|
+
/**
|
|
5583
|
+
* Utility to create a hydrator that hydrates the provided entity with the id
|
|
5584
|
+
* and name properties from the row.
|
|
5585
|
+
*/
|
|
5586
|
+
const makeIdNameHydrator = () => (row) => {
|
|
5587
|
+
var _a;
|
|
5588
|
+
return ({
|
|
5589
|
+
id: row.id,
|
|
5590
|
+
name: (_a = row.name.body.text) !== null && _a !== void 0 ? _a : '',
|
|
5591
|
+
});
|
|
5592
|
+
};
|
|
5593
|
+
exports.makeIdNameHydrator = makeIdNameHydrator;
|
|
5594
|
+
/**
|
|
5595
|
+
* Translates a pdb track row entry to a {@link Track} entity.
|
|
5596
|
+
*/
|
|
5597
|
+
function createTrack(trackRow) {
|
|
5598
|
+
const analyzePath = trackRow.analyzePath.body.text;
|
|
5599
|
+
const track = {
|
|
5600
|
+
id: trackRow.id,
|
|
5601
|
+
title: trackRow.title.body.text,
|
|
5602
|
+
trackNumber: trackRow.trackNumber,
|
|
5603
|
+
discNumber: trackRow.discNumber,
|
|
5604
|
+
duration: trackRow.duration,
|
|
5605
|
+
sampleRate: trackRow.sampleRate,
|
|
5606
|
+
sampleDepth: trackRow.sampleDepth,
|
|
5607
|
+
bitrate: trackRow.bitrate,
|
|
5608
|
+
tempo: trackRow.tempo / 100,
|
|
5609
|
+
playCount: trackRow.playCount,
|
|
5610
|
+
year: trackRow.year,
|
|
5611
|
+
rating: trackRow.rating,
|
|
5612
|
+
mixName: trackRow.mixName.body.text,
|
|
5613
|
+
comment: trackRow.comment.body.text,
|
|
5614
|
+
autoloadHotcues: trackRow.autoloadHotcues.body.text === 'ON',
|
|
5615
|
+
kuvoPublic: trackRow.kuvoPublic.body.text === 'ON',
|
|
5616
|
+
filePath: trackRow.filePath.body.text,
|
|
5617
|
+
fileName: trackRow.filename.body.text,
|
|
5618
|
+
fileSize: trackRow.fileSize,
|
|
5619
|
+
releaseDate: trackRow.releaseDate.body.text,
|
|
5620
|
+
analyzeDate: ensureDate(new Date(trackRow.analyzeDate.body.text)),
|
|
5621
|
+
dateAdded: ensureDate(new Date(trackRow.dateAdded.body.text)),
|
|
5622
|
+
// The analyze file comes in 3 forms
|
|
5623
|
+
//
|
|
5624
|
+
// 1. A `DAT` file, which is missing some extended information, for the older
|
|
5625
|
+
// Pioneer equipment (likely due to memory constraints).
|
|
5626
|
+
//
|
|
5627
|
+
// 2. A `EXT` file which includes colored waveforms and other extended data.
|
|
5628
|
+
//
|
|
5629
|
+
// 3. A `EX2` file -- currently unknown
|
|
5630
|
+
//
|
|
5631
|
+
// We noramlize this path by trimming the DAT extension off. Later we will
|
|
5632
|
+
// try and read whatever is available.
|
|
5633
|
+
analyzePath: analyzePath === null || analyzePath === void 0 ? void 0 : analyzePath.substring(0, analyzePath.length - 4),
|
|
5634
|
+
artworkId: trackRow.artworkId || null,
|
|
5635
|
+
artistId: trackRow.artistId || null,
|
|
5636
|
+
originalArtistId: trackRow.originalArtistId || null,
|
|
5637
|
+
remixerId: trackRow.remixerId || null,
|
|
5638
|
+
composerId: trackRow.composerId || null,
|
|
5639
|
+
albumId: trackRow.albumId || null,
|
|
5640
|
+
labelId: trackRow.labelId || null,
|
|
5641
|
+
genreId: trackRow.genreId || null,
|
|
5642
|
+
colorId: trackRow.colorId || null,
|
|
5643
|
+
keyId: trackRow.keyId || null,
|
|
5644
|
+
// NOTE: There are a few additional columns that will be hydrated through
|
|
5645
|
+
// the analyze files (given the analyzePath) which we do not assign here.
|
|
5646
|
+
beatGrid: null,
|
|
5647
|
+
cueAndLoops: null,
|
|
5648
|
+
waveformHd: null,
|
|
5649
|
+
};
|
|
5650
|
+
return track;
|
|
5651
|
+
}
|
|
5652
|
+
/**
|
|
5653
|
+
* Translates a pdb playlist row entry into a {@link Playlist} entity.
|
|
5654
|
+
*/
|
|
5655
|
+
function createPlaylist(playlistRow) {
|
|
5656
|
+
const playlist = {
|
|
5657
|
+
id: playlistRow.id,
|
|
5658
|
+
name: playlistRow.name.body.text,
|
|
5659
|
+
isFolder: playlistRow.rawIsFolder !== 0,
|
|
5660
|
+
parentId: playlistRow.parentId || null,
|
|
5661
|
+
};
|
|
5662
|
+
return playlist;
|
|
5663
|
+
}
|
|
5664
|
+
/**
|
|
5665
|
+
* Translates a pdb playlist track entry into a {@link PlaylistTrack} entity.
|
|
5666
|
+
*/
|
|
5667
|
+
function createPlaylistEntry(playlistTrackRow) {
|
|
5668
|
+
const entry = {
|
|
5669
|
+
id: playlistTrackRow.id,
|
|
5670
|
+
sortIndex: playlistTrackRow.entryIndex,
|
|
5671
|
+
playlistId: playlistTrackRow.playlistId,
|
|
5672
|
+
trackId: playlistTrackRow.trackId,
|
|
5673
|
+
};
|
|
5674
|
+
return entry;
|
|
5675
|
+
}
|
|
5676
|
+
/**
|
|
5677
|
+
* Translates a pdb artwork entry into a {@link Artwork} entity.
|
|
5678
|
+
*/
|
|
5679
|
+
function createArtworkEntry(artworkRow) {
|
|
5680
|
+
const art = {
|
|
5681
|
+
id: artworkRow.id,
|
|
5682
|
+
path: artworkRow.path.body.text,
|
|
5683
|
+
};
|
|
5684
|
+
return art;
|
|
5685
|
+
}
|
|
5686
|
+
|
|
5687
|
+
|
|
5688
|
+
/***/ },
|
|
5689
|
+
|
|
5690
|
+
/***/ "./src/localdb/rekordbox/hydrator.ts"
|
|
5691
|
+
/*!*******************************************!*\
|
|
5692
|
+
!*** ./src/localdb/rekordbox/hydrator.ts ***!
|
|
5693
|
+
\*******************************************/
|
|
5694
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
5695
|
+
|
|
5696
|
+
"use strict";
|
|
5697
|
+
|
|
5698
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5699
|
+
if (k2 === undefined) k2 = k;
|
|
5700
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5701
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
5702
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
5703
|
+
}
|
|
5704
|
+
Object.defineProperty(o, k2, desc);
|
|
5705
|
+
}) : (function(o, m, k, k2) {
|
|
5706
|
+
if (k2 === undefined) k2 = k;
|
|
5707
|
+
o[k2] = m[k];
|
|
5708
|
+
}));
|
|
5709
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
5710
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
5711
|
+
}) : function(o, v) {
|
|
5712
|
+
o["default"] = v;
|
|
5713
|
+
});
|
|
5714
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
5715
|
+
var ownKeys = function(o) {
|
|
5716
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
5717
|
+
var ar = [];
|
|
5718
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
5719
|
+
return ar;
|
|
5720
|
+
};
|
|
5721
|
+
return ownKeys(o);
|
|
5722
|
+
};
|
|
5723
|
+
return function (mod) {
|
|
5724
|
+
if (mod && mod.__esModule) return mod;
|
|
5725
|
+
var result = {};
|
|
5726
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
5727
|
+
__setModuleDefault(result, mod);
|
|
5728
|
+
return result;
|
|
5729
|
+
};
|
|
5730
|
+
})();
|
|
5731
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
5732
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
5733
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5734
|
+
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");
|
|
5735
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
5736
|
+
};
|
|
5737
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
5738
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
5739
|
+
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");
|
|
5740
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
5741
|
+
};
|
|
5742
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5743
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5744
|
+
};
|
|
5745
|
+
var _RekordboxHydrator_orm, _RekordboxHydrator_onProgress;
|
|
5746
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
5747
|
+
exports.RekordboxHydrator = void 0;
|
|
5748
|
+
const kaitai_struct_1 = __webpack_require__(/*! kaitai-struct */ "kaitai-struct");
|
|
5749
|
+
const rekordbox_pdb_ksy_1 = __importDefault(__webpack_require__(/*! src/localdb/kaitai/rekordbox_pdb.ksy */ "./src/localdb/kaitai/rekordbox_pdb.ksy"));
|
|
5750
|
+
const Telemetry = __importStar(__webpack_require__(/*! src/utils/telemetry */ "./src/utils/telemetry.ts"));
|
|
5751
|
+
const table_mappings_1 = __webpack_require__(/*! ./table-mappings */ "./src/localdb/rekordbox/table-mappings.ts");
|
|
5752
|
+
/**
|
|
5753
|
+
* This service provides utilities for translating rekordbox database (pdb_ and
|
|
5754
|
+
* analysis (ANLZ) files into the common entity types used in this library.
|
|
5755
|
+
*/
|
|
5756
|
+
class RekordboxHydrator {
|
|
5757
|
+
constructor({ orm, onProgress }) {
|
|
5758
|
+
_RekordboxHydrator_orm.set(this, void 0);
|
|
5759
|
+
_RekordboxHydrator_onProgress.set(this, void 0);
|
|
5760
|
+
__classPrivateFieldSet(this, _RekordboxHydrator_orm, orm, "f");
|
|
5761
|
+
__classPrivateFieldSet(this, _RekordboxHydrator_onProgress, onProgress !== null && onProgress !== void 0 ? onProgress : (() => null), "f");
|
|
5762
|
+
}
|
|
5763
|
+
/**
|
|
5764
|
+
* Extract entries from a rekordbox pdb file and hydrate the passed database
|
|
5765
|
+
* connection with entities derived from the rekordbox entries.
|
|
5766
|
+
*/
|
|
5767
|
+
async hydrateFromPdb(pdbData, span) {
|
|
5768
|
+
const tx = span
|
|
5769
|
+
? span.startChild({ op: 'hydrateFromPdb' })
|
|
5770
|
+
: Telemetry.startTransaction({ name: 'hydrateFromPdb' });
|
|
5771
|
+
const parseTx = tx.startChild({ op: 'parsePdbData', data: { size: pdbData.length } });
|
|
5772
|
+
const stream = new kaitai_struct_1.KaitaiStream(pdbData);
|
|
5773
|
+
const db = new rekordbox_pdb_ksy_1.default(stream);
|
|
5774
|
+
parseTx.finish();
|
|
5775
|
+
const hydrateTx = tx.startChild({ op: 'hydration' });
|
|
5776
|
+
await Promise.all(db.tables.map((table) => this.hydrateFromTable(table, hydrateTx)));
|
|
5777
|
+
hydrateTx.finish();
|
|
5778
|
+
tx.finish();
|
|
5779
|
+
}
|
|
5780
|
+
/**
|
|
5781
|
+
* Hydrate the database with entities from the provided RekordboxPdb table.
|
|
5782
|
+
* See pdbEntityCreators for how tables are mapped into database entities.
|
|
5783
|
+
*/
|
|
5784
|
+
async hydrateFromTable(table, span) {
|
|
5785
|
+
const tableName = table_mappings_1.pdbTables[table.type];
|
|
5786
|
+
const createObject = table_mappings_1.pdbEntityCreators[table.type];
|
|
5787
|
+
const tx = span.startChild({ op: 'hydrateFromTable', description: tableName });
|
|
5788
|
+
if (createObject === undefined) {
|
|
5789
|
+
return;
|
|
5790
|
+
}
|
|
5791
|
+
let totalSaved = 0;
|
|
5792
|
+
let totalItems = 0;
|
|
5793
|
+
for (const _row of tableRows(table)) {
|
|
5794
|
+
void _row; // Intentionally unused - just counting
|
|
5795
|
+
totalItems++;
|
|
5796
|
+
}
|
|
5797
|
+
tx.setData('items', totalItems);
|
|
5798
|
+
// Use transaction for bulk inserts (10-100x faster)
|
|
5799
|
+
__classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").beginTransaction();
|
|
5800
|
+
try {
|
|
5801
|
+
for (const row of tableRows(table)) {
|
|
5802
|
+
const entity = createObject(row);
|
|
5803
|
+
__classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").insertEntity(tableName, entity);
|
|
5804
|
+
totalSaved++;
|
|
5805
|
+
// Report progress and yield every 100 rows (instead of every row)
|
|
5806
|
+
if (totalSaved % 100 === 0 || totalSaved === totalItems) {
|
|
5807
|
+
__classPrivateFieldGet(this, _RekordboxHydrator_onProgress, "f").call(this, { complete: totalSaved, table: tableName, total: totalItems });
|
|
5808
|
+
// Yield to event loop periodically to keep UI responsive
|
|
5809
|
+
await new Promise(r => setTimeout(r, 0));
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
}
|
|
5813
|
+
finally {
|
|
5814
|
+
__classPrivateFieldGet(this, _RekordboxHydrator_orm, "f").commit();
|
|
5815
|
+
}
|
|
5816
|
+
tx.finish();
|
|
5817
|
+
}
|
|
5818
|
+
}
|
|
5819
|
+
exports.RekordboxHydrator = RekordboxHydrator;
|
|
5820
|
+
_RekordboxHydrator_orm = new WeakMap(), _RekordboxHydrator_onProgress = new WeakMap();
|
|
5821
|
+
/**
|
|
5822
|
+
* Utility generator that pages through a table and yields every present row.
|
|
5823
|
+
* This flattens the concept of rowGroups and refs.
|
|
5824
|
+
*/
|
|
5825
|
+
function* tableRows(table) {
|
|
5826
|
+
const { firstPage, lastPage } = table;
|
|
5827
|
+
let pageRef = firstPage;
|
|
5828
|
+
do {
|
|
5829
|
+
const page = pageRef.body;
|
|
5830
|
+
// Adjust our page ref for the next iteration. We do this early in our loop
|
|
5831
|
+
// so we can break without having to remember to update for the next iter.
|
|
5832
|
+
pageRef = page.nextPage;
|
|
5833
|
+
// Ignore non-data pages. Not sure what these are for?
|
|
5834
|
+
if (!page.isDataPage) {
|
|
5835
|
+
continue;
|
|
5836
|
+
}
|
|
5837
|
+
const rows = page.rowGroups
|
|
5838
|
+
.map((group) => group.rows)
|
|
5839
|
+
.flat()
|
|
5840
|
+
.filter((row) => row.present);
|
|
5841
|
+
for (const row of rows) {
|
|
5842
|
+
yield row.body;
|
|
5843
|
+
}
|
|
5844
|
+
} while (pageRef.index <= lastPage.index);
|
|
5845
|
+
}
|
|
5846
|
+
|
|
5847
|
+
|
|
5848
|
+
/***/ },
|
|
5849
|
+
|
|
5850
|
+
/***/ "./src/localdb/rekordbox/index.ts"
|
|
5851
|
+
/*!****************************************!*\
|
|
5852
|
+
!*** ./src/localdb/rekordbox/index.ts ***!
|
|
5853
|
+
\****************************************/
|
|
5854
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
5855
|
+
|
|
5856
|
+
"use strict";
|
|
5857
|
+
|
|
5858
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5859
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5860
|
+
};
|
|
5861
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
5862
|
+
exports.hydrateDatabase = hydrateDatabase;
|
|
5863
|
+
exports.loadAnlz = loadAnlz;
|
|
5864
|
+
const kaitai_struct_1 = __webpack_require__(/*! kaitai-struct */ "kaitai-struct");
|
|
5865
|
+
const rekordbox_anlz_ksy_1 = __importDefault(__webpack_require__(/*! src/localdb/kaitai/rekordbox_anlz.ksy */ "./src/localdb/kaitai/rekordbox_anlz.ksy"));
|
|
5866
|
+
const anlz_parsers_1 = __webpack_require__(/*! ./anlz-parsers */ "./src/localdb/rekordbox/anlz-parsers.ts");
|
|
5867
|
+
const hydrator_1 = __webpack_require__(/*! ./hydrator */ "./src/localdb/rekordbox/hydrator.ts");
|
|
5868
|
+
const { SectionTags } = rekordbox_anlz_ksy_1.default;
|
|
5869
|
+
/**
|
|
5870
|
+
* Given a rekordbox pdb file contents. This function will hydrate the provided
|
|
5871
|
+
* database with all entities from the Rekordbox database. This includes all
|
|
5872
|
+
* track metadata, including analyzed metadata (such as beatgrids and waveforms).
|
|
5873
|
+
*/
|
|
5874
|
+
async function hydrateDatabase({ pdbData, span, ...options }) {
|
|
5875
|
+
const hydrator = new hydrator_1.RekordboxHydrator(options);
|
|
5876
|
+
await hydrator.hydrateFromPdb(pdbData, span);
|
|
5877
|
+
}
|
|
5878
|
+
/**
|
|
5879
|
+
* Loads the ANLZ data of a Track entity from the analyzePath.
|
|
5880
|
+
*/
|
|
5881
|
+
async function loadAnlz(track, type, anlzResolver) {
|
|
5882
|
+
const path = `${track.analyzePath}.${type}`;
|
|
5883
|
+
const anlzData = await anlzResolver(path);
|
|
5884
|
+
const stream = new kaitai_struct_1.KaitaiStream(anlzData);
|
|
5885
|
+
const anlz = new rekordbox_anlz_ksy_1.default(stream);
|
|
5886
|
+
const result = {};
|
|
5887
|
+
const resultDat = result;
|
|
5888
|
+
const resultExt = result;
|
|
5889
|
+
const result2ex = result;
|
|
5890
|
+
for (const section of anlz.sections) {
|
|
5891
|
+
switch (section.fourcc) {
|
|
5892
|
+
case SectionTags.BEAT_GRID:
|
|
5893
|
+
resultDat.beatGrid = (0, anlz_parsers_1.makeBeatGrid)(section);
|
|
5894
|
+
break;
|
|
5895
|
+
case SectionTags.CUES:
|
|
5896
|
+
resultDat.cueAndLoops = (0, anlz_parsers_1.makeCueAndLoop)(section);
|
|
5897
|
+
break;
|
|
5898
|
+
case SectionTags.CUES_2:
|
|
5899
|
+
resultExt.extendedCues = (0, anlz_parsers_1.makeExtendedCues)(section);
|
|
5900
|
+
break;
|
|
5901
|
+
case SectionTags.WAVE_PREVIEW:
|
|
5902
|
+
resultDat.waveformPreview = (0, anlz_parsers_1.makeWaveformPreview)(section);
|
|
5903
|
+
break;
|
|
5904
|
+
case SectionTags.WAVE_TINY:
|
|
5905
|
+
resultDat.waveformTiny = (0, anlz_parsers_1.makeWaveformPreview)(section);
|
|
5906
|
+
break;
|
|
5907
|
+
case SectionTags.WAVE_SCROLL:
|
|
5908
|
+
resultExt.waveformDetail = Buffer.from(section.body.entries);
|
|
5909
|
+
break;
|
|
5910
|
+
case SectionTags.WAVE_COLOR_PREVIEW:
|
|
5911
|
+
resultExt.waveformColorPreview = Buffer.from(section.body.entries);
|
|
5912
|
+
break;
|
|
5913
|
+
case SectionTags.WAVE_COLOR_SCROLL:
|
|
5914
|
+
resultExt.waveformHd = (0, anlz_parsers_1.makeWaveformHd)(section);
|
|
5915
|
+
break;
|
|
5916
|
+
case SectionTags.SONG_STRUCTURE:
|
|
5917
|
+
resultExt.songStructure = (0, anlz_parsers_1.makeSongStructure)(section);
|
|
5918
|
+
break;
|
|
5919
|
+
case SectionTags.WAVE_COLOR_3BAND_PREVIEW:
|
|
5920
|
+
result2ex.waveform3BandPreview = (0, anlz_parsers_1.makeWaveform3BandPreview)(section);
|
|
5921
|
+
break;
|
|
5922
|
+
case SectionTags.WAVE_COLOR_3BAND_DETAIL:
|
|
5923
|
+
result2ex.waveform3BandDetail = (0, anlz_parsers_1.makeWaveform3BandDetail)(section);
|
|
5924
|
+
break;
|
|
5925
|
+
case SectionTags.VOCAL_CONFIG:
|
|
5926
|
+
result2ex.vocalConfig = (0, anlz_parsers_1.makeVocalConfig)(section);
|
|
5927
|
+
break;
|
|
5928
|
+
// VBR and PATH tags are defined but not currently extracted
|
|
5929
|
+
// as they're not commonly needed in the application
|
|
5930
|
+
case SectionTags.VBR:
|
|
5931
|
+
case SectionTags.PATH:
|
|
5932
|
+
break;
|
|
5933
|
+
}
|
|
5934
|
+
}
|
|
5935
|
+
return result;
|
|
5936
|
+
}
|
|
5937
|
+
|
|
5938
|
+
|
|
5939
|
+
/***/ },
|
|
5940
|
+
|
|
5941
|
+
/***/ "./src/localdb/rekordbox/table-mappings.ts"
|
|
5942
|
+
/*!*************************************************!*\
|
|
5943
|
+
!*** ./src/localdb/rekordbox/table-mappings.ts ***!
|
|
5944
|
+
\*************************************************/
|
|
5945
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
5946
|
+
|
|
5947
|
+
"use strict";
|
|
5948
|
+
|
|
5949
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
5950
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5951
|
+
};
|
|
5952
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
5953
|
+
exports.pdbEntityCreators = exports.pdbTables = void 0;
|
|
5954
|
+
const rekordbox_pdb_ksy_1 = __importDefault(__webpack_require__(/*! src/localdb/kaitai/rekordbox_pdb.ksy */ "./src/localdb/kaitai/rekordbox_pdb.ksy"));
|
|
5955
|
+
const orm_1 = __webpack_require__(/*! src/localdb/orm */ "./src/localdb/orm.ts");
|
|
5956
|
+
const entity_creators_1 = __webpack_require__(/*! ./entity-creators */ "./src/localdb/rekordbox/entity-creators.ts");
|
|
6297
5957
|
const { PageType } = rekordbox_pdb_ksy_1.default;
|
|
6298
|
-
const { SectionTags } = rekordbox_anlz_ksy_1.default;
|
|
6299
5958
|
/**
|
|
6300
5959
|
* Maps rekordbox pdb table types to orm table names.
|
|
6301
5960
|
*/
|
|
6302
|
-
|
|
5961
|
+
exports.pdbTables = {
|
|
6303
5962
|
[PageType.TRACKS]: orm_1.Table.Track,
|
|
6304
5963
|
[PageType.ARTISTS]: orm_1.Table.Artist,
|
|
6305
5964
|
[PageType.GENRES]: orm_1.Table.Genre,
|
|
@@ -6315,17 +5974,17 @@ const pdbTables = {
|
|
|
6315
5974
|
* Maps rekordbox pdb table types to functions that create entity objects for
|
|
6316
5975
|
* the passed pdb row.
|
|
6317
5976
|
*/
|
|
6318
|
-
|
|
6319
|
-
[PageType.TRACKS]: createTrack,
|
|
6320
|
-
[PageType.ARTISTS]: makeIdNameHydrator(),
|
|
6321
|
-
[PageType.GENRES]: makeIdNameHydrator(),
|
|
6322
|
-
[PageType.ALBUMS]: makeIdNameHydrator(),
|
|
6323
|
-
[PageType.LABELS]: makeIdNameHydrator(),
|
|
6324
|
-
[PageType.COLORS]: makeIdNameHydrator(),
|
|
6325
|
-
[PageType.KEYS]: makeIdNameHydrator(),
|
|
6326
|
-
[PageType.ARTWORK]: createArtworkEntry,
|
|
6327
|
-
[PageType.PLAYLIST_TREE]: createPlaylist,
|
|
6328
|
-
[PageType.PLAYLIST_ENTRIES]: createPlaylistEntry,
|
|
5977
|
+
exports.pdbEntityCreators = {
|
|
5978
|
+
[PageType.TRACKS]: entity_creators_1.createTrack,
|
|
5979
|
+
[PageType.ARTISTS]: (0, entity_creators_1.makeIdNameHydrator)(),
|
|
5980
|
+
[PageType.GENRES]: (0, entity_creators_1.makeIdNameHydrator)(),
|
|
5981
|
+
[PageType.ALBUMS]: (0, entity_creators_1.makeIdNameHydrator)(),
|
|
5982
|
+
[PageType.LABELS]: (0, entity_creators_1.makeIdNameHydrator)(),
|
|
5983
|
+
[PageType.COLORS]: (0, entity_creators_1.makeIdNameHydrator)(),
|
|
5984
|
+
[PageType.KEYS]: (0, entity_creators_1.makeIdNameHydrator)(),
|
|
5985
|
+
[PageType.ARTWORK]: entity_creators_1.createArtworkEntry,
|
|
5986
|
+
[PageType.PLAYLIST_TREE]: entity_creators_1.createPlaylist,
|
|
5987
|
+
[PageType.PLAYLIST_ENTRIES]: entity_creators_1.createPlaylistEntry,
|
|
6329
5988
|
// TODO: Register PageType.HISTORY
|
|
6330
5989
|
};
|
|
6331
5990
|
|
|
@@ -6466,6 +6125,110 @@ const makeCueLoopEntry = (isCue, isLoop, offset, length, button) => button !== f
|
|
|
6466
6125
|
exports.makeCueLoopEntry = makeCueLoopEntry;
|
|
6467
6126
|
|
|
6468
6127
|
|
|
6128
|
+
/***/ },
|
|
6129
|
+
|
|
6130
|
+
/***/ "./src/logger.ts"
|
|
6131
|
+
/*!***********************!*\
|
|
6132
|
+
!*** ./src/logger.ts ***!
|
|
6133
|
+
\***********************/
|
|
6134
|
+
(__unused_webpack_module, exports) {
|
|
6135
|
+
|
|
6136
|
+
"use strict";
|
|
6137
|
+
|
|
6138
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
6139
|
+
exports.noopLogger = void 0;
|
|
6140
|
+
exports.noopLogger = {
|
|
6141
|
+
trace() { },
|
|
6142
|
+
debug() { },
|
|
6143
|
+
info() { },
|
|
6144
|
+
warn() { },
|
|
6145
|
+
error() { },
|
|
6146
|
+
};
|
|
6147
|
+
|
|
6148
|
+
|
|
6149
|
+
/***/ },
|
|
6150
|
+
|
|
6151
|
+
/***/ "./src/metadata.ts"
|
|
6152
|
+
/*!*************************!*\
|
|
6153
|
+
!*** ./src/metadata.ts ***!
|
|
6154
|
+
\*************************/
|
|
6155
|
+
(__unused_webpack_module, exports, __webpack_require__) {
|
|
6156
|
+
|
|
6157
|
+
"use strict";
|
|
6158
|
+
|
|
6159
|
+
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
6160
|
+
exports.extractFullMetadata = extractFullMetadata;
|
|
6161
|
+
exports.isMetadataExtractionSupported = isMetadataExtractionSupported;
|
|
6162
|
+
exports.extractMetadataFromDevice = extractMetadataFromDevice;
|
|
6163
|
+
const metadata_connect_1 = __webpack_require__(/*! metadata-connect */ "metadata-connect");
|
|
6164
|
+
const nfs_1 = __webpack_require__(/*! ./nfs */ "./src/nfs/index.ts");
|
|
6165
|
+
const reader_1 = __webpack_require__(/*! ./artwork/reader */ "./src/artwork/reader.ts");
|
|
6166
|
+
/**
|
|
6167
|
+
* Extract full metadata from an audio file using a FileReader.
|
|
6168
|
+
*
|
|
6169
|
+
* This is the low-level API that works with any FileReader implementation.
|
|
6170
|
+
* For extracting from a Pro DJ Link device, use extractMetadataFromDevice.
|
|
6171
|
+
*
|
|
6172
|
+
* @param reader - FileReader interface for reading file data
|
|
6173
|
+
* @returns Extracted metadata, or null if extraction fails
|
|
6174
|
+
*/
|
|
6175
|
+
async function extractFullMetadata(reader) {
|
|
6176
|
+
return (0, metadata_connect_1.extractMetadata)(reader);
|
|
6177
|
+
}
|
|
6178
|
+
/**
|
|
6179
|
+
* Check if metadata extraction is supported for a file extension.
|
|
6180
|
+
*
|
|
6181
|
+
* @param extension - File extension (with or without leading dot)
|
|
6182
|
+
* @returns true if the extension is supported
|
|
6183
|
+
*/
|
|
6184
|
+
function isMetadataExtractionSupported(extension) {
|
|
6185
|
+
return (0, metadata_connect_1.isExtensionSupported)(extension);
|
|
6186
|
+
}
|
|
6187
|
+
/**
|
|
6188
|
+
* Extract full metadata from an audio file on a Pro DJ Link device.
|
|
6189
|
+
*
|
|
6190
|
+
* This function reads only the necessary bytes from the file header,
|
|
6191
|
+
* avoiding the need to transfer entire audio files over the network.
|
|
6192
|
+
*
|
|
6193
|
+
* @param device - The Pro DJ Link device
|
|
6194
|
+
* @param slot - The media slot containing the file (USB, SD, etc.)
|
|
6195
|
+
* @param filePath - The file path on the device
|
|
6196
|
+
* @returns Extracted metadata, or null if extraction fails
|
|
6197
|
+
*
|
|
6198
|
+
* @example
|
|
6199
|
+
* ```typescript
|
|
6200
|
+
* // Extract metadata when a track is loaded
|
|
6201
|
+
* const metadata = await extractMetadataFromDevice(device, slot, filePath);
|
|
6202
|
+
* if (metadata) {
|
|
6203
|
+
* console.log(metadata.title, metadata.artist, metadata.bpm);
|
|
6204
|
+
* if (metadata.artwork) {
|
|
6205
|
+
* // Display artwork
|
|
6206
|
+
* }
|
|
6207
|
+
* }
|
|
6208
|
+
* ```
|
|
6209
|
+
*/
|
|
6210
|
+
async function extractMetadataFromDevice(device, slot, filePath) {
|
|
6211
|
+
var _a, _b;
|
|
6212
|
+
// Get file extension and check if supported
|
|
6213
|
+
const extension = (_b = (_a = filePath.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
|
|
6214
|
+
if (!(0, metadata_connect_1.isExtensionSupported)(extension)) {
|
|
6215
|
+
return null;
|
|
6216
|
+
}
|
|
6217
|
+
try {
|
|
6218
|
+
const fileInfo = await (0, nfs_1.getFileInfo)({ device, slot, path: filePath });
|
|
6219
|
+
if (fileInfo.size === 0) {
|
|
6220
|
+
return null;
|
|
6221
|
+
}
|
|
6222
|
+
const reader = (0, reader_1.createNfsFileReader)(device, slot, filePath, fileInfo.size);
|
|
6223
|
+
return (0, metadata_connect_1.extractMetadata)(reader);
|
|
6224
|
+
}
|
|
6225
|
+
catch {
|
|
6226
|
+
// Return null on any error (file not found, network issues, etc.)
|
|
6227
|
+
return null;
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6230
|
+
|
|
6231
|
+
|
|
6469
6232
|
/***/ },
|
|
6470
6233
|
|
|
6471
6234
|
/***/ "./src/mixstatus/index.ts"
|
|
@@ -6487,7 +6250,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
6487
6250
|
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");
|
|
6488
6251
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
6489
6252
|
};
|
|
6490
|
-
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;
|
|
6253
|
+
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;
|
|
6491
6254
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
6492
6255
|
exports.MixstatusProcessor = void 0;
|
|
6493
6256
|
const events_1 = __webpack_require__(/*! events */ "events");
|
|
@@ -6543,6 +6306,14 @@ class MixstatusProcessor {
|
|
|
6543
6306
|
* Records which players have been reported as 'live'
|
|
6544
6307
|
*/
|
|
6545
6308
|
_MixstatusProcessor_livePlayers.set(this, new Set());
|
|
6309
|
+
/**
|
|
6310
|
+
* Records the last trackId emitted as 'nowPlaying' for each device. Used to
|
|
6311
|
+
* suppress duplicate emissions when a deck is briefly cued/loaded and then
|
|
6312
|
+
* resumed on the same track (a common pattern for House/Techno DJs who
|
|
6313
|
+
* cue-juggle mid-track). The entry is replaced when a different trackId is
|
|
6314
|
+
* emitted on the same deck, so subsequent track loads still emit normally.
|
|
6315
|
+
*/
|
|
6316
|
+
_MixstatusProcessor_lastEmittedTrackId.set(this, new Map());
|
|
6546
6317
|
/**
|
|
6547
6318
|
* Incidates if we're currentiny in an active DJ set
|
|
6548
6319
|
*/
|
|
@@ -6577,6 +6348,14 @@ class MixstatusProcessor {
|
|
|
6577
6348
|
if (__classPrivateFieldGet(this, _MixstatusProcessor_livePlayers, "f").has(deviceId)) {
|
|
6578
6349
|
return;
|
|
6579
6350
|
}
|
|
6351
|
+
// Suppress duplicate nowPlaying for the same track on the same deck. This
|
|
6352
|
+
// can happen when a DJ cues mid-track and resumes (Cued/Loading/Ended are
|
|
6353
|
+
// all stoppingStates that drop the deck from livePlayers, so the next play
|
|
6354
|
+
// of the same trackId would otherwise re-emit). When a different track is
|
|
6355
|
+
// loaded, the trackId differs and the new emission proceeds normally.
|
|
6356
|
+
if (__classPrivateFieldGet(this, _MixstatusProcessor_lastEmittedTrackId, "f").get(deviceId) === state.trackId) {
|
|
6357
|
+
return;
|
|
6358
|
+
}
|
|
6580
6359
|
if (!__classPrivateFieldGet(this, _MixstatusProcessor_isSetActive, "f")) {
|
|
6581
6360
|
__classPrivateFieldSet(this, _MixstatusProcessor_isSetActive, true, "f");
|
|
6582
6361
|
__classPrivateFieldGet(this, _MixstatusProcessor_emitter, "f").emit('setStarted');
|
|
@@ -6585,6 +6364,7 @@ class MixstatusProcessor {
|
|
|
6585
6364
|
__classPrivateFieldGet(this, _MixstatusProcessor_cancelSetEnding, "f").call(this);
|
|
6586
6365
|
}
|
|
6587
6366
|
__classPrivateFieldGet(this, _MixstatusProcessor_livePlayers, "f").add(deviceId);
|
|
6367
|
+
__classPrivateFieldGet(this, _MixstatusProcessor_lastEmittedTrackId, "f").set(deviceId, state.trackId);
|
|
6588
6368
|
__classPrivateFieldGet(this, _MixstatusProcessor_emitter, "f").emit('nowPlaying', state);
|
|
6589
6369
|
});
|
|
6590
6370
|
/**
|
|
@@ -6778,7 +6558,7 @@ class MixstatusProcessor {
|
|
|
6778
6558
|
}
|
|
6779
6559
|
}
|
|
6780
6560
|
exports.MixstatusProcessor = MixstatusProcessor;
|
|
6781
|
-
_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();
|
|
6561
|
+
_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();
|
|
6782
6562
|
|
|
6783
6563
|
|
|
6784
6564
|
/***/ },
|
|
@@ -6869,7 +6649,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
6869
6649
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6870
6650
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
6871
6651
|
};
|
|
6872
|
-
var _ProlinkNetwork_state, _ProlinkNetwork_announceSocket, _ProlinkNetwork_beatSocket, _ProlinkNetwork_statusSocket, _ProlinkNetwork_deviceManager, _ProlinkNetwork_statusEmitter, _ProlinkNetwork_positionEmitter, _ProlinkNetwork_config, _ProlinkNetwork_connection, _ProlinkNetwork_mixstatus;
|
|
6652
|
+
var _ProlinkNetwork_state, _ProlinkNetwork_announceSocket, _ProlinkNetwork_beatSocket, _ProlinkNetwork_statusSocket, _ProlinkNetwork_deviceManager, _ProlinkNetwork_statusEmitter, _ProlinkNetwork_positionEmitter, _ProlinkNetwork_logger, _ProlinkNetwork_config, _ProlinkNetwork_connection, _ProlinkNetwork_mixstatus;
|
|
6873
6653
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
6874
6654
|
exports.ProlinkNetwork = void 0;
|
|
6875
6655
|
exports.bringOnline = bringOnline;
|
|
@@ -6879,6 +6659,7 @@ const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts
|
|
|
6879
6659
|
const control_1 = __importDefault(__webpack_require__(/*! src/control */ "./src/control/index.ts"));
|
|
6880
6660
|
const db_1 = __importDefault(__webpack_require__(/*! src/db */ "./src/db/index.ts"));
|
|
6881
6661
|
const devices_1 = __importDefault(__webpack_require__(/*! src/devices */ "./src/devices/index.ts"));
|
|
6662
|
+
const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
|
|
6882
6663
|
const localdb_1 = __importDefault(__webpack_require__(/*! src/localdb */ "./src/localdb/index.ts"));
|
|
6883
6664
|
const mixstatus_1 = __webpack_require__(/*! src/mixstatus */ "./src/mixstatus/index.ts");
|
|
6884
6665
|
const remotedb_1 = __importDefault(__webpack_require__(/*! src/remotedb */ "./src/remotedb/index.ts"));
|
|
@@ -6909,6 +6690,9 @@ async function bringOnline(config) {
|
|
|
6909
6690
|
await (0, udp_1.udpBind)(announceSocket, constants_1.ANNOUNCE_PORT, '0.0.0.0');
|
|
6910
6691
|
await (0, udp_1.udpBind)(beatSocket, constants_1.BEAT_PORT, '0.0.0.0');
|
|
6911
6692
|
await (0, udp_1.udpBind)(statusSocket, constants_1.STATUS_PORT, '0.0.0.0');
|
|
6693
|
+
// Enable broadcast so the announcer can reach the whole subnet before any
|
|
6694
|
+
// devices have been discovered.
|
|
6695
|
+
announceSocket.setBroadcast(true);
|
|
6912
6696
|
}
|
|
6913
6697
|
catch (err) {
|
|
6914
6698
|
Telemetry.captureException(err);
|
|
@@ -6936,6 +6720,7 @@ class ProlinkNetwork {
|
|
|
6936
6720
|
* @internal
|
|
6937
6721
|
*/
|
|
6938
6722
|
constructor({ config, announceSocket, beatSocket, statusSocket, deviceManager, statusEmitter, positionEmitter, }) {
|
|
6723
|
+
var _a;
|
|
6939
6724
|
_ProlinkNetwork_state.set(this, types_1.NetworkState.Online);
|
|
6940
6725
|
_ProlinkNetwork_announceSocket.set(this, void 0);
|
|
6941
6726
|
_ProlinkNetwork_beatSocket.set(this, void 0);
|
|
@@ -6943,10 +6728,12 @@ class ProlinkNetwork {
|
|
|
6943
6728
|
_ProlinkNetwork_deviceManager.set(this, void 0);
|
|
6944
6729
|
_ProlinkNetwork_statusEmitter.set(this, void 0);
|
|
6945
6730
|
_ProlinkNetwork_positionEmitter.set(this, void 0);
|
|
6731
|
+
_ProlinkNetwork_logger.set(this, void 0);
|
|
6946
6732
|
_ProlinkNetwork_config.set(this, void 0);
|
|
6947
6733
|
_ProlinkNetwork_connection.set(this, void 0);
|
|
6948
6734
|
_ProlinkNetwork_mixstatus.set(this, void 0);
|
|
6949
6735
|
__classPrivateFieldSet(this, _ProlinkNetwork_config, config !== null && config !== void 0 ? config : null, "f");
|
|
6736
|
+
__classPrivateFieldSet(this, _ProlinkNetwork_logger, (_a = config === null || config === void 0 ? void 0 : config.logger) !== null && _a !== void 0 ? _a : logger_1.noopLogger, "f");
|
|
6950
6737
|
__classPrivateFieldSet(this, _ProlinkNetwork_announceSocket, announceSocket, "f");
|
|
6951
6738
|
__classPrivateFieldSet(this, _ProlinkNetwork_beatSocket, beatSocket, "f");
|
|
6952
6739
|
__classPrivateFieldSet(this, _ProlinkNetwork_statusSocket, statusSocket, "f");
|
|
@@ -6966,6 +6753,9 @@ class ProlinkNetwork {
|
|
|
6966
6753
|
*/
|
|
6967
6754
|
configure(config) {
|
|
6968
6755
|
__classPrivateFieldSet(this, _ProlinkNetwork_config, { ...__classPrivateFieldGet(this, _ProlinkNetwork_config, "f"), ...config }, "f");
|
|
6756
|
+
if (config.logger !== undefined) {
|
|
6757
|
+
__classPrivateFieldSet(this, _ProlinkNetwork_logger, config.logger, "f");
|
|
6758
|
+
}
|
|
6969
6759
|
}
|
|
6970
6760
|
/**
|
|
6971
6761
|
* Wait for another device to show up on the network to determine which network
|
|
@@ -6999,7 +6789,7 @@ class ProlinkNetwork {
|
|
|
6999
6789
|
* or manual configuration). This will then initialize all the network services.
|
|
7000
6790
|
*/
|
|
7001
6791
|
connect() {
|
|
7002
|
-
var _a, _b;
|
|
6792
|
+
var _a, _b, _c;
|
|
7003
6793
|
if (__classPrivateFieldGet(this, _ProlinkNetwork_config, "f") === null) {
|
|
7004
6794
|
throw new Error(connectErrorHelp);
|
|
7005
6795
|
}
|
|
@@ -7011,13 +6801,13 @@ class ProlinkNetwork {
|
|
|
7011
6801
|
__classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f").reconfigure({ vcdjName: __classPrivateFieldGet(this, _ProlinkNetwork_config, "f").vcdjName });
|
|
7012
6802
|
}
|
|
7013
6803
|
// Start announcing
|
|
7014
|
-
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);
|
|
6804
|
+
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"));
|
|
7015
6805
|
announcer.start();
|
|
7016
6806
|
// Create remote and local databases
|
|
7017
6807
|
const remotedb = new remotedb_1.default(__classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), vcdj);
|
|
7018
|
-
const localdb = new localdb_1.default(vcdj, __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"), __classPrivateFieldGet(this, _ProlinkNetwork_statusEmitter, "f"), (
|
|
6808
|
+
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');
|
|
7019
6809
|
// Create unified database
|
|
7020
|
-
const database = new db_1.default(
|
|
6810
|
+
const database = new db_1.default(localdb, remotedb, __classPrivateFieldGet(this, _ProlinkNetwork_deviceManager, "f"));
|
|
7021
6811
|
// Create controller service
|
|
7022
6812
|
const control = new control_1.default(__classPrivateFieldGet(this, _ProlinkNetwork_beatSocket, "f"), vcdj);
|
|
7023
6813
|
__classPrivateFieldSet(this, _ProlinkNetwork_state, types_1.NetworkState.Connected, "f");
|
|
@@ -7147,6 +6937,14 @@ class ProlinkNetwork {
|
|
|
7147
6937
|
var _a, _b;
|
|
7148
6938
|
return (_b = (_a = __classPrivateFieldGet(this, _ProlinkNetwork_connection, "f")) === null || _a === void 0 ? void 0 : _a.remotedb) !== null && _b !== void 0 ? _b : null;
|
|
7149
6939
|
}
|
|
6940
|
+
/**
|
|
6941
|
+
* Returns a promise that resolves when the full startup protocol completes.
|
|
6942
|
+
* Resolves immediately if fullStartup is disabled or not connected.
|
|
6943
|
+
*/
|
|
6944
|
+
get startupReady() {
|
|
6945
|
+
var _a, _b;
|
|
6946
|
+
return (_b = (_a = __classPrivateFieldGet(this, _ProlinkNetwork_connection, "f")) === null || _a === void 0 ? void 0 : _a.announcer.ready) !== null && _b !== void 0 ? _b : Promise.resolve();
|
|
6947
|
+
}
|
|
7150
6948
|
/**
|
|
7151
6949
|
* Get (and initialize) the {@link MixstatusProcessor} service. This service can
|
|
7152
6950
|
* be used to monitor the 'status' of devices on the network as a whole.
|
|
@@ -7165,7 +6963,7 @@ class ProlinkNetwork {
|
|
|
7165
6963
|
}
|
|
7166
6964
|
}
|
|
7167
6965
|
exports.ProlinkNetwork = ProlinkNetwork;
|
|
7168
|
-
_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();
|
|
6966
|
+
_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();
|
|
7169
6967
|
|
|
7170
6968
|
|
|
7171
6969
|
/***/ },
|
|
@@ -7212,6 +7010,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
7212
7010
|
};
|
|
7213
7011
|
})();
|
|
7214
7012
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
7013
|
+
exports.parseWindowsPath = parseWindowsPath;
|
|
7014
|
+
exports.resolveNfsPath = resolveNfsPath;
|
|
7015
|
+
exports.getPortmapPort = getPortmapPort;
|
|
7215
7016
|
exports.fetchFileRange = fetchFileRange;
|
|
7216
7017
|
exports.getFileInfo = getFileInfo;
|
|
7217
7018
|
exports.fetchFile = fetchFile;
|
|
@@ -7229,8 +7030,54 @@ const xdr_1 = __webpack_require__(/*! ./xdr */ "./src/nfs/xdr.ts");
|
|
|
7229
7030
|
const slotMountMapping = {
|
|
7230
7031
|
[types_1.MediaSlot.USB]: '/C/',
|
|
7231
7032
|
[types_1.MediaSlot.SD]: '/B/',
|
|
7232
|
-
[types_1.MediaSlot.RB]: '/',
|
|
7233
7033
|
};
|
|
7034
|
+
/**
|
|
7035
|
+
* Parse a Windows absolute path (e.g. `C:\Users\chris\Music\track.mp3`)
|
|
7036
|
+
* into the NFS mount path and relative file path.
|
|
7037
|
+
*
|
|
7038
|
+
* @internal Exported for testing
|
|
7039
|
+
*/
|
|
7040
|
+
function parseWindowsPath(filePath) {
|
|
7041
|
+
const match = filePath.match(/^([A-Za-z]):[/\\](.*)$/);
|
|
7042
|
+
if (!match) {
|
|
7043
|
+
return null;
|
|
7044
|
+
}
|
|
7045
|
+
return {
|
|
7046
|
+
mountPath: `/${match[1].toUpperCase()}/`,
|
|
7047
|
+
nfsPath: match[2].replace(/\\/g, '/'),
|
|
7048
|
+
};
|
|
7049
|
+
}
|
|
7050
|
+
/**
|
|
7051
|
+
* Resolve the NFS mount path and file path for a given slot.
|
|
7052
|
+
*
|
|
7053
|
+
* For USB/SD slots, the mount path is well-known (/C/ and /B/).
|
|
7054
|
+
* For the RB slot, the mount path is extracted from the file path returned
|
|
7055
|
+
* by remotedb:
|
|
7056
|
+
* - Windows: `C:\Users\chris\Music\track.mp3` → mount `/C/`, path `Users/chris/Music/track.mp3`
|
|
7057
|
+
* - macOS: `/Users/chris/Music/track.mp3` → mount `/`, path `Users/chris/Music/track.mp3`
|
|
7058
|
+
*
|
|
7059
|
+
* @internal Exported for testing
|
|
7060
|
+
*/
|
|
7061
|
+
function resolveNfsPath(slot, filePath) {
|
|
7062
|
+
if (slot === types_1.MediaSlot.RB) {
|
|
7063
|
+
// Windows: C:\Users\chris\Music\track.mp3 → mount /C/, path Users/...
|
|
7064
|
+
const parsed = parseWindowsPath(filePath);
|
|
7065
|
+
if (parsed) {
|
|
7066
|
+
return parsed;
|
|
7067
|
+
}
|
|
7068
|
+
// macOS: /Users/chris/Music/track.mp3 → mount /, path Users/...
|
|
7069
|
+
if (filePath.startsWith('/')) {
|
|
7070
|
+
return {
|
|
7071
|
+
mountPath: '/',
|
|
7072
|
+
nfsPath: filePath.slice(1),
|
|
7073
|
+
};
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
return {
|
|
7077
|
+
mountPath: slotMountMapping[slot],
|
|
7078
|
+
nfsPath: filePath,
|
|
7079
|
+
};
|
|
7080
|
+
}
|
|
7234
7081
|
/**
|
|
7235
7082
|
* The module-level retry configuration for newly created RpcConnections.
|
|
7236
7083
|
*/
|
|
@@ -7241,6 +7088,18 @@ let retryConfig = {};
|
|
|
7241
7088
|
* still be connected.
|
|
7242
7089
|
*/
|
|
7243
7090
|
const clientsCache = new Map();
|
|
7091
|
+
/**
|
|
7092
|
+
* Get the portmapper port for the given device. Rekordbox software uses a
|
|
7093
|
+
* non-standard port (50111) while CDJs and other hardware use the standard
|
|
7094
|
+
* port (111).
|
|
7095
|
+
*
|
|
7096
|
+
* @internal Exported for testing
|
|
7097
|
+
*/
|
|
7098
|
+
function getPortmapPort(device) {
|
|
7099
|
+
return device.type === types_1.DeviceType.Rekordbox
|
|
7100
|
+
? programs_1.REKORDBOX_PORTMAP_PORT
|
|
7101
|
+
: programs_1.STANDARD_PORTMAP_PORT;
|
|
7102
|
+
}
|
|
7244
7103
|
/**
|
|
7245
7104
|
* Given a device address running a nfs and mountd RPC server, provide
|
|
7246
7105
|
* RpcProgram clients that may be used to call these services.
|
|
@@ -7248,7 +7107,8 @@ const clientsCache = new Map();
|
|
|
7248
7107
|
* NOTE: This function will cache the clients for the address, recreating the
|
|
7249
7108
|
* connections if the cached clients have disconnected.
|
|
7250
7109
|
*/
|
|
7251
|
-
async function getClients(
|
|
7110
|
+
async function getClients(device) {
|
|
7111
|
+
const { address } = device.ip;
|
|
7252
7112
|
const cachedSet = clientsCache.get(address);
|
|
7253
7113
|
if (cachedSet !== undefined && cachedSet.conn.connected) {
|
|
7254
7114
|
return cachedSet;
|
|
@@ -7258,49 +7118,50 @@ async function getClients(address) {
|
|
|
7258
7118
|
clientsCache.delete(address);
|
|
7259
7119
|
}
|
|
7260
7120
|
const conn = new rpc_1.RpcConnection(address, retryConfig);
|
|
7121
|
+
const portmapPort = getPortmapPort(device);
|
|
7261
7122
|
const mountClient = await (0, programs_1.makeProgramClient)(conn, {
|
|
7262
7123
|
id: xdr_1.mount.Program,
|
|
7263
7124
|
version: xdr_1.mount.Version,
|
|
7264
|
-
});
|
|
7125
|
+
}, portmapPort);
|
|
7265
7126
|
const nfsClient = await (0, programs_1.makeProgramClient)(conn, {
|
|
7266
7127
|
id: xdr_1.nfs.Program,
|
|
7267
7128
|
version: xdr_1.nfs.Version,
|
|
7268
|
-
});
|
|
7129
|
+
}, portmapPort);
|
|
7269
7130
|
const set = { conn, mountClient, nfsClient };
|
|
7270
7131
|
clientsCache.set(address, set);
|
|
7271
7132
|
return set;
|
|
7272
7133
|
}
|
|
7273
7134
|
/**
|
|
7274
|
-
* This module maintains a singleton cached list of (device address +
|
|
7135
|
+
* This module maintains a singleton cached list of (device address + mount path) -> file
|
|
7275
7136
|
* handles. The file handles may become stale in this list should the devices
|
|
7276
7137
|
* connected to the players slot change.
|
|
7277
7138
|
*/
|
|
7278
7139
|
const rootHandleCache = new Map();
|
|
7279
7140
|
/**
|
|
7280
|
-
* Locate the root filehandle of the given device
|
|
7141
|
+
* Locate the root filehandle of the given device mount path.
|
|
7281
7142
|
*
|
|
7282
|
-
* NOTE: This function will cache the root handle for the device +
|
|
7143
|
+
* NOTE: This function will cache the root handle for the device + mount path. Should
|
|
7283
7144
|
* the device have changed the slot will not longer be valid (TODO,
|
|
7284
7145
|
* verify this). It is up to the caller to clear the cache and get the
|
|
7285
7146
|
* new root handle in that case.
|
|
7286
7147
|
*/
|
|
7287
|
-
async function getRootHandle({ device,
|
|
7148
|
+
async function getRootHandle({ device, mountPath, mountClient, span, }) {
|
|
7288
7149
|
var _a;
|
|
7289
7150
|
const tx = span === null || span === void 0 ? void 0 : span.startChild({ op: 'getRootHandle' });
|
|
7290
7151
|
const { address } = device.ip;
|
|
7291
|
-
const
|
|
7292
|
-
const cachedRootHandle =
|
|
7152
|
+
const deviceMountCache = (_a = rootHandleCache.get(address)) !== null && _a !== void 0 ? _a : new Map();
|
|
7153
|
+
const cachedRootHandle = deviceMountCache.get(mountPath);
|
|
7293
7154
|
if (cachedRootHandle !== undefined) {
|
|
7294
7155
|
return cachedRootHandle;
|
|
7295
7156
|
}
|
|
7296
7157
|
const exports = await (0, programs_1.getExports)(mountClient, tx);
|
|
7297
|
-
const targetExport = exports.find(e => e.filesystem ===
|
|
7158
|
+
const targetExport = exports.find(e => e.filesystem === mountPath);
|
|
7298
7159
|
if (targetExport === undefined) {
|
|
7299
7160
|
return null;
|
|
7300
7161
|
}
|
|
7301
7162
|
const rootHandle = await (0, programs_1.mountFilesystem)(mountClient, targetExport, tx);
|
|
7302
|
-
|
|
7303
|
-
rootHandleCache.set(address,
|
|
7163
|
+
deviceMountCache.set(mountPath, rootHandle);
|
|
7164
|
+
rootHandleCache.set(address, deviceMountCache);
|
|
7304
7165
|
tx === null || tx === void 0 ? void 0 : tx.finish();
|
|
7305
7166
|
return rootHandle;
|
|
7306
7167
|
}
|
|
@@ -7313,22 +7174,23 @@ async function fetchFileRange({ device, slot, path, offset, length, span, }) {
|
|
|
7313
7174
|
const tx = span
|
|
7314
7175
|
? span.startChild({ op: 'fetchFileRange' })
|
|
7315
7176
|
: Telemetry.startTransaction({ name: 'fetchFileRange' });
|
|
7316
|
-
const {
|
|
7317
|
-
const
|
|
7177
|
+
const { mountPath, nfsPath } = resolveNfsPath(slot, path);
|
|
7178
|
+
const { mountClient, nfsClient } = await getClients(device);
|
|
7179
|
+
const rootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
|
|
7318
7180
|
if (rootHandle === null) {
|
|
7319
7181
|
throw badRoothandleError(slot, device.id);
|
|
7320
7182
|
}
|
|
7321
7183
|
let fileInfo = null;
|
|
7322
7184
|
try {
|
|
7323
|
-
fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle,
|
|
7185
|
+
fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, nfsPath, tx);
|
|
7324
7186
|
}
|
|
7325
7187
|
catch {
|
|
7326
7188
|
rootHandleCache.delete(device.ip.address);
|
|
7327
|
-
const newRootHandle = await getRootHandle({ device,
|
|
7189
|
+
const newRootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
|
|
7328
7190
|
if (newRootHandle === null) {
|
|
7329
7191
|
throw badRoothandleError(slot, device.id);
|
|
7330
7192
|
}
|
|
7331
|
-
fileInfo = await (0, programs_1.lookupPath)(nfsClient, newRootHandle,
|
|
7193
|
+
fileInfo = await (0, programs_1.lookupPath)(nfsClient, newRootHandle, nfsPath, tx);
|
|
7332
7194
|
}
|
|
7333
7195
|
const actualOffset = Math.min(offset, fileInfo.size);
|
|
7334
7196
|
const actualLength = Math.min(length, fileInfo.size - actualOffset);
|
|
@@ -7352,22 +7214,23 @@ async function getFileInfo({ device, slot, path, span, }) {
|
|
|
7352
7214
|
const tx = span
|
|
7353
7215
|
? span.startChild({ op: 'getFileInfo' })
|
|
7354
7216
|
: Telemetry.startTransaction({ name: 'getFileInfo' });
|
|
7355
|
-
const {
|
|
7356
|
-
const
|
|
7217
|
+
const { mountPath, nfsPath } = resolveNfsPath(slot, path);
|
|
7218
|
+
const { mountClient, nfsClient } = await getClients(device);
|
|
7219
|
+
const rootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
|
|
7357
7220
|
if (rootHandle === null) {
|
|
7358
7221
|
throw badRoothandleError(slot, device.id);
|
|
7359
7222
|
}
|
|
7360
7223
|
let fileInfo;
|
|
7361
7224
|
try {
|
|
7362
|
-
fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle,
|
|
7225
|
+
fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, nfsPath, tx);
|
|
7363
7226
|
}
|
|
7364
7227
|
catch {
|
|
7365
7228
|
rootHandleCache.delete(device.ip.address);
|
|
7366
|
-
const newRootHandle = await getRootHandle({ device,
|
|
7229
|
+
const newRootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
|
|
7367
7230
|
if (newRootHandle === null) {
|
|
7368
7231
|
throw badRoothandleError(slot, device.id);
|
|
7369
7232
|
}
|
|
7370
|
-
fileInfo = await (0, programs_1.lookupPath)(nfsClient, newRootHandle,
|
|
7233
|
+
fileInfo = await (0, programs_1.lookupPath)(nfsClient, newRootHandle, nfsPath, tx);
|
|
7371
7234
|
}
|
|
7372
7235
|
tx.setData('path', path);
|
|
7373
7236
|
tx.setData('slot', (0, utils_1.getSlotName)(slot));
|
|
@@ -7387,8 +7250,9 @@ async function fetchFile({ device, slot, path, onProgress, span, chunkSize, }) {
|
|
|
7387
7250
|
const tx = span
|
|
7388
7251
|
? span.startChild({ op: 'fetchFile' })
|
|
7389
7252
|
: Telemetry.startTransaction({ name: 'fetchFile' });
|
|
7390
|
-
const {
|
|
7391
|
-
const
|
|
7253
|
+
const { mountPath, nfsPath } = resolveNfsPath(slot, path);
|
|
7254
|
+
const { mountClient, nfsClient } = await getClients(device);
|
|
7255
|
+
const rootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
|
|
7392
7256
|
if (rootHandle === null) {
|
|
7393
7257
|
throw badRoothandleError(slot, device.id);
|
|
7394
7258
|
}
|
|
@@ -7396,16 +7260,16 @@ async function fetchFile({ device, slot, path, onProgress, span, chunkSize, }) {
|
|
|
7396
7260
|
// a path lets first try and clear our roothandle cache
|
|
7397
7261
|
let fileInfo = null;
|
|
7398
7262
|
try {
|
|
7399
|
-
fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle,
|
|
7263
|
+
fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, nfsPath, tx);
|
|
7400
7264
|
}
|
|
7401
7265
|
catch {
|
|
7402
7266
|
rootHandleCache.delete(device.ip.address);
|
|
7403
|
-
const rootHandle = await getRootHandle({ device,
|
|
7267
|
+
const rootHandle = await getRootHandle({ device, mountPath, mountClient, span: tx });
|
|
7404
7268
|
if (rootHandle === null) {
|
|
7405
7269
|
throw badRoothandleError(slot, device.id);
|
|
7406
7270
|
}
|
|
7407
7271
|
// Desperately try once more to lookup the file
|
|
7408
|
-
fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle,
|
|
7272
|
+
fileInfo = await (0, programs_1.lookupPath)(nfsClient, rootHandle, nfsPath, tx);
|
|
7409
7273
|
}
|
|
7410
7274
|
const file = await (0, programs_1.fetchFile)(nfsClient, fileInfo, onProgress, tx, chunkSize);
|
|
7411
7275
|
tx.setData('path', path);
|
|
@@ -7443,6 +7307,7 @@ function configureRetryStrategy(config) {
|
|
|
7443
7307
|
"use strict";
|
|
7444
7308
|
|
|
7445
7309
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
7310
|
+
exports.REKORDBOX_PORTMAP_PORT = exports.STANDARD_PORTMAP_PORT = void 0;
|
|
7446
7311
|
exports.makeProgramClient = makeProgramClient;
|
|
7447
7312
|
exports.getExports = getExports;
|
|
7448
7313
|
exports.mountFilesystem = mountFilesystem;
|
|
@@ -7457,10 +7322,19 @@ const xdr_1 = __webpack_require__(/*! ./xdr */ "./src/nfs/xdr.ts");
|
|
|
7457
7322
|
* How many bytes of a file should we read at once.
|
|
7458
7323
|
*/
|
|
7459
7324
|
const READ_SIZE = 8192;
|
|
7325
|
+
/**
|
|
7326
|
+
* Standard portmapper port used by CDJs and other hardware.
|
|
7327
|
+
*/
|
|
7328
|
+
exports.STANDARD_PORTMAP_PORT = 111;
|
|
7329
|
+
/**
|
|
7330
|
+
* Non-standard portmapper port used by rekordbox software.
|
|
7331
|
+
* Rekordbox registers its RPC services on this port instead of 111.
|
|
7332
|
+
*/
|
|
7333
|
+
exports.REKORDBOX_PORTMAP_PORT = 50111;
|
|
7460
7334
|
/**
|
|
7461
7335
|
* Queries for the listening port of a RPC program
|
|
7462
7336
|
*/
|
|
7463
|
-
async function makeProgramClient(conn, program) {
|
|
7337
|
+
async function makeProgramClient(conn, program, portmapPort = exports.STANDARD_PORTMAP_PORT) {
|
|
7464
7338
|
const getPortData = new xdr_1.portmap.GetPort({
|
|
7465
7339
|
program: program.id,
|
|
7466
7340
|
version: program.version,
|
|
@@ -7468,7 +7342,7 @@ async function makeProgramClient(conn, program) {
|
|
|
7468
7342
|
port: 0,
|
|
7469
7343
|
});
|
|
7470
7344
|
const data = await conn.call({
|
|
7471
|
-
port:
|
|
7345
|
+
port: portmapPort,
|
|
7472
7346
|
program: xdr_1.portmap.Program,
|
|
7473
7347
|
version: xdr_1.portmap.Version,
|
|
7474
7348
|
procedure: xdr_1.portmap.Procedure.getPort().value,
|
|
@@ -8296,15 +8170,8 @@ function findAllAlphaThetaInterfaces() {
|
|
|
8296
8170
|
return results;
|
|
8297
8171
|
}
|
|
8298
8172
|
/**
|
|
8299
|
-
* macOS implementation:
|
|
8300
|
-
*
|
|
8301
|
-
*/
|
|
8302
|
-
function findAlphaThetaInterfaceMacOS() {
|
|
8303
|
-
const results = findAllAlphaThetaInterfacesMacOS();
|
|
8304
|
-
return results.length > 0 ? results[0] : null;
|
|
8305
|
-
}
|
|
8306
|
-
/**
|
|
8307
|
-
* macOS implementation: Find ALL AlphaTheta USB interfaces.
|
|
8173
|
+
* macOS implementation: Find ALL AlphaTheta USB interfaces using ioreg
|
|
8174
|
+
* and networksetup to map them to interface names.
|
|
8308
8175
|
*/
|
|
8309
8176
|
function findAllAlphaThetaInterfacesMacOS() {
|
|
8310
8177
|
try {
|
|
@@ -8379,14 +8246,7 @@ function findAllAlphaThetaInterfacesMacOS() {
|
|
|
8379
8246
|
}
|
|
8380
8247
|
}
|
|
8381
8248
|
/**
|
|
8382
|
-
* Windows implementation:
|
|
8383
|
-
*/
|
|
8384
|
-
function findAlphaThetaInterfaceWindows() {
|
|
8385
|
-
const results = findAllAlphaThetaInterfacesWindows();
|
|
8386
|
-
return results.length > 0 ? results[0] : null;
|
|
8387
|
-
}
|
|
8388
|
-
/**
|
|
8389
|
-
* Windows implementation: Find ALL AlphaTheta USB interfaces.
|
|
8249
|
+
* Windows implementation: Find ALL AlphaTheta USB network adapters via PowerShell.
|
|
8390
8250
|
*/
|
|
8391
8251
|
function findAllAlphaThetaInterfacesWindows() {
|
|
8392
8252
|
var _a;
|
|
@@ -8571,14 +8431,6 @@ function getAlphaThetaArpEntries() {
|
|
|
8571
8431
|
}
|
|
8572
8432
|
return result;
|
|
8573
8433
|
}
|
|
8574
|
-
/**
|
|
8575
|
-
* Find an AlphaTheta device connected via Ethernet by checking the ARP cache
|
|
8576
|
-
* for known MAC address prefixes.
|
|
8577
|
-
*/
|
|
8578
|
-
function findAlphaThetaViaEthernet() {
|
|
8579
|
-
const results = findAllAlphaThetaViaEthernet();
|
|
8580
|
-
return results.length > 0 ? results[0] : null;
|
|
8581
|
-
}
|
|
8582
8434
|
/**
|
|
8583
8435
|
* Find ALL AlphaTheta devices connected via Ethernet by checking the ARP cache
|
|
8584
8436
|
* for known MAC address prefixes.
|
|
@@ -8907,7 +8759,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
8907
8759
|
};
|
|
8908
8760
|
var _PassiveProlinkNetwork_adapter, _PassiveProlinkNetwork_deviceManager, _PassiveProlinkNetwork_statusEmitter, _PassiveProlinkNetwork_positionEmitter, _PassiveProlinkNetwork_localdb, _PassiveProlinkNetwork_remotedb, _PassiveProlinkNetwork_mixstatus;
|
|
8909
8761
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
8910
|
-
exports.PassiveProlinkNetwork = exports.PassiveStatusEmitter = exports.PassiveRemoteDatabase = exports.PassivePositionEmitter = exports.PcapAdapter = exports.PassiveLocalDatabase = exports.PassiveDeviceManager = exports.getArpCacheForInterface = exports.
|
|
8762
|
+
exports.PassiveProlinkNetwork = exports.PassiveStatusEmitter = exports.PassiveRemoteDatabase = exports.PassivePositionEmitter = exports.PcapAdapter = exports.PassiveLocalDatabase = exports.PassiveDeviceManager = exports.getArpCacheForInterface = exports.findAlphaThetaInterface = exports.findAllAlphaThetaInterfaces = void 0;
|
|
8911
8763
|
exports.bringOnlinePassive = bringOnlinePassive;
|
|
8912
8764
|
exports.listInterfaces = listInterfaces;
|
|
8913
8765
|
const os_1 = __webpack_require__(/*! os */ "os");
|
|
@@ -8919,8 +8771,8 @@ const position_1 = __webpack_require__(/*! ./position */ "./src/passive/position
|
|
|
8919
8771
|
const remotedb_1 = __webpack_require__(/*! ./remotedb */ "./src/passive/remotedb.ts");
|
|
8920
8772
|
const status_1 = __webpack_require__(/*! ./status */ "./src/passive/status.ts");
|
|
8921
8773
|
var alphatheta_1 = __webpack_require__(/*! ./alphatheta */ "./src/passive/alphatheta.ts");
|
|
8922
|
-
Object.defineProperty(exports, "findAlphaThetaInterface", ({ enumerable: true, get: function () { return alphatheta_1.findAlphaThetaInterface; } }));
|
|
8923
8774
|
Object.defineProperty(exports, "findAllAlphaThetaInterfaces", ({ enumerable: true, get: function () { return alphatheta_1.findAllAlphaThetaInterfaces; } }));
|
|
8775
|
+
Object.defineProperty(exports, "findAlphaThetaInterface", ({ enumerable: true, get: function () { return alphatheta_1.findAlphaThetaInterface; } }));
|
|
8924
8776
|
Object.defineProperty(exports, "getArpCacheForInterface", ({ enumerable: true, get: function () { return alphatheta_1.getArpCacheForInterface; } }));
|
|
8925
8777
|
var devices_2 = __webpack_require__(/*! ./devices */ "./src/passive/devices.ts");
|
|
8926
8778
|
Object.defineProperty(exports, "PassiveDeviceManager", ({ enumerable: true, get: function () { return devices_2.PassiveDeviceManager; } }));
|
|
@@ -9205,6 +9057,7 @@ const events_1 = __webpack_require__(/*! events */ "events");
|
|
|
9205
9057
|
const fs = __importStar(__webpack_require__(/*! fs */ "fs"));
|
|
9206
9058
|
const os = __importStar(__webpack_require__(/*! os */ "os"));
|
|
9207
9059
|
const path = __importStar(__webpack_require__(/*! path */ "path"));
|
|
9060
|
+
const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
|
|
9208
9061
|
const onelibrary_1 = __webpack_require__(/*! src/localdb/onelibrary */ "./src/localdb/onelibrary.ts");
|
|
9209
9062
|
const orm_1 = __webpack_require__(/*! src/localdb/orm */ "./src/localdb/orm.ts");
|
|
9210
9063
|
const rekordbox_1 = __webpack_require__(/*! src/localdb/rekordbox */ "./src/localdb/rekordbox.ts");
|
|
@@ -9287,7 +9140,9 @@ class PassiveLocalDatabase {
|
|
|
9287
9140
|
* Helper to fetch a file from device, trying both dotted and non-dotted paths
|
|
9288
9141
|
*/
|
|
9289
9142
|
_PassiveLocalDatabase_fetchFileWithFallback.set(this, async (device, slot, basePath, tx) => {
|
|
9290
|
-
const attemptOrder = process.platform === 'win32'
|
|
9143
|
+
const attemptOrder = process.platform === 'win32'
|
|
9144
|
+
? [basePath, `.${basePath}`]
|
|
9145
|
+
: [`.${basePath}`, basePath];
|
|
9291
9146
|
try {
|
|
9292
9147
|
return await (0, nfs_1.fetchFile)({
|
|
9293
9148
|
device,
|
|
@@ -9298,7 +9153,7 @@ class PassiveLocalDatabase {
|
|
|
9298
9153
|
});
|
|
9299
9154
|
}
|
|
9300
9155
|
catch {
|
|
9301
|
-
return
|
|
9156
|
+
return (0, nfs_1.fetchFile)({
|
|
9302
9157
|
device,
|
|
9303
9158
|
slot,
|
|
9304
9159
|
path: attemptOrder[1],
|
|
@@ -9476,8 +9331,10 @@ class PassiveLocalDatabase {
|
|
|
9476
9331
|
var _a;
|
|
9477
9332
|
const lockKey = `${device.id}-${slot}`;
|
|
9478
9333
|
const lock = (_a = __classPrivateFieldGet(this, _PassiveLocalDatabase_slotLocks, "f").get(lockKey)) !== null && _a !== void 0 ? _a : __classPrivateFieldGet(this, _PassiveLocalDatabase_slotLocks, "f").set(lockKey, new async_mutex_1.Mutex()).get(lockKey);
|
|
9479
|
-
if (device.type !== types_1.DeviceType.CDJ
|
|
9480
|
-
|
|
9334
|
+
if (device.type !== types_1.DeviceType.CDJ ||
|
|
9335
|
+
device.id < constants_1.MIN_CDJ_DEVICE_ID ||
|
|
9336
|
+
device.id > constants_1.MAX_CDJ_DEVICE_ID) {
|
|
9337
|
+
return null;
|
|
9481
9338
|
}
|
|
9482
9339
|
if (media.tracksType !== types_1.TrackType.RB) {
|
|
9483
9340
|
return null;
|
|
@@ -9506,7 +9363,9 @@ class PassiveLocalDatabase {
|
|
|
9506
9363
|
var _a;
|
|
9507
9364
|
const lockKey = `${device.id}-${slot}-nomedia`;
|
|
9508
9365
|
const lock = (_a = __classPrivateFieldGet(this, _PassiveLocalDatabase_slotLocks, "f").get(lockKey)) !== null && _a !== void 0 ? _a : __classPrivateFieldGet(this, _PassiveLocalDatabase_slotLocks, "f").set(lockKey, new async_mutex_1.Mutex()).get(lockKey);
|
|
9509
|
-
if (device.type !== types_1.DeviceType.CDJ
|
|
9366
|
+
if (device.type !== types_1.DeviceType.CDJ ||
|
|
9367
|
+
device.id < constants_1.MIN_CDJ_DEVICE_ID ||
|
|
9368
|
+
device.id > constants_1.MAX_CDJ_DEVICE_ID) {
|
|
9510
9369
|
return null;
|
|
9511
9370
|
}
|
|
9512
9371
|
// Check if we already have a cached database for this device/slot
|
|
@@ -9549,7 +9408,9 @@ class PassiveLocalDatabase {
|
|
|
9549
9408
|
*/
|
|
9550
9409
|
async preload() {
|
|
9551
9410
|
const allDevices = [...__classPrivateFieldGet(this, _PassiveLocalDatabase_deviceManager, "f").devices.values()];
|
|
9552
|
-
const cdjDevices = allDevices.filter(device => device.type === types_1.DeviceType.CDJ
|
|
9411
|
+
const cdjDevices = allDevices.filter(device => device.type === types_1.DeviceType.CDJ &&
|
|
9412
|
+
device.id >= constants_1.MIN_CDJ_DEVICE_ID &&
|
|
9413
|
+
device.id <= constants_1.MAX_CDJ_DEVICE_ID);
|
|
9553
9414
|
if (cdjDevices.length === 0) {
|
|
9554
9415
|
return;
|
|
9555
9416
|
}
|
|
@@ -9587,6 +9448,7 @@ var _PcapAdapter_emitter, _PcapAdapter_cap, _PcapAdapter_buffer, _PcapAdapter_if
|
|
|
9587
9448
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
9588
9449
|
exports.PcapAdapter = void 0;
|
|
9589
9450
|
const events_1 = __webpack_require__(/*! events */ "events");
|
|
9451
|
+
const os_1 = __webpack_require__(/*! os */ "os");
|
|
9590
9452
|
const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
|
|
9591
9453
|
/**
|
|
9592
9454
|
* PcapAdapter captures Pro DJ Link packets via pcap (libpcap).
|
|
@@ -9635,13 +9497,22 @@ class PcapAdapter {
|
|
|
9635
9497
|
const { Cap, decoders } = cap;
|
|
9636
9498
|
const PROTOCOL = decoders.PROTOCOL;
|
|
9637
9499
|
__classPrivateFieldSet(this, _PcapAdapter_cap, new Cap(), "f");
|
|
9500
|
+
// On Windows, translate Node.js interface name (e.g. "Ethernet") to Npcap
|
|
9501
|
+
// device path (e.g. "\Device\NPF_{GUID}") by matching IPv4 addresses.
|
|
9502
|
+
let deviceName = __classPrivateFieldGet(this, _PcapAdapter_iface, "f");
|
|
9503
|
+
if (process.platform === 'win32' && !deviceName.startsWith('\\Device\\')) {
|
|
9504
|
+
const resolved = resolveWindowsInterface(__classPrivateFieldGet(this, _PcapAdapter_iface, "f"), Cap);
|
|
9505
|
+
if (resolved) {
|
|
9506
|
+
deviceName = resolved;
|
|
9507
|
+
}
|
|
9508
|
+
}
|
|
9638
9509
|
// BPF filter for Pro DJ Link UDP ports
|
|
9639
9510
|
const filter = `udp and (port ${constants_1.ANNOUNCE_PORT} or port ${constants_1.BEAT_PORT} or port ${constants_1.STATUS_PORT})`;
|
|
9640
9511
|
try {
|
|
9641
|
-
__classPrivateFieldGet(this, _PcapAdapter_cap, "f").open(
|
|
9512
|
+
__classPrivateFieldGet(this, _PcapAdapter_cap, "f").open(deviceName, filter, 65535, __classPrivateFieldGet(this, _PcapAdapter_buffer, "f"));
|
|
9642
9513
|
}
|
|
9643
9514
|
catch (err) {
|
|
9644
|
-
throw new Error(`Failed to open interface "${__classPrivateFieldGet(this, _PcapAdapter_iface, "f")}" for packet capture. ` +
|
|
9515
|
+
throw new Error(`Failed to open interface "${__classPrivateFieldGet(this, _PcapAdapter_iface, "f")}" (device: ${deviceName}) for packet capture. ` +
|
|
9645
9516
|
`Ensure you have root/sudo privileges and the interface exists.\n` +
|
|
9646
9517
|
`Original error: ${err}`);
|
|
9647
9518
|
}
|
|
@@ -9727,6 +9598,41 @@ class PcapAdapter {
|
|
|
9727
9598
|
}
|
|
9728
9599
|
exports.PcapAdapter = PcapAdapter;
|
|
9729
9600
|
_PcapAdapter_emitter = new WeakMap(), _PcapAdapter_cap = new WeakMap(), _PcapAdapter_buffer = new WeakMap(), _PcapAdapter_iface = new WeakMap(), _PcapAdapter_started = new WeakMap();
|
|
9601
|
+
/**
|
|
9602
|
+
* Resolve a Node.js network interface name (e.g. "Ethernet") to an Npcap
|
|
9603
|
+
* device path (e.g. "\Device\NPF_{GUID}") on Windows.
|
|
9604
|
+
*
|
|
9605
|
+
* Node.js os.networkInterfaces() returns friendly names, but the cap module
|
|
9606
|
+
* requires Npcap device paths. This function matches by comparing IPv4
|
|
9607
|
+
* addresses between os.networkInterfaces() and Cap.deviceList().
|
|
9608
|
+
*/
|
|
9609
|
+
function resolveWindowsInterface(ifaceName, Cap) {
|
|
9610
|
+
// Get the IPv4 addresses for the requested Node.js interface
|
|
9611
|
+
const nodeInterfaces = (0, os_1.networkInterfaces)();
|
|
9612
|
+
const nodeIface = nodeInterfaces[ifaceName];
|
|
9613
|
+
if (!nodeIface) {
|
|
9614
|
+
return null;
|
|
9615
|
+
}
|
|
9616
|
+
const nodeIpv4Addresses = new Set(nodeIface
|
|
9617
|
+
.filter(i => i.family === 'IPv4' || i.family === 4)
|
|
9618
|
+
.map(i => i.address));
|
|
9619
|
+
if (nodeIpv4Addresses.size === 0) {
|
|
9620
|
+
return null;
|
|
9621
|
+
}
|
|
9622
|
+
// Find the cap device whose addresses overlap with the Node.js interface
|
|
9623
|
+
const capDevices = Cap.deviceList();
|
|
9624
|
+
for (const device of capDevices) {
|
|
9625
|
+
if (!device.addresses) {
|
|
9626
|
+
continue;
|
|
9627
|
+
}
|
|
9628
|
+
for (const addr of device.addresses) {
|
|
9629
|
+
if (addr.addr && nodeIpv4Addresses.has(addr.addr)) {
|
|
9630
|
+
return device.name;
|
|
9631
|
+
}
|
|
9632
|
+
}
|
|
9633
|
+
}
|
|
9634
|
+
return null;
|
|
9635
|
+
}
|
|
9730
9636
|
exports["default"] = PcapAdapter;
|
|
9731
9637
|
|
|
9732
9638
|
|
|
@@ -10038,6 +9944,17 @@ class PassiveRemoteDatabase {
|
|
|
10038
9944
|
query: remotedb_1.Query.GetMetadata,
|
|
10039
9945
|
args: { trackId },
|
|
10040
9946
|
});
|
|
9947
|
+
// File path is not available for streaming tracks (Beatport, etc.)
|
|
9948
|
+
try {
|
|
9949
|
+
track.filePath = await conn.query({
|
|
9950
|
+
queryDescriptor,
|
|
9951
|
+
query: remotedb_1.Query.GetTrackInfo,
|
|
9952
|
+
args: { trackId },
|
|
9953
|
+
});
|
|
9954
|
+
}
|
|
9955
|
+
catch {
|
|
9956
|
+
// No-op — streaming tracks don't have a local file
|
|
9957
|
+
}
|
|
10041
9958
|
return track;
|
|
10042
9959
|
}
|
|
10043
9960
|
catch (err) {
|
|
@@ -10046,6 +9963,50 @@ class PassiveRemoteDatabase {
|
|
|
10046
9963
|
throw err;
|
|
10047
9964
|
}
|
|
10048
9965
|
}
|
|
9966
|
+
/**
|
|
9967
|
+
* Query metadata for an unanalyzed track (loaded directly from USB without
|
|
9968
|
+
* Rekordbox analysis). Uses GetGenericMetadata which reads ID3 tags from the
|
|
9969
|
+
* audio file via the CDJ.
|
|
9970
|
+
*
|
|
9971
|
+
* @param deviceId - The CDJ device to query
|
|
9972
|
+
* @param trackSlot - The media slot (USB or SD)
|
|
9973
|
+
* @param trackType - Should be TrackType.Unanalyzed
|
|
9974
|
+
* @param trackId - The track ID to look up
|
|
9975
|
+
*/
|
|
9976
|
+
async getGenericTrackMetadata(deviceId, trackSlot, trackType, trackId) {
|
|
9977
|
+
const conn = await this.get(deviceId);
|
|
9978
|
+
if (conn === null) {
|
|
9979
|
+
return null;
|
|
9980
|
+
}
|
|
9981
|
+
const queryDescriptor = {
|
|
9982
|
+
trackSlot,
|
|
9983
|
+
trackType,
|
|
9984
|
+
menuTarget: remotedb_1.MenuTarget.Main,
|
|
9985
|
+
};
|
|
9986
|
+
try {
|
|
9987
|
+
const track = await conn.query({
|
|
9988
|
+
queryDescriptor,
|
|
9989
|
+
query: remotedb_1.Query.GetGenericMetadata,
|
|
9990
|
+
args: { trackId },
|
|
9991
|
+
});
|
|
9992
|
+
// Try to get file path for artwork extraction
|
|
9993
|
+
try {
|
|
9994
|
+
track.filePath = await conn.query({
|
|
9995
|
+
queryDescriptor,
|
|
9996
|
+
query: remotedb_1.Query.GetTrackInfo,
|
|
9997
|
+
args: { trackId },
|
|
9998
|
+
});
|
|
9999
|
+
}
|
|
10000
|
+
catch {
|
|
10001
|
+
// GetTrackInfo may not be available for unanalyzed tracks
|
|
10002
|
+
}
|
|
10003
|
+
return track;
|
|
10004
|
+
}
|
|
10005
|
+
catch (err) {
|
|
10006
|
+
__classPrivateFieldGet(this, _PassiveRemoteDatabase_connections, "f").delete(deviceId);
|
|
10007
|
+
throw err;
|
|
10008
|
+
}
|
|
10009
|
+
}
|
|
10049
10010
|
/**
|
|
10050
10011
|
* Stop all connections
|
|
10051
10012
|
*/
|
|
@@ -10504,10 +10465,14 @@ class QueryInterface {
|
|
|
10504
10465
|
const anyArgs = args;
|
|
10505
10466
|
const handler = queries_1.queryHandlers[query];
|
|
10506
10467
|
const releaseLock = await __classPrivateFieldGet(this, _QueryInterface_lock, "f").acquire();
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10468
|
+
try {
|
|
10469
|
+
const response = await handler({ conn, lookupDescriptor, span: tx, args: anyArgs });
|
|
10470
|
+
tx.finish();
|
|
10471
|
+
return response;
|
|
10472
|
+
}
|
|
10473
|
+
finally {
|
|
10474
|
+
releaseLock();
|
|
10475
|
+
}
|
|
10511
10476
|
}
|
|
10512
10477
|
}
|
|
10513
10478
|
exports.QueryInterface = QueryInterface;
|
|
@@ -10535,6 +10500,11 @@ class RemoteDatabase {
|
|
|
10535
10500
|
const { ip } = device;
|
|
10536
10501
|
const dbPort = await getRemoteDBServerPort(ip);
|
|
10537
10502
|
const socket = new promise_socket_1.default(new net_1.Socket());
|
|
10503
|
+
// Set a connection timeout to prevent hanging forever
|
|
10504
|
+
socket.stream.setTimeout(10000);
|
|
10505
|
+
socket.stream.once('timeout', () => {
|
|
10506
|
+
socket.stream.destroy(new Error(`RemoteDB connection to ${ip.address}:${dbPort} timed out`));
|
|
10507
|
+
});
|
|
10538
10508
|
await socket.connect(dbPort, ip.address);
|
|
10539
10509
|
// Send required preamble to open communications with the device
|
|
10540
10510
|
const preamble = new fields_1.UInt32(0x01);
|
|
@@ -10556,6 +10526,9 @@ class RemoteDatabase {
|
|
|
10556
10526
|
if (resp.type !== types_1.MessageType.Success) {
|
|
10557
10527
|
throw new Error(`Failed to introduce self to device ID: ${device.id}`);
|
|
10558
10528
|
}
|
|
10529
|
+
// Clear socket timeout after successful connection — the per-device mutex
|
|
10530
|
+
// and application-level Promise.race timeouts handle query timeouts
|
|
10531
|
+
socket.stream.setTimeout(0);
|
|
10559
10532
|
__classPrivateFieldGet(this, _RemoteDatabase_connections, "f").set(device.id, new Connection(device, socket));
|
|
10560
10533
|
tx.finish();
|
|
10561
10534
|
};
|
|
@@ -10597,15 +10570,19 @@ class RemoteDatabase {
|
|
|
10597
10570
|
}
|
|
10598
10571
|
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);
|
|
10599
10572
|
const releaseLock = await lock.acquire();
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10573
|
+
try {
|
|
10574
|
+
let conn = __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").get(deviceId);
|
|
10575
|
+
if (conn === undefined) {
|
|
10576
|
+
await this.connectToDevice(device);
|
|
10577
|
+
}
|
|
10578
|
+
conn = __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").get(deviceId);
|
|
10579
|
+
// NOTE: We pass the same lock we use for this device to the query
|
|
10580
|
+
// interface to ensure all query interfaces use the same lock.
|
|
10581
|
+
return new QueryInterface(conn, lock, __classPrivateFieldGet(this, _RemoteDatabase_hostDevice, "f"));
|
|
10582
|
+
}
|
|
10583
|
+
finally {
|
|
10584
|
+
releaseLock();
|
|
10603
10585
|
}
|
|
10604
|
-
conn = __classPrivateFieldGet(this, _RemoteDatabase_connections, "f").get(deviceId);
|
|
10605
|
-
releaseLock();
|
|
10606
|
-
// NOTE: We pass the same lock we use for this device to the query
|
|
10607
|
-
// interface to ensure all query interfaces use the same lock.
|
|
10608
|
-
return new QueryInterface(conn, lock, __classPrivateFieldGet(this, _RemoteDatabase_hostDevice, "f"));
|
|
10609
10586
|
}
|
|
10610
10587
|
}
|
|
10611
10588
|
_RemoteDatabase_hostDevice = new WeakMap(), _RemoteDatabase_deviceManager = new WeakMap(), _RemoteDatabase_connections = new WeakMap(), _RemoteDatabase_deviceLocks = new WeakMap();
|
|
@@ -11307,7 +11284,7 @@ const utils_1 = __webpack_require__(/*! ./utils */ "./src/remotedb/utils.ts");
|
|
|
11307
11284
|
* Lookup track metadata from rekordbox and coerce it into a Track entity
|
|
11308
11285
|
*/
|
|
11309
11286
|
async function getMetadata(opts) {
|
|
11310
|
-
var _a, _b, _c, _d, _e;
|
|
11287
|
+
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;
|
|
11311
11288
|
const { conn, lookupDescriptor, span, args } = opts;
|
|
11312
11289
|
const { trackId } = args;
|
|
11313
11290
|
const request = new message_1.Message({
|
|
@@ -11324,24 +11301,25 @@ async function getMetadata(opts) {
|
|
|
11324
11301
|
trackItems[item.type] = item;
|
|
11325
11302
|
}
|
|
11326
11303
|
// Translate our trackItems into a (partial) Track entity.
|
|
11304
|
+
// Use optional chaining for all fields since streaming tracks may omit some.
|
|
11327
11305
|
const track = {
|
|
11328
|
-
id: trackItems[item_1.ItemType.TrackTitle].id,
|
|
11329
|
-
title: trackItems[item_1.ItemType.TrackTitle].title,
|
|
11330
|
-
duration: trackItems[item_1.ItemType.Duration].duration,
|
|
11331
|
-
tempo: trackItems[item_1.ItemType.Tempo].bpm,
|
|
11332
|
-
comment: trackItems[item_1.ItemType.Comment].comment,
|
|
11333
|
-
rating: trackItems[item_1.ItemType.Rating].rating,
|
|
11334
|
-
year: (
|
|
11335
|
-
bitrate: (
|
|
11336
|
-
artwork: { id: trackItems[item_1.ItemType.TrackTitle].artworkId },
|
|
11337
|
-
album: trackItems[item_1.ItemType.AlbumTitle],
|
|
11338
|
-
artist: trackItems[item_1.ItemType.Artist],
|
|
11339
|
-
genre: trackItems[item_1.ItemType.Genre],
|
|
11340
|
-
key: trackItems[item_1.ItemType.Key],
|
|
11341
|
-
color: (0, utils_1.findColor)(Object.values(trackItems)),
|
|
11342
|
-
label: (
|
|
11343
|
-
remixer: (
|
|
11344
|
-
originalArtist: (
|
|
11306
|
+
id: (_b = (_a = trackItems[item_1.ItemType.TrackTitle]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : 0,
|
|
11307
|
+
title: (_d = (_c = trackItems[item_1.ItemType.TrackTitle]) === null || _c === void 0 ? void 0 : _c.title) !== null && _d !== void 0 ? _d : '',
|
|
11308
|
+
duration: (_f = (_e = trackItems[item_1.ItemType.Duration]) === null || _e === void 0 ? void 0 : _e.duration) !== null && _f !== void 0 ? _f : 0,
|
|
11309
|
+
tempo: (_h = (_g = trackItems[item_1.ItemType.Tempo]) === null || _g === void 0 ? void 0 : _g.bpm) !== null && _h !== void 0 ? _h : 0,
|
|
11310
|
+
comment: (_k = (_j = trackItems[item_1.ItemType.Comment]) === null || _j === void 0 ? void 0 : _j.comment) !== null && _k !== void 0 ? _k : '',
|
|
11311
|
+
rating: (_m = (_l = trackItems[item_1.ItemType.Rating]) === null || _l === void 0 ? void 0 : _l.rating) !== null && _m !== void 0 ? _m : 0,
|
|
11312
|
+
year: (_o = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.Year]) === null || _o === void 0 ? void 0 : _o.year,
|
|
11313
|
+
bitrate: (_p = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.BitRate]) === null || _p === void 0 ? void 0 : _p.bitrate,
|
|
11314
|
+
artwork: { id: (_r = (_q = trackItems[item_1.ItemType.TrackTitle]) === null || _q === void 0 ? void 0 : _q.artworkId) !== null && _r !== void 0 ? _r : 0 },
|
|
11315
|
+
album: (_s = trackItems[item_1.ItemType.AlbumTitle]) !== null && _s !== void 0 ? _s : null,
|
|
11316
|
+
artist: (_t = trackItems[item_1.ItemType.Artist]) !== null && _t !== void 0 ? _t : null,
|
|
11317
|
+
genre: (_u = trackItems[item_1.ItemType.Genre]) !== null && _u !== void 0 ? _u : null,
|
|
11318
|
+
key: (_v = trackItems[item_1.ItemType.Key]) !== null && _v !== void 0 ? _v : null,
|
|
11319
|
+
color: (_w = (0, utils_1.findColor)(Object.values(trackItems))) !== null && _w !== void 0 ? _w : null,
|
|
11320
|
+
label: (_x = trackItems[item_1.ItemType.Label]) !== null && _x !== void 0 ? _x : null,
|
|
11321
|
+
remixer: (_y = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.Remixer]) !== null && _y !== void 0 ? _y : null,
|
|
11322
|
+
originalArtist: (_z = trackItems === null || trackItems === void 0 ? void 0 : trackItems[item_1.ItemType.OriginalArtist]) !== null && _z !== void 0 ? _z : null,
|
|
11345
11323
|
composer: null,
|
|
11346
11324
|
fileName: '',
|
|
11347
11325
|
filePath: '',
|
|
@@ -11949,7 +11927,7 @@ function mediaSlotFromPacket(packet) {
|
|
|
11949
11927
|
return undefined;
|
|
11950
11928
|
}
|
|
11951
11929
|
const name = packet
|
|
11952
|
-
.slice(0x2c,
|
|
11930
|
+
.slice(0x2c, 0x2c + 40)
|
|
11953
11931
|
.toString()
|
|
11954
11932
|
.replace(/\0/g, '');
|
|
11955
11933
|
const createdDate = new Date(packet
|
|
@@ -12126,7 +12104,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
12126
12104
|
};
|
|
12127
12105
|
})();
|
|
12128
12106
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
12129
|
-
exports.MixstatusMode = exports.NetworkState = exports.
|
|
12107
|
+
exports.MixstatusMode = exports.NetworkState = exports.HotcueButton = exports.CueColor = exports.TrackType = exports.MediaSlot = exports.MediaColor = exports.DeviceType = exports.CDJStatus = void 0;
|
|
12130
12108
|
exports.CDJStatus = __importStar(__webpack_require__(/*! src/status/types */ "./src/status/types.ts"));
|
|
12131
12109
|
/**
|
|
12132
12110
|
* Known device types on the network
|
|
@@ -12159,6 +12137,11 @@ var MediaSlot;
|
|
|
12159
12137
|
MediaSlot[MediaSlot["SD"] = 2] = "SD";
|
|
12160
12138
|
MediaSlot[MediaSlot["USB"] = 3] = "USB";
|
|
12161
12139
|
MediaSlot[MediaSlot["RB"] = 4] = "RB";
|
|
12140
|
+
MediaSlot[MediaSlot["Unknown05"] = 5] = "Unknown05";
|
|
12141
|
+
MediaSlot[MediaSlot["StreamingDirectPlay"] = 6] = "StreamingDirectPlay";
|
|
12142
|
+
MediaSlot[MediaSlot["Unknown07"] = 7] = "Unknown07";
|
|
12143
|
+
MediaSlot[MediaSlot["Unknown08"] = 8] = "Unknown08";
|
|
12144
|
+
MediaSlot[MediaSlot["Beatport"] = 9] = "Beatport";
|
|
12162
12145
|
})(MediaSlot || (exports.MediaSlot = MediaSlot = {}));
|
|
12163
12146
|
/**
|
|
12164
12147
|
* Track type flags
|
|
@@ -12169,46 +12152,14 @@ var TrackType;
|
|
|
12169
12152
|
TrackType[TrackType["RB"] = 1] = "RB";
|
|
12170
12153
|
TrackType[TrackType["Unanalyzed"] = 2] = "Unanalyzed";
|
|
12171
12154
|
TrackType[TrackType["AudioCD"] = 5] = "AudioCD";
|
|
12155
|
+
TrackType[TrackType["Streaming"] = 6] = "Streaming";
|
|
12172
12156
|
})(TrackType || (exports.TrackType = TrackType = {}));
|
|
12173
12157
|
/**
|
|
12174
|
-
*
|
|
12175
|
-
*/
|
|
12176
|
-
var HotcueButton;
|
|
12177
|
-
(function (HotcueButton) {
|
|
12178
|
-
HotcueButton[HotcueButton["A"] = 1] = "A";
|
|
12179
|
-
HotcueButton[HotcueButton["B"] = 2] = "B";
|
|
12180
|
-
HotcueButton[HotcueButton["C"] = 3] = "C";
|
|
12181
|
-
HotcueButton[HotcueButton["D"] = 4] = "D";
|
|
12182
|
-
HotcueButton[HotcueButton["E"] = 5] = "E";
|
|
12183
|
-
HotcueButton[HotcueButton["F"] = 6] = "F";
|
|
12184
|
-
HotcueButton[HotcueButton["G"] = 7] = "G";
|
|
12185
|
-
HotcueButton[HotcueButton["H"] = 8] = "H";
|
|
12186
|
-
})(HotcueButton || (exports.HotcueButton = HotcueButton = {}));
|
|
12187
|
-
/**
|
|
12188
|
-
* When a custom color is not configured the cue point will be one of these
|
|
12189
|
-
* colors.
|
|
12158
|
+
* Re-export cue types from onelibrary-connect
|
|
12190
12159
|
*/
|
|
12191
|
-
var
|
|
12192
|
-
(function (
|
|
12193
|
-
|
|
12194
|
-
CueColor[CueColor["Blank"] = 21] = "Blank";
|
|
12195
|
-
CueColor[CueColor["Magenta"] = 49] = "Magenta";
|
|
12196
|
-
CueColor[CueColor["Violet"] = 56] = "Violet";
|
|
12197
|
-
CueColor[CueColor["Fuchsia"] = 60] = "Fuchsia";
|
|
12198
|
-
CueColor[CueColor["LightSlateBlue"] = 62] = "LightSlateBlue";
|
|
12199
|
-
CueColor[CueColor["Blue"] = 1] = "Blue";
|
|
12200
|
-
CueColor[CueColor["SteelBlue"] = 5] = "SteelBlue";
|
|
12201
|
-
CueColor[CueColor["Aqua"] = 9] = "Aqua";
|
|
12202
|
-
CueColor[CueColor["SeaGreen"] = 14] = "SeaGreen";
|
|
12203
|
-
CueColor[CueColor["Teal"] = 18] = "Teal";
|
|
12204
|
-
CueColor[CueColor["Green"] = 22] = "Green";
|
|
12205
|
-
CueColor[CueColor["Lime"] = 26] = "Lime";
|
|
12206
|
-
CueColor[CueColor["Olive"] = 30] = "Olive";
|
|
12207
|
-
CueColor[CueColor["Yellow"] = 32] = "Yellow";
|
|
12208
|
-
CueColor[CueColor["Orange"] = 38] = "Orange";
|
|
12209
|
-
CueColor[CueColor["Red"] = 42] = "Red";
|
|
12210
|
-
CueColor[CueColor["Pink"] = 45] = "Pink";
|
|
12211
|
-
})(CueColor || (exports.CueColor = CueColor = {}));
|
|
12160
|
+
var onelibrary_connect_1 = __webpack_require__(/*! onelibrary-connect */ "onelibrary-connect");
|
|
12161
|
+
Object.defineProperty(exports, "CueColor", ({ enumerable: true, get: function () { return onelibrary_connect_1.CueColor; } }));
|
|
12162
|
+
Object.defineProperty(exports, "HotcueButton", ({ enumerable: true, get: function () { return onelibrary_connect_1.HotcueButton; } }));
|
|
12212
12163
|
var NetworkState;
|
|
12213
12164
|
(function (NetworkState) {
|
|
12214
12165
|
/**
|
|
@@ -12366,6 +12317,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
12366
12317
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
12367
12318
|
exports.buildName = buildName;
|
|
12368
12319
|
exports.getMatchingInterface = getMatchingInterface;
|
|
12320
|
+
exports.getBroadcastAddress = getBroadcastAddress;
|
|
12369
12321
|
exports.bpmToSeconds = bpmToSeconds;
|
|
12370
12322
|
exports.getSlotName = getSlotName;
|
|
12371
12323
|
exports.getTrackTypeName = getTrackTypeName;
|
|
@@ -12403,6 +12355,18 @@ function getMatchingInterface(ipAddr) {
|
|
|
12403
12355
|
}
|
|
12404
12356
|
return matchedIface;
|
|
12405
12357
|
}
|
|
12358
|
+
/**
|
|
12359
|
+
* Computes the IPv4 subnet broadcast address for a network interface.
|
|
12360
|
+
*
|
|
12361
|
+
* Builds the address from the interface CIDR so the broadcast covers the whole
|
|
12362
|
+
* subnet (e.g. x.x.x.255 for a /24). Falls back to the bare interface address
|
|
12363
|
+
* when no CIDR is available, which only reaches the host itself but avoids
|
|
12364
|
+
* throwing.
|
|
12365
|
+
*/
|
|
12366
|
+
function getBroadcastAddress(iface) {
|
|
12367
|
+
var _a;
|
|
12368
|
+
return new ip.Address4((_a = iface.cidr) !== null && _a !== void 0 ? _a : iface.address).endAddress().address;
|
|
12369
|
+
}
|
|
12406
12370
|
/**
|
|
12407
12371
|
* Given a BPM and pitch value, compute how many seconds per beat
|
|
12408
12372
|
*/
|
|
@@ -12660,13 +12624,14 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
12660
12624
|
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");
|
|
12661
12625
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12662
12626
|
};
|
|
12663
|
-
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;
|
|
12627
|
+
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;
|
|
12664
12628
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
12665
12629
|
exports.Announcer = exports.getVirtualCDJ = void 0;
|
|
12666
12630
|
exports.makeStatusPacket = makeStatusPacket;
|
|
12667
12631
|
exports.makeAnnouncePacket = makeAnnouncePacket;
|
|
12668
12632
|
const ip = __importStar(__webpack_require__(/*! ip-address */ "ip-address"));
|
|
12669
12633
|
const constants_1 = __webpack_require__(/*! src/constants */ "./src/constants.ts");
|
|
12634
|
+
const logger_1 = __webpack_require__(/*! src/logger */ "./src/logger.ts");
|
|
12670
12635
|
const types_1 = __webpack_require__(/*! src/types */ "./src/types.ts");
|
|
12671
12636
|
const utils_1 = __webpack_require__(/*! src/utils */ "./src/utils/index.ts");
|
|
12672
12637
|
/**
|
|
@@ -12914,7 +12879,7 @@ function parseConflictPacket(packet) {
|
|
|
12914
12879
|
* as if it was a real CDJ.
|
|
12915
12880
|
*/
|
|
12916
12881
|
class Announcer {
|
|
12917
|
-
constructor(vcdj, announceSocket, deviceManager, iface, fullStartup = false) {
|
|
12882
|
+
constructor(vcdj, announceSocket, deviceManager, iface, fullStartup = false, announceToStagehand = false, logger = logger_1.noopLogger) {
|
|
12918
12883
|
_Announcer_instances.add(this);
|
|
12919
12884
|
/**
|
|
12920
12885
|
* The announce socket to use to make the announcements
|
|
@@ -12937,6 +12902,11 @@ class Announcer {
|
|
|
12937
12902
|
* Whether to use full startup protocol
|
|
12938
12903
|
*/
|
|
12939
12904
|
_Announcer_fullStartup.set(this, void 0);
|
|
12905
|
+
/**
|
|
12906
|
+
* Whether to send announcer packets to Pioneer Stagehand devices, which are
|
|
12907
|
+
* normally excluded because they crash on our packets
|
|
12908
|
+
*/
|
|
12909
|
+
_Announcer_announceToStagehand.set(this, void 0);
|
|
12940
12910
|
/**
|
|
12941
12911
|
* Current startup stage (only used when fullStartup is enabled)
|
|
12942
12912
|
*/
|
|
@@ -12953,11 +12923,36 @@ class Announcer {
|
|
|
12953
12923
|
* Network interface for the virtual CDJ
|
|
12954
12924
|
*/
|
|
12955
12925
|
_Announcer_iface.set(this, void 0);
|
|
12926
|
+
/**
|
|
12927
|
+
* Logger instance for diagnostic output
|
|
12928
|
+
*/
|
|
12929
|
+
_Announcer_logger.set(this, void 0);
|
|
12930
|
+
/**
|
|
12931
|
+
* Callback invoked when the full startup protocol completes
|
|
12932
|
+
*/
|
|
12933
|
+
_Announcer_onStartupComplete.set(this, void 0);
|
|
12956
12934
|
__classPrivateFieldSet(this, _Announcer_vcdj, vcdj, "f");
|
|
12957
12935
|
__classPrivateFieldSet(this, _Announcer_announceSocket, announceSocket, "f");
|
|
12958
12936
|
__classPrivateFieldSet(this, _Announcer_deviceManager, deviceManager, "f");
|
|
12959
12937
|
__classPrivateFieldSet(this, _Announcer_iface, iface, "f");
|
|
12960
12938
|
__classPrivateFieldSet(this, _Announcer_fullStartup, fullStartup, "f");
|
|
12939
|
+
__classPrivateFieldSet(this, _Announcer_announceToStagehand, announceToStagehand, "f");
|
|
12940
|
+
__classPrivateFieldSet(this, _Announcer_logger, logger, "f");
|
|
12941
|
+
}
|
|
12942
|
+
/**
|
|
12943
|
+
* Returns a promise that resolves when the startup protocol completes.
|
|
12944
|
+
* Resolves immediately if fullStartup is disabled.
|
|
12945
|
+
*/
|
|
12946
|
+
get ready() {
|
|
12947
|
+
if (!__classPrivateFieldGet(this, _Announcer_fullStartup, "f")) {
|
|
12948
|
+
return Promise.resolve();
|
|
12949
|
+
}
|
|
12950
|
+
if (__classPrivateFieldGet(this, _Announcer_currentStage, "f") === StartupStage.KeepAlive) {
|
|
12951
|
+
return Promise.resolve();
|
|
12952
|
+
}
|
|
12953
|
+
return new Promise(resolve => {
|
|
12954
|
+
__classPrivateFieldSet(this, _Announcer_onStartupComplete, resolve, "f");
|
|
12955
|
+
});
|
|
12961
12956
|
}
|
|
12962
12957
|
start() {
|
|
12963
12958
|
if (__classPrivateFieldGet(this, _Announcer_fullStartup, "f")) {
|
|
@@ -12981,14 +12976,14 @@ class Announcer {
|
|
|
12981
12976
|
}
|
|
12982
12977
|
}
|
|
12983
12978
|
exports.Announcer = Announcer;
|
|
12984
|
-
_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() {
|
|
12979
|
+
_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() {
|
|
12985
12980
|
__classPrivateFieldSet(this, _Announcer_currentStage, StartupStage.InitialAnnounce, "f");
|
|
12986
12981
|
__classPrivateFieldSet(this, _Announcer_stageCounter, 0, "f");
|
|
12987
12982
|
// Set up conflict detection listener
|
|
12988
12983
|
__classPrivateFieldSet(this, _Announcer_conflictListener, (msg) => {
|
|
12989
12984
|
const conflictDeviceId = parseConflictPacket(msg);
|
|
12990
12985
|
if (conflictDeviceId === __classPrivateFieldGet(this, _Announcer_vcdj, "f").id) {
|
|
12991
|
-
|
|
12986
|
+
__classPrivateFieldGet(this, _Announcer_logger, "f").warn(`Device ID ${__classPrivateFieldGet(this, _Announcer_vcdj, "f").id} is already in use. Finding alternative...`);
|
|
12992
12987
|
__classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_handleConflict).call(this);
|
|
12993
12988
|
}
|
|
12994
12989
|
}, "f");
|
|
@@ -13019,20 +13014,29 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
|
|
|
13019
13014
|
}
|
|
13020
13015
|
}
|
|
13021
13016
|
if (newId === null) {
|
|
13022
|
-
|
|
13017
|
+
__classPrivateFieldGet(this, _Announcer_logger, "f").error('No available device IDs. All 32 slots are occupied.');
|
|
13023
13018
|
this.stop();
|
|
13024
13019
|
return;
|
|
13025
13020
|
}
|
|
13026
|
-
|
|
13021
|
+
__classPrivateFieldGet(this, _Announcer_logger, "f").info(`Switching to device ID ${newId}`);
|
|
13027
13022
|
// Update virtual CDJ with new ID
|
|
13028
13023
|
__classPrivateFieldSet(this, _Announcer_vcdj, (0, exports.getVirtualCDJ)(__classPrivateFieldGet(this, _Announcer_iface, "f"), newId), "f");
|
|
13029
13024
|
// Restart startup sequence
|
|
13030
13025
|
__classPrivateFieldSet(this, _Announcer_currentStage, StartupStage.InitialAnnounce, "f");
|
|
13031
13026
|
__classPrivateFieldSet(this, _Announcer_stageCounter, 0, "f");
|
|
13032
13027
|
__classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_sendStagePackets).call(this);
|
|
13028
|
+
}, _Announcer_sendPacket = function _Announcer_sendPacket(packet) {
|
|
13029
|
+
const devices = [...__classPrivateFieldGet(this, _Announcer_deviceManager, "f").devices.values()].filter(d => __classPrivateFieldGet(this, _Announcer_announceToStagehand, "f") || !d.name.toLowerCase().includes('stagehand'));
|
|
13030
|
+
devices.forEach(device => __classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, device.ip.address));
|
|
13031
|
+
// Cold-start discovery broadcast — gated; see the method doc above.
|
|
13032
|
+
if (devices.length === 0 && __classPrivateFieldGet(this, _Announcer_announceToStagehand, "f")) {
|
|
13033
|
+
const broadcastAddr = (0, utils_1.getBroadcastAddress)(__classPrivateFieldGet(this, _Announcer_iface, "f"));
|
|
13034
|
+
__classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, broadcastAddr);
|
|
13035
|
+
}
|
|
13033
13036
|
}, _Announcer_sendStagePackets = function _Announcer_sendStagePackets() {
|
|
13034
13037
|
var _a;
|
|
13035
|
-
|
|
13038
|
+
var _b;
|
|
13039
|
+
__classPrivateFieldSet(this, _Announcer_stageCounter, (_b = __classPrivateFieldGet(this, _Announcer_stageCounter, "f"), _b++, _b), "f");
|
|
13036
13040
|
// Build packet for current stage
|
|
13037
13041
|
let packet;
|
|
13038
13042
|
switch (__classPrivateFieldGet(this, _Announcer_currentStage, "f")) {
|
|
@@ -13051,16 +13055,11 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
|
|
|
13051
13055
|
case StartupStage.KeepAlive:
|
|
13052
13056
|
// Transition to keep-alive mode
|
|
13053
13057
|
__classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_startKeepAlive).call(this);
|
|
13058
|
+
(_a = __classPrivateFieldGet(this, _Announcer_onStartupComplete, "f")) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
13059
|
+
__classPrivateFieldSet(this, _Announcer_onStartupComplete, undefined, "f");
|
|
13054
13060
|
return;
|
|
13055
13061
|
}
|
|
13056
|
-
|
|
13057
|
-
const devices = [...__classPrivateFieldGet(this, _Announcer_deviceManager, "f").devices.values()];
|
|
13058
|
-
devices.forEach(device => __classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, device.ip.address));
|
|
13059
|
-
// Also broadcast to network if no devices yet
|
|
13060
|
-
if (devices.length === 0) {
|
|
13061
|
-
const broadcastAddr = __classPrivateFieldGet(this, _Announcer_vcdj, "f").ip.endAddress().address;
|
|
13062
|
-
__classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, broadcastAddr);
|
|
13063
|
-
}
|
|
13062
|
+
__classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_sendPacket).call(this, packet);
|
|
13064
13063
|
// Progress to next packet or stage
|
|
13065
13064
|
if (__classPrivateFieldGet(this, _Announcer_stageCounter, "f") >= 3) {
|
|
13066
13065
|
// Move to next stage
|
|
@@ -13099,13 +13098,7 @@ _Announcer_announceSocket = new WeakMap(), _Announcer_deviceManager = new WeakMa
|
|
|
13099
13098
|
const packet = __classPrivateFieldGet(this, _Announcer_fullStartup, "f")
|
|
13100
13099
|
? makeStage06Packet(__classPrivateFieldGet(this, _Announcer_vcdj, "f"), peerCount)
|
|
13101
13100
|
: makeAnnouncePacket(__classPrivateFieldGet(this, _Announcer_vcdj, "f"));
|
|
13102
|
-
|
|
13103
|
-
devices.forEach(device => __classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, device.ip.address));
|
|
13104
|
-
// Also broadcast if no devices
|
|
13105
|
-
if (devices.length === 0) {
|
|
13106
|
-
const broadcastAddr = __classPrivateFieldGet(this, _Announcer_vcdj, "f").ip.endAddress().address;
|
|
13107
|
-
__classPrivateFieldGet(this, _Announcer_announceSocket, "f").send(packet, constants_1.ANNOUNCE_PORT, broadcastAddr);
|
|
13108
|
-
}
|
|
13101
|
+
__classPrivateFieldGet(this, _Announcer_instances, "m", _Announcer_sendPacket).call(this, packet);
|
|
13109
13102
|
};
|
|
13110
13103
|
// Send first keep-alive immediately
|
|
13111
13104
|
sendKeepAlive();
|
|
@@ -13259,6 +13252,17 @@ module.exports = require("lodash");
|
|
|
13259
13252
|
|
|
13260
13253
|
/***/ },
|
|
13261
13254
|
|
|
13255
|
+
/***/ "metadata-connect"
|
|
13256
|
+
/*!***********************************!*\
|
|
13257
|
+
!*** external "metadata-connect" ***!
|
|
13258
|
+
\***********************************/
|
|
13259
|
+
(module) {
|
|
13260
|
+
|
|
13261
|
+
"use strict";
|
|
13262
|
+
module.exports = require("metadata-connect");
|
|
13263
|
+
|
|
13264
|
+
/***/ },
|
|
13265
|
+
|
|
13262
13266
|
/***/ "net"
|
|
13263
13267
|
/*!**********************!*\
|
|
13264
13268
|
!*** external "net" ***!
|
|
@@ -13270,6 +13274,17 @@ module.exports = require("net");
|
|
|
13270
13274
|
|
|
13271
13275
|
/***/ },
|
|
13272
13276
|
|
|
13277
|
+
/***/ "onelibrary-connect"
|
|
13278
|
+
/*!*************************************!*\
|
|
13279
|
+
!*** external "onelibrary-connect" ***!
|
|
13280
|
+
\*************************************/
|
|
13281
|
+
(module) {
|
|
13282
|
+
|
|
13283
|
+
"use strict";
|
|
13284
|
+
module.exports = require("onelibrary-connect");
|
|
13285
|
+
|
|
13286
|
+
/***/ },
|
|
13287
|
+
|
|
13273
13288
|
/***/ "os"
|
|
13274
13289
|
/*!*********************!*\
|
|
13275
13290
|
!*** external "os" ***!
|
|
@@ -13323,17 +13338,6 @@ module.exports = require("promise-socket");
|
|
|
13323
13338
|
"use strict";
|
|
13324
13339
|
module.exports = require("promise-timeout");
|
|
13325
13340
|
|
|
13326
|
-
/***/ },
|
|
13327
|
-
|
|
13328
|
-
/***/ "zlib"
|
|
13329
|
-
/*!***********************!*\
|
|
13330
|
-
!*** external "zlib" ***!
|
|
13331
|
-
\***********************/
|
|
13332
|
-
(module) {
|
|
13333
|
-
|
|
13334
|
-
"use strict";
|
|
13335
|
-
module.exports = require("zlib");
|
|
13336
|
-
|
|
13337
13341
|
/***/ }
|
|
13338
13342
|
|
|
13339
13343
|
/******/ });
|