reelsort 0.2.1 → 0.2.3
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/dist/cli.js +537 -200
- package/dist/index.d.mts +7 -10
- package/dist/index.d.ts +7 -10
- package/dist/index.js +299 -57
- package/dist/index.mjs +305 -63
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -366,8 +366,20 @@ var sourceAdd = async ({ dir }) => {
|
|
|
366
366
|
spinner_default.stop();
|
|
367
367
|
};
|
|
368
368
|
var sourceRemove = async ({ dir }) => {
|
|
369
|
-
const resolved = (0, import_path3.resolve)(dir);
|
|
370
369
|
const config = getConfig();
|
|
370
|
+
if (!dir) {
|
|
371
|
+
if (config.sources.length === 0) {
|
|
372
|
+
spinner_default.start();
|
|
373
|
+
spinner_default.warn("no sources configured");
|
|
374
|
+
spinner_default.stop();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const select = new import_termkit3.Select();
|
|
378
|
+
const picked = await select.ask("Which source do you want to remove?", config.sources.map((s) => ({ label: s, value: s })));
|
|
379
|
+
if (!picked) return;
|
|
380
|
+
dir = picked.value;
|
|
381
|
+
}
|
|
382
|
+
const resolved = (0, import_path3.resolve)(dir);
|
|
371
383
|
const index = config.sources.indexOf(resolved);
|
|
372
384
|
if (index === -1) {
|
|
373
385
|
spinner_default.start();
|
|
@@ -394,10 +406,23 @@ var destAdd = async ({ type, dir }) => {
|
|
|
394
406
|
spinner_default.stop();
|
|
395
407
|
};
|
|
396
408
|
var destRemove = async ({ type }) => {
|
|
409
|
+
const config = getConfig();
|
|
410
|
+
if (!type) {
|
|
411
|
+
const configured = DEST_TYPES.filter((t) => config.dest[t]);
|
|
412
|
+
if (configured.length === 0) {
|
|
413
|
+
spinner_default.start();
|
|
414
|
+
spinner_default.warn("no destinations configured");
|
|
415
|
+
spinner_default.stop();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const select = new import_termkit3.Select();
|
|
419
|
+
const picked = await select.ask("Which destination do you want to remove?", configured.map((t) => ({ label: `${t.padEnd(6)} ${config.dest[t]}`, value: t })));
|
|
420
|
+
if (!picked) return;
|
|
421
|
+
type = picked.value;
|
|
422
|
+
}
|
|
397
423
|
if (!DEST_TYPES.includes(type)) {
|
|
398
424
|
throw new Error(`unknown type '${type}', expected: ${DEST_TYPES.join(", ")}`);
|
|
399
425
|
}
|
|
400
|
-
const config = getConfig();
|
|
401
426
|
if (!config.dest[type]) {
|
|
402
427
|
spinner_default.start();
|
|
403
428
|
spinner_default.warn(`no ${type} destination configured`);
|
|
@@ -475,6 +500,12 @@ Subtitle language: ${import_termkit3.Color.green.encoder(config.language ?? "eng
|
|
|
475
500
|
console.log(`Movie format: ${import_termkit3.Color.green.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
|
|
476
501
|
console.log(`Episode format: ${import_termkit3.Color.green.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
|
|
477
502
|
console.log(`Season folder: ${import_termkit3.Color.green.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
|
|
503
|
+
console.log("\nIgnored files:");
|
|
504
|
+
if (!config.ignore || config.ignore.length === 0) {
|
|
505
|
+
console.log(" (none)");
|
|
506
|
+
} else {
|
|
507
|
+
for (const name of config.ignore) console.log(` ${import_termkit3.Color.white.encoder(name)}`);
|
|
508
|
+
}
|
|
478
509
|
console.log();
|
|
479
510
|
};
|
|
480
511
|
|
|
@@ -782,6 +813,12 @@ var import_child_process = require("child_process");
|
|
|
782
813
|
var import_fs7 = require("fs");
|
|
783
814
|
var import_path8 = require("path");
|
|
784
815
|
var import_termkit7 = require("termkit");
|
|
816
|
+
|
|
817
|
+
// src/refs/verbose.ts
|
|
818
|
+
var _verbose = false;
|
|
819
|
+
var isVerbose = () => _verbose;
|
|
820
|
+
|
|
821
|
+
// src/actions/probe.ts
|
|
785
822
|
var DEST_TYPES3 = ["movie", "tv", "ps3"];
|
|
786
823
|
var CODEC_MAP2 = {
|
|
787
824
|
hevc: "x265",
|
|
@@ -843,7 +880,7 @@ var walkVideoFiles = (dir, depth = 0, maxDepth = 3) => {
|
|
|
843
880
|
}
|
|
844
881
|
return results;
|
|
845
882
|
};
|
|
846
|
-
var probe = async ({ type, force
|
|
883
|
+
var probe = async ({ type, force }) => {
|
|
847
884
|
spinner_default.start();
|
|
848
885
|
if (!isFfprobeAvailable()) {
|
|
849
886
|
spinner_default.fail("ffprobe not found \u2014 install ffmpeg to use this command");
|
|
@@ -861,19 +898,19 @@ var probe = async ({ type, force, verbose }) => {
|
|
|
861
898
|
const files = walkVideoFiles(destRoot);
|
|
862
899
|
for (const filePath of files) {
|
|
863
900
|
if (!force && getMediaInfo(filePath)) {
|
|
864
|
-
if (
|
|
901
|
+
if (isVerbose()) spinner_default.info(`already probed: ${filePath}`);
|
|
865
902
|
skipped++;
|
|
866
903
|
continue;
|
|
867
904
|
}
|
|
868
905
|
spinner_default.text = `probing ${import_termkit7.Color.white.encoder(filePath)}`;
|
|
869
906
|
const result = runFfprobe(filePath);
|
|
870
907
|
if (!result) {
|
|
871
|
-
if (
|
|
908
|
+
if (isVerbose()) spinner_default.warn(`ffprobe failed: ${filePath}`);
|
|
872
909
|
failed++;
|
|
873
910
|
continue;
|
|
874
911
|
}
|
|
875
912
|
upsertMediaInfo(filePath, result.codec, result.resolution, result.width, result.height, result.duration);
|
|
876
|
-
if (
|
|
913
|
+
if (isVerbose()) spinner_default.succeed(`${result.resolution ?? "?"} ${result.codec ?? "?"} ${filePath}`);
|
|
877
914
|
probed++;
|
|
878
915
|
}
|
|
879
916
|
}
|
|
@@ -943,7 +980,7 @@ var titleCase_default = (s) => {
|
|
|
943
980
|
};
|
|
944
981
|
|
|
945
982
|
// src/actions/rename.ts
|
|
946
|
-
var rename = async ({ dir: inputDir, type
|
|
983
|
+
var rename = async ({ dir: inputDir, type }) => {
|
|
947
984
|
const dir = (0, import_path9.resolve)(inputDir);
|
|
948
985
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
949
986
|
const config = getConfig();
|
|
@@ -957,7 +994,7 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
957
994
|
for (const [index, entry] of list2.entries()) {
|
|
958
995
|
spinner_default.text = `renaming in ${import_termkit8.Color.white.encoder(dir)} ${index + 1}/${list2.length}`;
|
|
959
996
|
if (!(0, import_fs8.lstatSync)((0, import_path9.resolve)(dir, entry)).isDirectory()) {
|
|
960
|
-
if (
|
|
997
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
961
998
|
skipped++;
|
|
962
999
|
continue;
|
|
963
1000
|
}
|
|
@@ -967,7 +1004,7 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
967
1004
|
const nameMatch = entry.match(/(?<=\[).+?(?=\])/);
|
|
968
1005
|
const id = entry.split("-")[0];
|
|
969
1006
|
if (!nameMatch || !id) {
|
|
970
|
-
if (
|
|
1007
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
971
1008
|
skipped++;
|
|
972
1009
|
continue;
|
|
973
1010
|
}
|
|
@@ -981,13 +1018,13 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
981
1018
|
}
|
|
982
1019
|
const yearMatch = entry.match(/\([^\d]*(\d+)[^\d]*\)/);
|
|
983
1020
|
if (!yearMatch) {
|
|
984
|
-
if (
|
|
1021
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
985
1022
|
skipped++;
|
|
986
1023
|
continue;
|
|
987
1024
|
}
|
|
988
1025
|
const year = yearMatch[0];
|
|
989
1026
|
if (year.length !== 6) {
|
|
990
|
-
if (
|
|
1027
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
991
1028
|
skipped++;
|
|
992
1029
|
continue;
|
|
993
1030
|
}
|
|
@@ -998,20 +1035,20 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
998
1035
|
return videoExtensions_default.includes(ext2) && title.split(" ").reduce((a, w) => f.toLowerCase().includes(w.toLowerCase()) ? a : false, true);
|
|
999
1036
|
});
|
|
1000
1037
|
if (!video) {
|
|
1001
|
-
if (
|
|
1038
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1002
1039
|
skipped++;
|
|
1003
1040
|
continue;
|
|
1004
1041
|
}
|
|
1005
1042
|
const ext = video.match(/([^.]+$)/)?.[0];
|
|
1006
1043
|
if (!ext) {
|
|
1007
|
-
if (
|
|
1044
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1008
1045
|
skipped++;
|
|
1009
1046
|
continue;
|
|
1010
1047
|
}
|
|
1011
1048
|
const yearNum = parseInt(year.replace(/\D/g, ""));
|
|
1012
1049
|
const formatted = formatMovieName(movieFormat, title, yearNum);
|
|
1013
1050
|
if (entry === formatted && video === `${formatted}.${ext}`) {
|
|
1014
|
-
if (
|
|
1051
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1015
1052
|
skipped++;
|
|
1016
1053
|
continue;
|
|
1017
1054
|
}
|
|
@@ -1244,10 +1281,111 @@ var findVideo = (dir) => (0, import_fs10.readdirSync)(dir).find((f) => {
|
|
|
1244
1281
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1245
1282
|
return ext && videoExtensions_default.includes(ext);
|
|
1246
1283
|
}) ?? null;
|
|
1247
|
-
var containsBook = (dir) => (0, import_fs10.readdirSync)(dir).some((f) => {
|
|
1284
|
+
var containsBook = (dir, depth = 2) => (0, import_fs10.readdirSync)(dir).some((f) => {
|
|
1248
1285
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1249
|
-
|
|
1286
|
+
if (ext && bookExtensions_default.includes(ext)) return true;
|
|
1287
|
+
if (depth > 1) {
|
|
1288
|
+
try {
|
|
1289
|
+
const sub = (0, import_path11.resolve)(dir, f);
|
|
1290
|
+
if ((0, import_fs10.lstatSync)(sub).isDirectory()) return containsBook(sub, depth - 1);
|
|
1291
|
+
} catch {
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
return false;
|
|
1250
1295
|
});
|
|
1296
|
+
var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
|
|
1297
|
+
var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
|
|
1298
|
+
var gatherEntries = (source) => {
|
|
1299
|
+
const result = [];
|
|
1300
|
+
for (const name of (0, import_fs10.readdirSync)(source)) {
|
|
1301
|
+
const fullPath = (0, import_path11.resolve)(source, name);
|
|
1302
|
+
let isDir;
|
|
1303
|
+
try {
|
|
1304
|
+
isDir = (0, import_fs10.lstatSync)(fullPath).isDirectory();
|
|
1305
|
+
} catch {
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1308
|
+
const ext = name.match(/([^.]+$)/)?.[0];
|
|
1309
|
+
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
1310
|
+
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1311
|
+
if (!isDir && !isVideo && !isBook) continue;
|
|
1312
|
+
if (!isDir) {
|
|
1313
|
+
result.push({ entry: name, entryPath: fullPath, isDir: false });
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
if (/(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name) || isTvEpisodeName(name)) {
|
|
1317
|
+
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
let children;
|
|
1321
|
+
try {
|
|
1322
|
+
children = (0, import_fs10.readdirSync)(fullPath);
|
|
1323
|
+
} catch {
|
|
1324
|
+
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
if (children.some((c) => isTvEpisodeName(c))) {
|
|
1328
|
+
for (const child of children) {
|
|
1329
|
+
const childPath = (0, import_path11.resolve)(fullPath, child);
|
|
1330
|
+
let childIsDir;
|
|
1331
|
+
try {
|
|
1332
|
+
childIsDir = (0, import_fs10.lstatSync)(childPath).isDirectory();
|
|
1333
|
+
} catch {
|
|
1334
|
+
continue;
|
|
1335
|
+
}
|
|
1336
|
+
const childExt = child.match(/([^.]+$)/)?.[0];
|
|
1337
|
+
if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
|
|
1338
|
+
result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
|
|
1339
|
+
}
|
|
1340
|
+
continue;
|
|
1341
|
+
}
|
|
1342
|
+
const seasonDirs = children.filter((c) => {
|
|
1343
|
+
try {
|
|
1344
|
+
return isSeasonDirName(c) && (0, import_fs10.lstatSync)((0, import_path11.resolve)(fullPath, c)).isDirectory();
|
|
1345
|
+
} catch {
|
|
1346
|
+
return false;
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
if (seasonDirs.length > 0) {
|
|
1350
|
+
for (const seasonDir of seasonDirs) {
|
|
1351
|
+
const seasonPath = (0, import_path11.resolve)(fullPath, seasonDir);
|
|
1352
|
+
let seasonChildren;
|
|
1353
|
+
try {
|
|
1354
|
+
seasonChildren = (0, import_fs10.readdirSync)(seasonPath);
|
|
1355
|
+
} catch {
|
|
1356
|
+
continue;
|
|
1357
|
+
}
|
|
1358
|
+
for (const child of seasonChildren) {
|
|
1359
|
+
const childPath = (0, import_path11.resolve)(seasonPath, child);
|
|
1360
|
+
let childIsDir;
|
|
1361
|
+
try {
|
|
1362
|
+
childIsDir = (0, import_fs10.lstatSync)(childPath).isDirectory();
|
|
1363
|
+
} catch {
|
|
1364
|
+
continue;
|
|
1365
|
+
}
|
|
1366
|
+
const childExt = child.match(/([^.]+$)/)?.[0];
|
|
1367
|
+
if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
|
|
1368
|
+
result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1374
|
+
}
|
|
1375
|
+
return result;
|
|
1376
|
+
};
|
|
1377
|
+
var findShowFolder = (destRoot, title) => {
|
|
1378
|
+
if (!(0, import_fs10.existsSync)(destRoot)) return null;
|
|
1379
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1380
|
+
const target = normalize(title);
|
|
1381
|
+
return (0, import_fs10.readdirSync)(destRoot).filter((f) => {
|
|
1382
|
+
try {
|
|
1383
|
+
return (0, import_fs10.lstatSync)((0, import_path11.resolve)(destRoot, f)).isDirectory();
|
|
1384
|
+
} catch {
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
}).find((f) => normalize(f) === target) ?? null;
|
|
1388
|
+
};
|
|
1251
1389
|
var findSeasonFolder = (showPath, season) => {
|
|
1252
1390
|
if (!(0, import_fs10.existsSync)(showPath)) return null;
|
|
1253
1391
|
const folders = (0, import_fs10.readdirSync)(showPath).filter((f) => {
|
|
@@ -1271,7 +1409,14 @@ var classifyMovieConfidence = (entry) => {
|
|
|
1271
1409
|
if (/\.\d{4}\..*?(?:2160p|1080p|720p|BluRay|WEBRip|WEB-DL|HDTV)/i.test(entry)) return "auto";
|
|
1272
1410
|
return "ambiguous";
|
|
1273
1411
|
};
|
|
1274
|
-
var
|
|
1412
|
+
var typeColor = {
|
|
1413
|
+
movie: import_termkit10.Color.white.cyan,
|
|
1414
|
+
tv: import_termkit10.Color.white.green,
|
|
1415
|
+
book: import_termkit10.Color.white.yellow,
|
|
1416
|
+
ps3: import_termkit10.Color.white.magenta
|
|
1417
|
+
};
|
|
1418
|
+
var typeTag = (t) => isVerbose() ? import_termkit10.Color.white.faint.encoder(` (${t})`) : "";
|
|
1419
|
+
var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
|
|
1275
1420
|
const config = getConfig();
|
|
1276
1421
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1277
1422
|
const language = config.language ?? "eng";
|
|
@@ -1316,7 +1461,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1316
1461
|
}
|
|
1317
1462
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1318
1463
|
if (!videoFile) {
|
|
1319
|
-
if (
|
|
1464
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1320
1465
|
return false;
|
|
1321
1466
|
}
|
|
1322
1467
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
@@ -1363,37 +1508,37 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1363
1508
|
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1364
1509
|
}
|
|
1365
1510
|
}
|
|
1366
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${folderName}`);
|
|
1511
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
|
|
1367
1512
|
return true;
|
|
1368
1513
|
};
|
|
1369
1514
|
spinner_default.start();
|
|
1370
1515
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
1371
1516
|
let imported = 0, skipped = 0;
|
|
1372
1517
|
const pendingMovies = [];
|
|
1518
|
+
const pendingTv = [];
|
|
1519
|
+
const ignoreSet = new Set(config.ignore ?? []);
|
|
1520
|
+
const seenIgnored = /* @__PURE__ */ new Set();
|
|
1373
1521
|
for (const source of config.sources) {
|
|
1374
1522
|
if (!(0, import_fs10.existsSync)(source)) {
|
|
1375
1523
|
spinner_default.warn(`source not found: ${import_termkit10.Color.white.encoder(source)}`);
|
|
1376
1524
|
continue;
|
|
1377
1525
|
}
|
|
1378
1526
|
spinner_default.text = `scanning ${import_termkit10.Color.white.encoder(source)}`;
|
|
1379
|
-
for (const entry of (
|
|
1380
|
-
|
|
1381
|
-
|
|
1527
|
+
for (const { entry, entryPath, isDir } of gatherEntries(source)) {
|
|
1528
|
+
if (ignoreSet.has(entry)) {
|
|
1529
|
+
seenIgnored.add(entry);
|
|
1530
|
+
if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1382
1533
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
1383
|
-
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
1384
1534
|
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1385
1535
|
const isBookDir = isDir && containsBook(entryPath);
|
|
1386
|
-
if (!isDir && !isVideo && !isBook) {
|
|
1387
|
-
if (verbose) spinner_default.info(`skipped ${entry}`);
|
|
1388
|
-
skipped++;
|
|
1389
|
-
continue;
|
|
1390
|
-
}
|
|
1391
1536
|
let detectedType;
|
|
1392
1537
|
if (type) {
|
|
1393
1538
|
detectedType = type;
|
|
1394
1539
|
} else if (isBook || isBookDir) {
|
|
1395
1540
|
detectedType = "book";
|
|
1396
|
-
} else if (isDir &&
|
|
1541
|
+
} else if (isDir && /^[A-Z]{4}\d{5}/i.test(entry)) {
|
|
1397
1542
|
detectedType = "ps3";
|
|
1398
1543
|
} else if (/S\d{2,3}E\d{2,3}/i.test(entry) || /\d+x\d{2,3}/i.test(entry)) {
|
|
1399
1544
|
detectedType = "tv";
|
|
@@ -1402,7 +1547,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1402
1547
|
}
|
|
1403
1548
|
const destRoot = config.dest[detectedType];
|
|
1404
1549
|
if (!destRoot) {
|
|
1405
|
-
if (
|
|
1550
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
1406
1551
|
skipped++;
|
|
1407
1552
|
continue;
|
|
1408
1553
|
}
|
|
@@ -1424,7 +1569,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1424
1569
|
moveFolder(entryPath, destPath);
|
|
1425
1570
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1426
1571
|
}
|
|
1427
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${destName}`);
|
|
1572
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
|
|
1428
1573
|
imported++;
|
|
1429
1574
|
continue;
|
|
1430
1575
|
}
|
|
@@ -1449,20 +1594,20 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1449
1594
|
}
|
|
1450
1595
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1451
1596
|
}
|
|
1452
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${entry}`);
|
|
1597
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.book.encoder(entry)}${typeTag("book")}`);
|
|
1453
1598
|
imported++;
|
|
1454
1599
|
continue;
|
|
1455
1600
|
}
|
|
1456
1601
|
const parsed = parseDownloadName(entry);
|
|
1457
1602
|
if (!parsed) {
|
|
1458
|
-
if (
|
|
1603
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
1459
1604
|
skipped++;
|
|
1460
1605
|
continue;
|
|
1461
1606
|
}
|
|
1462
1607
|
if (detectedType === "movie") {
|
|
1463
1608
|
const confidence = classifyMovieConfidence(entry);
|
|
1464
1609
|
if (confidence === "skip") {
|
|
1465
|
-
if (
|
|
1610
|
+
if (isVerbose()) spinner_default.info(`skipped (uncertain): ${entry}`);
|
|
1466
1611
|
skipped++;
|
|
1467
1612
|
continue;
|
|
1468
1613
|
}
|
|
@@ -1506,7 +1651,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1506
1651
|
}
|
|
1507
1652
|
if (detectedType === "tv") {
|
|
1508
1653
|
if (parsed.season === void 0) {
|
|
1509
|
-
if (
|
|
1654
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
1510
1655
|
skipped++;
|
|
1511
1656
|
continue;
|
|
1512
1657
|
}
|
|
@@ -1516,20 +1661,25 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1516
1661
|
if (registeredShow) {
|
|
1517
1662
|
showPath = registeredShow.path;
|
|
1518
1663
|
showFolderName = showPath.split("/").pop() ?? registeredShow.path;
|
|
1519
|
-
} else if (auto) {
|
|
1520
|
-
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1521
|
-
showPath = (0, import_path11.resolve)(destRoot, showFolderName);
|
|
1522
|
-
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1523
1664
|
} else {
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1665
|
+
const existingFolder = findShowFolder(destRoot, resolvedTitle);
|
|
1666
|
+
if (existingFolder) {
|
|
1667
|
+
showFolderName = existingFolder;
|
|
1668
|
+
showPath = (0, import_path11.resolve)(destRoot, existingFolder);
|
|
1669
|
+
} else if (auto) {
|
|
1670
|
+
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1671
|
+
showPath = (0, import_path11.resolve)(destRoot, showFolderName);
|
|
1672
|
+
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1673
|
+
} else {
|
|
1674
|
+
pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1527
1677
|
}
|
|
1528
1678
|
const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1529
1679
|
const seasonPath = (0, import_path11.resolve)(showPath, seasonFolderName);
|
|
1530
1680
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1531
1681
|
if (!videoFile) {
|
|
1532
|
-
if (
|
|
1682
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1533
1683
|
skipped++;
|
|
1534
1684
|
continue;
|
|
1535
1685
|
}
|
|
@@ -1593,7 +1743,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1593
1743
|
}
|
|
1594
1744
|
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
1595
1745
|
}
|
|
1596
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${showFolderName} / ${seasonFolderName} / ${episodeName}`);
|
|
1746
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
|
|
1597
1747
|
imported++;
|
|
1598
1748
|
continue;
|
|
1599
1749
|
}
|
|
@@ -1606,7 +1756,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1606
1756
|
}
|
|
1607
1757
|
if (pendingMovies.length > 0) {
|
|
1608
1758
|
spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
|
|
1609
|
-
for (const p of pendingMovies) spinner_default.info(` ? ${p.entry.replace(/\/$/, "")}`);
|
|
1759
|
+
for (const p of pendingMovies) spinner_default.info(` ${typeColor.movie.encoder("?")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
|
|
1610
1760
|
let toProcess = [];
|
|
1611
1761
|
if (interactive) {
|
|
1612
1762
|
spinner_default.stop();
|
|
@@ -1633,7 +1783,21 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1633
1783
|
}
|
|
1634
1784
|
}
|
|
1635
1785
|
}
|
|
1636
|
-
|
|
1786
|
+
if (pendingTv.length > 0) {
|
|
1787
|
+
spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
|
|
1788
|
+
for (const p of pendingTv) spinner_default.info(` ${typeColor.tv.encoder("?")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
1789
|
+
skipped += pendingTv.length;
|
|
1790
|
+
}
|
|
1791
|
+
if (ignoreSet.size > 0) {
|
|
1792
|
+
const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
|
|
1793
|
+
if (stale.length > 0 && !dryRun) {
|
|
1794
|
+
const updated = config.ignore.filter((name) => !stale.includes(name));
|
|
1795
|
+
config.ignore = updated;
|
|
1796
|
+
saveConfig(config);
|
|
1797
|
+
for (const name of stale) spinner_default.info(`removed from ignore list (not found): ${import_termkit10.Color.white.encoder(name)}`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
|
|
1637
1801
|
if (skipped) spinner_default.info(`skipped ${skipped} items`);
|
|
1638
1802
|
spinner_default.stop();
|
|
1639
1803
|
};
|
|
@@ -1688,10 +1852,86 @@ var findVideo2 = (dir) => (0, import_fs12.readdirSync)(dir).find((f) => {
|
|
|
1688
1852
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1689
1853
|
return ext && videoExtensions_default.includes(ext);
|
|
1690
1854
|
}) ?? null;
|
|
1691
|
-
var containsBook2 = (dir) => (0, import_fs12.readdirSync)(dir).some((f) => {
|
|
1855
|
+
var containsBook2 = (dir, depth = 2) => (0, import_fs12.readdirSync)(dir).some((f) => {
|
|
1692
1856
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1693
|
-
|
|
1857
|
+
if (ext && bookExtensions_default.includes(ext)) return true;
|
|
1858
|
+
if (depth > 1) {
|
|
1859
|
+
try {
|
|
1860
|
+
const sub = (0, import_path12.resolve)(dir, f);
|
|
1861
|
+
if ((0, import_fs12.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
|
|
1862
|
+
} catch {
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
return false;
|
|
1694
1866
|
});
|
|
1867
|
+
var isTvEpisodeName2 = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
|
|
1868
|
+
var isSeasonDirName2 = (name) => !isTvEpisodeName2(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
|
|
1869
|
+
var expandWatchPath = (p) => {
|
|
1870
|
+
let isDir;
|
|
1871
|
+
try {
|
|
1872
|
+
isDir = (0, import_fs12.lstatSync)(p).isDirectory();
|
|
1873
|
+
} catch {
|
|
1874
|
+
return [p];
|
|
1875
|
+
}
|
|
1876
|
+
if (!isDir) return [p];
|
|
1877
|
+
const name = (0, import_path12.basename)(p);
|
|
1878
|
+
if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
|
|
1879
|
+
let children;
|
|
1880
|
+
try {
|
|
1881
|
+
children = (0, import_fs12.readdirSync)(p);
|
|
1882
|
+
} catch {
|
|
1883
|
+
return [p];
|
|
1884
|
+
}
|
|
1885
|
+
if (children.some((c) => isTvEpisodeName2(c))) {
|
|
1886
|
+
const entries = [];
|
|
1887
|
+
for (const child of children) {
|
|
1888
|
+
const cp = (0, import_path12.resolve)(p, child);
|
|
1889
|
+
let cd;
|
|
1890
|
+
try {
|
|
1891
|
+
cd = (0, import_fs12.lstatSync)(cp).isDirectory();
|
|
1892
|
+
} catch {
|
|
1893
|
+
continue;
|
|
1894
|
+
}
|
|
1895
|
+
const ext = child.match(/([^.]+$)/)?.[0];
|
|
1896
|
+
if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
|
|
1897
|
+
entries.push(cp);
|
|
1898
|
+
}
|
|
1899
|
+
return entries.length > 0 ? entries : [p];
|
|
1900
|
+
}
|
|
1901
|
+
const seasonDirs = children.filter((c) => {
|
|
1902
|
+
try {
|
|
1903
|
+
return isSeasonDirName2(c) && (0, import_fs12.lstatSync)((0, import_path12.resolve)(p, c)).isDirectory();
|
|
1904
|
+
} catch {
|
|
1905
|
+
return false;
|
|
1906
|
+
}
|
|
1907
|
+
});
|
|
1908
|
+
if (seasonDirs.length > 0) {
|
|
1909
|
+
const entries = [];
|
|
1910
|
+
for (const sd of seasonDirs) {
|
|
1911
|
+
const sp = (0, import_path12.resolve)(p, sd);
|
|
1912
|
+
let sc;
|
|
1913
|
+
try {
|
|
1914
|
+
sc = (0, import_fs12.readdirSync)(sp);
|
|
1915
|
+
} catch {
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
for (const child of sc) {
|
|
1919
|
+
const cp = (0, import_path12.resolve)(sp, child);
|
|
1920
|
+
let cd;
|
|
1921
|
+
try {
|
|
1922
|
+
cd = (0, import_fs12.lstatSync)(cp).isDirectory();
|
|
1923
|
+
} catch {
|
|
1924
|
+
continue;
|
|
1925
|
+
}
|
|
1926
|
+
const ext = child.match(/([^.]+$)/)?.[0];
|
|
1927
|
+
if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
|
|
1928
|
+
entries.push(cp);
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
return entries.length > 0 ? entries : [p];
|
|
1932
|
+
}
|
|
1933
|
+
return [p];
|
|
1934
|
+
};
|
|
1695
1935
|
var findSeasonFolder2 = (showPath, season) => {
|
|
1696
1936
|
if (!(0, import_fs12.existsSync)(showPath)) return null;
|
|
1697
1937
|
const folders = (0, import_fs12.readdirSync)(showPath).filter((f) => {
|
|
@@ -1706,7 +1946,7 @@ var findSeasonFolder2 = (showPath, season) => {
|
|
|
1706
1946
|
return match && parseInt(match[1]) === season;
|
|
1707
1947
|
}) ?? null;
|
|
1708
1948
|
};
|
|
1709
|
-
var processItem = async (entryPath, useHardlink,
|
|
1949
|
+
var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
1710
1950
|
const config = getConfig();
|
|
1711
1951
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1712
1952
|
const entry = (0, import_path12.basename)(entryPath);
|
|
@@ -1730,7 +1970,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1730
1970
|
}
|
|
1731
1971
|
const destRoot = config.dest[detectedType];
|
|
1732
1972
|
if (!destRoot) {
|
|
1733
|
-
if (
|
|
1973
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
1734
1974
|
return;
|
|
1735
1975
|
}
|
|
1736
1976
|
if (detectedType === "ps3") {
|
|
@@ -1771,12 +2011,12 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1771
2011
|
}
|
|
1772
2012
|
const parsed = parseDownloadName(entry);
|
|
1773
2013
|
if (!parsed) {
|
|
1774
|
-
if (
|
|
2014
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
1775
2015
|
return;
|
|
1776
2016
|
}
|
|
1777
2017
|
if (detectedType === "tv") {
|
|
1778
2018
|
if (parsed.season === void 0) {
|
|
1779
|
-
if (
|
|
2019
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
1780
2020
|
return;
|
|
1781
2021
|
}
|
|
1782
2022
|
const registeredShow = getShowByTitle(parsed.title);
|
|
@@ -1790,14 +2030,14 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1790
2030
|
showPath = (0, import_path12.resolve)(destRoot, showFolderName);
|
|
1791
2031
|
upsertShow(showPath, null, parsed.title);
|
|
1792
2032
|
} else {
|
|
1793
|
-
if (
|
|
2033
|
+
if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
|
|
1794
2034
|
return;
|
|
1795
2035
|
}
|
|
1796
2036
|
const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1797
2037
|
const seasonPath = (0, import_path12.resolve)(showPath, seasonFolderName);
|
|
1798
2038
|
const videoFile2 = isDir ? findVideo2(entryPath) : entry;
|
|
1799
2039
|
if (!videoFile2) {
|
|
1800
|
-
if (
|
|
2040
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1801
2041
|
return;
|
|
1802
2042
|
}
|
|
1803
2043
|
const videoExt2 = videoFile2.match(/([^.]+$)/)?.[0];
|
|
@@ -1851,7 +2091,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1851
2091
|
}
|
|
1852
2092
|
const videoFile = isDir ? findVideo2(entryPath) : entry;
|
|
1853
2093
|
if (!videoFile) {
|
|
1854
|
-
if (
|
|
2094
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1855
2095
|
return;
|
|
1856
2096
|
}
|
|
1857
2097
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
@@ -1898,7 +2138,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1898
2138
|
}
|
|
1899
2139
|
spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(folderName)}`);
|
|
1900
2140
|
};
|
|
1901
|
-
var watch = async ({ hardlink = false,
|
|
2141
|
+
var watch = async ({ hardlink = false, auto = false }) => {
|
|
1902
2142
|
const config = getConfig();
|
|
1903
2143
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
1904
2144
|
const language = config.language ?? "eng";
|
|
@@ -1911,7 +2151,9 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
1911
2151
|
setTimeout(async () => {
|
|
1912
2152
|
pending.delete(path);
|
|
1913
2153
|
try {
|
|
1914
|
-
|
|
2154
|
+
for (const entry of expandWatchPath(path)) {
|
|
2155
|
+
await processItem(entry, hardlink, language, auto);
|
|
2156
|
+
}
|
|
1915
2157
|
} catch (err) {
|
|
1916
2158
|
spinner_default.fail(`error processing ${path}: ${err.message}`);
|
|
1917
2159
|
}
|