reelsort 0.2.2 → 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 +380 -202
- package/dist/index.d.mts +7 -10
- package/dist/index.d.ts +7 -10
- package/dist/index.js +125 -42
- package/dist/index.mjs +131 -48
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -226,7 +226,7 @@ var clean_default = clean;
|
|
|
226
226
|
|
|
227
227
|
// src/actions/config.ts
|
|
228
228
|
import { resolve } from "path";
|
|
229
|
-
import { Color as Color2 } from "termkit";
|
|
229
|
+
import { Color as Color2, MultiSelect, Select } from "termkit";
|
|
230
230
|
|
|
231
231
|
// src/config.ts
|
|
232
232
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
@@ -290,8 +290,20 @@ var sourceAdd = async ({ dir }) => {
|
|
|
290
290
|
spinner_default.stop();
|
|
291
291
|
};
|
|
292
292
|
var sourceRemove = async ({ dir }) => {
|
|
293
|
-
const resolved = resolve(dir);
|
|
294
293
|
const config = getConfig();
|
|
294
|
+
if (!dir) {
|
|
295
|
+
if (config.sources.length === 0) {
|
|
296
|
+
spinner_default.start();
|
|
297
|
+
spinner_default.warn("no sources configured");
|
|
298
|
+
spinner_default.stop();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const select = new Select();
|
|
302
|
+
const picked = await select.ask("Which source do you want to remove?", config.sources.map((s) => ({ label: s, value: s })));
|
|
303
|
+
if (!picked) return;
|
|
304
|
+
dir = picked.value;
|
|
305
|
+
}
|
|
306
|
+
const resolved = resolve(dir);
|
|
295
307
|
const index = config.sources.indexOf(resolved);
|
|
296
308
|
if (index === -1) {
|
|
297
309
|
spinner_default.start();
|
|
@@ -318,10 +330,23 @@ var destAdd = async ({ type, dir }) => {
|
|
|
318
330
|
spinner_default.stop();
|
|
319
331
|
};
|
|
320
332
|
var destRemove = async ({ type }) => {
|
|
333
|
+
const config = getConfig();
|
|
334
|
+
if (!type) {
|
|
335
|
+
const configured = DEST_TYPES.filter((t) => config.dest[t]);
|
|
336
|
+
if (configured.length === 0) {
|
|
337
|
+
spinner_default.start();
|
|
338
|
+
spinner_default.warn("no destinations configured");
|
|
339
|
+
spinner_default.stop();
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const select = new Select();
|
|
343
|
+
const picked = await select.ask("Which destination do you want to remove?", configured.map((t) => ({ label: `${t.padEnd(6)} ${config.dest[t]}`, value: t })));
|
|
344
|
+
if (!picked) return;
|
|
345
|
+
type = picked.value;
|
|
346
|
+
}
|
|
321
347
|
if (!DEST_TYPES.includes(type)) {
|
|
322
348
|
throw new Error(`unknown type '${type}', expected: ${DEST_TYPES.join(", ")}`);
|
|
323
349
|
}
|
|
324
|
-
const config = getConfig();
|
|
325
350
|
if (!config.dest[type]) {
|
|
326
351
|
spinner_default.start();
|
|
327
352
|
spinner_default.warn(`no ${type} destination configured`);
|
|
@@ -399,6 +424,12 @@ Subtitle language: ${Color2.green.encoder(config.language ?? "eng (default)")}`)
|
|
|
399
424
|
console.log(`Movie format: ${Color2.green.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
|
|
400
425
|
console.log(`Episode format: ${Color2.green.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
|
|
401
426
|
console.log(`Season folder: ${Color2.green.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
|
|
427
|
+
console.log("\nIgnored files:");
|
|
428
|
+
if (!config.ignore || config.ignore.length === 0) {
|
|
429
|
+
console.log(" (none)");
|
|
430
|
+
} else {
|
|
431
|
+
for (const name of config.ignore) console.log(` ${Color2.white.encoder(name)}`);
|
|
432
|
+
}
|
|
402
433
|
console.log();
|
|
403
434
|
};
|
|
404
435
|
|
|
@@ -706,6 +737,12 @@ import { spawnSync } from "child_process";
|
|
|
706
737
|
import { existsSync as existsSync6, lstatSync as lstatSync2, readdirSync as readdirSync4 } from "fs";
|
|
707
738
|
import { resolve as resolve5 } from "path";
|
|
708
739
|
import { Color as Color6 } from "termkit";
|
|
740
|
+
|
|
741
|
+
// src/refs/verbose.ts
|
|
742
|
+
var _verbose = false;
|
|
743
|
+
var isVerbose = () => _verbose;
|
|
744
|
+
|
|
745
|
+
// src/actions/probe.ts
|
|
709
746
|
var DEST_TYPES3 = ["movie", "tv", "ps3"];
|
|
710
747
|
var CODEC_MAP2 = {
|
|
711
748
|
hevc: "x265",
|
|
@@ -767,7 +804,7 @@ var walkVideoFiles = (dir, depth = 0, maxDepth = 3) => {
|
|
|
767
804
|
}
|
|
768
805
|
return results;
|
|
769
806
|
};
|
|
770
|
-
var probe = async ({ type, force
|
|
807
|
+
var probe = async ({ type, force }) => {
|
|
771
808
|
spinner_default.start();
|
|
772
809
|
if (!isFfprobeAvailable()) {
|
|
773
810
|
spinner_default.fail("ffprobe not found \u2014 install ffmpeg to use this command");
|
|
@@ -785,19 +822,19 @@ var probe = async ({ type, force, verbose }) => {
|
|
|
785
822
|
const files = walkVideoFiles(destRoot);
|
|
786
823
|
for (const filePath of files) {
|
|
787
824
|
if (!force && getMediaInfo(filePath)) {
|
|
788
|
-
if (
|
|
825
|
+
if (isVerbose()) spinner_default.info(`already probed: ${filePath}`);
|
|
789
826
|
skipped++;
|
|
790
827
|
continue;
|
|
791
828
|
}
|
|
792
829
|
spinner_default.text = `probing ${Color6.white.encoder(filePath)}`;
|
|
793
830
|
const result = runFfprobe(filePath);
|
|
794
831
|
if (!result) {
|
|
795
|
-
if (
|
|
832
|
+
if (isVerbose()) spinner_default.warn(`ffprobe failed: ${filePath}`);
|
|
796
833
|
failed++;
|
|
797
834
|
continue;
|
|
798
835
|
}
|
|
799
836
|
upsertMediaInfo(filePath, result.codec, result.resolution, result.width, result.height, result.duration);
|
|
800
|
-
if (
|
|
837
|
+
if (isVerbose()) spinner_default.succeed(`${result.resolution ?? "?"} ${result.codec ?? "?"} ${filePath}`);
|
|
801
838
|
probed++;
|
|
802
839
|
}
|
|
803
840
|
}
|
|
@@ -867,7 +904,7 @@ var titleCase_default = (s) => {
|
|
|
867
904
|
};
|
|
868
905
|
|
|
869
906
|
// src/actions/rename.ts
|
|
870
|
-
var rename = async ({ dir: inputDir, type
|
|
907
|
+
var rename = async ({ dir: inputDir, type }) => {
|
|
871
908
|
const dir = resolve6(inputDir);
|
|
872
909
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
873
910
|
const config = getConfig();
|
|
@@ -881,7 +918,7 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
881
918
|
for (const [index, entry] of list2.entries()) {
|
|
882
919
|
spinner_default.text = `renaming in ${Color7.white.encoder(dir)} ${index + 1}/${list2.length}`;
|
|
883
920
|
if (!lstatSync3(resolve6(dir, entry)).isDirectory()) {
|
|
884
|
-
if (
|
|
921
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
885
922
|
skipped++;
|
|
886
923
|
continue;
|
|
887
924
|
}
|
|
@@ -891,7 +928,7 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
891
928
|
const nameMatch = entry.match(/(?<=\[).+?(?=\])/);
|
|
892
929
|
const id = entry.split("-")[0];
|
|
893
930
|
if (!nameMatch || !id) {
|
|
894
|
-
if (
|
|
931
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
895
932
|
skipped++;
|
|
896
933
|
continue;
|
|
897
934
|
}
|
|
@@ -905,13 +942,13 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
905
942
|
}
|
|
906
943
|
const yearMatch = entry.match(/\([^\d]*(\d+)[^\d]*\)/);
|
|
907
944
|
if (!yearMatch) {
|
|
908
|
-
if (
|
|
945
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
909
946
|
skipped++;
|
|
910
947
|
continue;
|
|
911
948
|
}
|
|
912
949
|
const year = yearMatch[0];
|
|
913
950
|
if (year.length !== 6) {
|
|
914
|
-
if (
|
|
951
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
915
952
|
skipped++;
|
|
916
953
|
continue;
|
|
917
954
|
}
|
|
@@ -922,20 +959,20 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
922
959
|
return videoExtensions_default.includes(ext2) && title.split(" ").reduce((a, w) => f.toLowerCase().includes(w.toLowerCase()) ? a : false, true);
|
|
923
960
|
});
|
|
924
961
|
if (!video) {
|
|
925
|
-
if (
|
|
962
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
926
963
|
skipped++;
|
|
927
964
|
continue;
|
|
928
965
|
}
|
|
929
966
|
const ext = video.match(/([^.]+$)/)?.[0];
|
|
930
967
|
if (!ext) {
|
|
931
|
-
if (
|
|
968
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
932
969
|
skipped++;
|
|
933
970
|
continue;
|
|
934
971
|
}
|
|
935
972
|
const yearNum = parseInt(year.replace(/\D/g, ""));
|
|
936
973
|
const formatted = formatMovieName(movieFormat, title, yearNum);
|
|
937
974
|
if (entry === formatted && video === `${formatted}.${ext}`) {
|
|
938
|
-
if (
|
|
975
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
939
976
|
skipped++;
|
|
940
977
|
continue;
|
|
941
978
|
}
|
|
@@ -1025,7 +1062,7 @@ var reset_default = reset;
|
|
|
1025
1062
|
// src/actions/scan.ts
|
|
1026
1063
|
import { cpSync, existsSync as existsSync9, linkSync, lstatSync as lstatSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync7, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
1027
1064
|
import { dirname as dirname2, resolve as resolve8 } from "path";
|
|
1028
|
-
import { Color as Color9, MultiSelect, Select } from "termkit";
|
|
1065
|
+
import { Color as Color9, MultiSelect as MultiSelect2, Select as Select2 } from "termkit";
|
|
1029
1066
|
|
|
1030
1067
|
// src/helpers/detectEdition.ts
|
|
1031
1068
|
var EDITIONS = [
|
|
@@ -1261,6 +1298,18 @@ var gatherEntries = (source) => {
|
|
|
1261
1298
|
}
|
|
1262
1299
|
return result;
|
|
1263
1300
|
};
|
|
1301
|
+
var findShowFolder = (destRoot, title) => {
|
|
1302
|
+
if (!existsSync9(destRoot)) return null;
|
|
1303
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1304
|
+
const target = normalize(title);
|
|
1305
|
+
return readdirSync7(destRoot).filter((f) => {
|
|
1306
|
+
try {
|
|
1307
|
+
return lstatSync4(resolve8(destRoot, f)).isDirectory();
|
|
1308
|
+
} catch {
|
|
1309
|
+
return false;
|
|
1310
|
+
}
|
|
1311
|
+
}).find((f) => normalize(f) === target) ?? null;
|
|
1312
|
+
};
|
|
1264
1313
|
var findSeasonFolder = (showPath, season) => {
|
|
1265
1314
|
if (!existsSync9(showPath)) return null;
|
|
1266
1315
|
const folders = readdirSync7(showPath).filter((f) => {
|
|
@@ -1284,7 +1333,14 @@ var classifyMovieConfidence = (entry) => {
|
|
|
1284
1333
|
if (/\.\d{4}\..*?(?:2160p|1080p|720p|BluRay|WEBRip|WEB-DL|HDTV)/i.test(entry)) return "auto";
|
|
1285
1334
|
return "ambiguous";
|
|
1286
1335
|
};
|
|
1287
|
-
var
|
|
1336
|
+
var typeColor = {
|
|
1337
|
+
movie: Color9.white.cyan,
|
|
1338
|
+
tv: Color9.white.green,
|
|
1339
|
+
book: Color9.white.yellow,
|
|
1340
|
+
ps3: Color9.white.magenta
|
|
1341
|
+
};
|
|
1342
|
+
var typeTag = (t) => isVerbose() ? Color9.white.faint.encoder(` (${t})`) : "";
|
|
1343
|
+
var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
|
|
1288
1344
|
const config = getConfig();
|
|
1289
1345
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1290
1346
|
const language = config.language ?? "eng";
|
|
@@ -1302,7 +1358,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1302
1358
|
resolvedYear = results[0].year ?? parsed.year;
|
|
1303
1359
|
} else if (results.length > 1) {
|
|
1304
1360
|
spinner_default.stop();
|
|
1305
|
-
const select = new
|
|
1361
|
+
const select = new Select2();
|
|
1306
1362
|
const items = results.map((r) => ({
|
|
1307
1363
|
label: r.year ? `${r.title} (${r.year})` : r.title,
|
|
1308
1364
|
description: [r.overview?.slice(0, 60), hyperlink(r.url, "themoviedb.org")].filter(Boolean).join(" \xB7 "),
|
|
@@ -1329,7 +1385,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1329
1385
|
}
|
|
1330
1386
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1331
1387
|
if (!videoFile) {
|
|
1332
|
-
if (
|
|
1388
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1333
1389
|
return false;
|
|
1334
1390
|
}
|
|
1335
1391
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
@@ -1376,13 +1432,16 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1376
1432
|
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1377
1433
|
}
|
|
1378
1434
|
}
|
|
1379
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${folderName}`);
|
|
1435
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
|
|
1380
1436
|
return true;
|
|
1381
1437
|
};
|
|
1382
1438
|
spinner_default.start();
|
|
1383
1439
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
1384
1440
|
let imported = 0, skipped = 0;
|
|
1385
1441
|
const pendingMovies = [];
|
|
1442
|
+
const pendingTv = [];
|
|
1443
|
+
const ignoreSet = new Set(config.ignore ?? []);
|
|
1444
|
+
const seenIgnored = /* @__PURE__ */ new Set();
|
|
1386
1445
|
for (const source of config.sources) {
|
|
1387
1446
|
if (!existsSync9(source)) {
|
|
1388
1447
|
spinner_default.warn(`source not found: ${Color9.white.encoder(source)}`);
|
|
@@ -1390,6 +1449,11 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1390
1449
|
}
|
|
1391
1450
|
spinner_default.text = `scanning ${Color9.white.encoder(source)}`;
|
|
1392
1451
|
for (const { entry, entryPath, isDir } of gatherEntries(source)) {
|
|
1452
|
+
if (ignoreSet.has(entry)) {
|
|
1453
|
+
seenIgnored.add(entry);
|
|
1454
|
+
if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
|
|
1455
|
+
continue;
|
|
1456
|
+
}
|
|
1393
1457
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
1394
1458
|
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1395
1459
|
const isBookDir = isDir && containsBook(entryPath);
|
|
@@ -1407,7 +1471,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1407
1471
|
}
|
|
1408
1472
|
const destRoot = config.dest[detectedType];
|
|
1409
1473
|
if (!destRoot) {
|
|
1410
|
-
if (
|
|
1474
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
1411
1475
|
skipped++;
|
|
1412
1476
|
continue;
|
|
1413
1477
|
}
|
|
@@ -1429,7 +1493,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1429
1493
|
moveFolder(entryPath, destPath);
|
|
1430
1494
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1431
1495
|
}
|
|
1432
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${destName}`);
|
|
1496
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
|
|
1433
1497
|
imported++;
|
|
1434
1498
|
continue;
|
|
1435
1499
|
}
|
|
@@ -1454,20 +1518,20 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1454
1518
|
}
|
|
1455
1519
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1456
1520
|
}
|
|
1457
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${entry}`);
|
|
1521
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.book.encoder(entry)}${typeTag("book")}`);
|
|
1458
1522
|
imported++;
|
|
1459
1523
|
continue;
|
|
1460
1524
|
}
|
|
1461
1525
|
const parsed = parseDownloadName(entry);
|
|
1462
1526
|
if (!parsed) {
|
|
1463
|
-
if (
|
|
1527
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
1464
1528
|
skipped++;
|
|
1465
1529
|
continue;
|
|
1466
1530
|
}
|
|
1467
1531
|
if (detectedType === "movie") {
|
|
1468
1532
|
const confidence = classifyMovieConfidence(entry);
|
|
1469
1533
|
if (confidence === "skip") {
|
|
1470
|
-
if (
|
|
1534
|
+
if (isVerbose()) spinner_default.info(`skipped (uncertain): ${entry}`);
|
|
1471
1535
|
skipped++;
|
|
1472
1536
|
continue;
|
|
1473
1537
|
}
|
|
@@ -1488,7 +1552,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1488
1552
|
resolvedYear = results[0].year ?? parsed.year;
|
|
1489
1553
|
} else if (results.length > 1) {
|
|
1490
1554
|
spinner_default.stop();
|
|
1491
|
-
const select = new
|
|
1555
|
+
const select = new Select2();
|
|
1492
1556
|
const items = results.map((r) => ({
|
|
1493
1557
|
label: r.year ? `${r.title} (${r.year})` : r.title,
|
|
1494
1558
|
description: [r.overview?.slice(0, 60), hyperlink(r.url, "themoviedb.org")].filter(Boolean).join(" \xB7 "),
|
|
@@ -1511,7 +1575,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1511
1575
|
}
|
|
1512
1576
|
if (detectedType === "tv") {
|
|
1513
1577
|
if (parsed.season === void 0) {
|
|
1514
|
-
if (
|
|
1578
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
1515
1579
|
skipped++;
|
|
1516
1580
|
continue;
|
|
1517
1581
|
}
|
|
@@ -1521,20 +1585,25 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1521
1585
|
if (registeredShow) {
|
|
1522
1586
|
showPath = registeredShow.path;
|
|
1523
1587
|
showFolderName = showPath.split("/").pop() ?? registeredShow.path;
|
|
1524
|
-
} else if (auto) {
|
|
1525
|
-
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1526
|
-
showPath = resolve8(destRoot, showFolderName);
|
|
1527
|
-
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1528
1588
|
} else {
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1589
|
+
const existingFolder = findShowFolder(destRoot, resolvedTitle);
|
|
1590
|
+
if (existingFolder) {
|
|
1591
|
+
showFolderName = existingFolder;
|
|
1592
|
+
showPath = resolve8(destRoot, existingFolder);
|
|
1593
|
+
} else if (auto) {
|
|
1594
|
+
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1595
|
+
showPath = resolve8(destRoot, showFolderName);
|
|
1596
|
+
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1597
|
+
} else {
|
|
1598
|
+
pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1532
1601
|
}
|
|
1533
1602
|
const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1534
1603
|
const seasonPath = resolve8(showPath, seasonFolderName);
|
|
1535
1604
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1536
1605
|
if (!videoFile) {
|
|
1537
|
-
if (
|
|
1606
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1538
1607
|
skipped++;
|
|
1539
1608
|
continue;
|
|
1540
1609
|
}
|
|
@@ -1548,7 +1617,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1548
1617
|
let shouldReplace = force;
|
|
1549
1618
|
if (!shouldReplace && interactive) {
|
|
1550
1619
|
spinner_default.stop();
|
|
1551
|
-
const select = new
|
|
1620
|
+
const select = new Select2();
|
|
1552
1621
|
const picked = await select.ask(`Already exists \u2014 replace?`, [
|
|
1553
1622
|
{ label: `${showFolderName} / ${seasonFolderName} / ${episodeName}`, value: "replace" },
|
|
1554
1623
|
{ label: "Skip", value: "skip" }
|
|
@@ -1598,7 +1667,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1598
1667
|
}
|
|
1599
1668
|
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
1600
1669
|
}
|
|
1601
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${showFolderName} / ${seasonFolderName} / ${episodeName}`);
|
|
1670
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
|
|
1602
1671
|
imported++;
|
|
1603
1672
|
continue;
|
|
1604
1673
|
}
|
|
@@ -1611,11 +1680,11 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1611
1680
|
}
|
|
1612
1681
|
if (pendingMovies.length > 0) {
|
|
1613
1682
|
spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
|
|
1614
|
-
for (const p of pendingMovies) spinner_default.info(` ? ${p.entry.replace(/\/$/, "")}`);
|
|
1683
|
+
for (const p of pendingMovies) spinner_default.info(` ${typeColor.movie.encoder("?")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
|
|
1615
1684
|
let toProcess = [];
|
|
1616
1685
|
if (interactive) {
|
|
1617
1686
|
spinner_default.stop();
|
|
1618
|
-
const ms = new
|
|
1687
|
+
const ms = new MultiSelect2({ allowSkip: true, search: true, maxHeight: 20 });
|
|
1619
1688
|
const items = pendingMovies.map((p) => ({
|
|
1620
1689
|
label: p.entry.replace(/\/$/, ""),
|
|
1621
1690
|
description: p.parsed.year ? `parsed: ${p.parsed.title} \xB7 ${p.parsed.year}` : `parsed: ${p.parsed.title}`,
|
|
@@ -1638,6 +1707,20 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1638
1707
|
}
|
|
1639
1708
|
}
|
|
1640
1709
|
}
|
|
1710
|
+
if (pendingTv.length > 0) {
|
|
1711
|
+
spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
|
|
1712
|
+
for (const p of pendingTv) spinner_default.info(` ${typeColor.tv.encoder("?")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
1713
|
+
skipped += pendingTv.length;
|
|
1714
|
+
}
|
|
1715
|
+
if (ignoreSet.size > 0) {
|
|
1716
|
+
const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
|
|
1717
|
+
if (stale.length > 0 && !dryRun) {
|
|
1718
|
+
const updated = config.ignore.filter((name) => !stale.includes(name));
|
|
1719
|
+
config.ignore = updated;
|
|
1720
|
+
saveConfig(config);
|
|
1721
|
+
for (const name of stale) spinner_default.info(`removed from ignore list (not found): ${Color9.white.encoder(name)}`);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1641
1724
|
spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
|
|
1642
1725
|
if (skipped) spinner_default.info(`skipped ${skipped} items`);
|
|
1643
1726
|
spinner_default.stop();
|
|
@@ -1787,7 +1870,7 @@ var findSeasonFolder2 = (showPath, season) => {
|
|
|
1787
1870
|
return match && parseInt(match[1]) === season;
|
|
1788
1871
|
}) ?? null;
|
|
1789
1872
|
};
|
|
1790
|
-
var processItem = async (entryPath, useHardlink,
|
|
1873
|
+
var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
1791
1874
|
const config = getConfig();
|
|
1792
1875
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1793
1876
|
const entry = basename3(entryPath);
|
|
@@ -1811,7 +1894,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1811
1894
|
}
|
|
1812
1895
|
const destRoot = config.dest[detectedType];
|
|
1813
1896
|
if (!destRoot) {
|
|
1814
|
-
if (
|
|
1897
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
1815
1898
|
return;
|
|
1816
1899
|
}
|
|
1817
1900
|
if (detectedType === "ps3") {
|
|
@@ -1852,12 +1935,12 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1852
1935
|
}
|
|
1853
1936
|
const parsed = parseDownloadName(entry);
|
|
1854
1937
|
if (!parsed) {
|
|
1855
|
-
if (
|
|
1938
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
1856
1939
|
return;
|
|
1857
1940
|
}
|
|
1858
1941
|
if (detectedType === "tv") {
|
|
1859
1942
|
if (parsed.season === void 0) {
|
|
1860
|
-
if (
|
|
1943
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
1861
1944
|
return;
|
|
1862
1945
|
}
|
|
1863
1946
|
const registeredShow = getShowByTitle(parsed.title);
|
|
@@ -1871,14 +1954,14 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1871
1954
|
showPath = resolve9(destRoot, showFolderName);
|
|
1872
1955
|
upsertShow(showPath, null, parsed.title);
|
|
1873
1956
|
} else {
|
|
1874
|
-
if (
|
|
1957
|
+
if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
|
|
1875
1958
|
return;
|
|
1876
1959
|
}
|
|
1877
1960
|
const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1878
1961
|
const seasonPath = resolve9(showPath, seasonFolderName);
|
|
1879
1962
|
const videoFile2 = isDir ? findVideo2(entryPath) : entry;
|
|
1880
1963
|
if (!videoFile2) {
|
|
1881
|
-
if (
|
|
1964
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1882
1965
|
return;
|
|
1883
1966
|
}
|
|
1884
1967
|
const videoExt2 = videoFile2.match(/([^.]+$)/)?.[0];
|
|
@@ -1932,7 +2015,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1932
2015
|
}
|
|
1933
2016
|
const videoFile = isDir ? findVideo2(entryPath) : entry;
|
|
1934
2017
|
if (!videoFile) {
|
|
1935
|
-
if (
|
|
2018
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1936
2019
|
return;
|
|
1937
2020
|
}
|
|
1938
2021
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
@@ -1979,7 +2062,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1979
2062
|
}
|
|
1980
2063
|
spinner_default.succeed(`imported ${Color11.green.encoder(folderName)}`);
|
|
1981
2064
|
};
|
|
1982
|
-
var watch = async ({ hardlink = false,
|
|
2065
|
+
var watch = async ({ hardlink = false, auto = false }) => {
|
|
1983
2066
|
const config = getConfig();
|
|
1984
2067
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
1985
2068
|
const language = config.language ?? "eng";
|
|
@@ -1993,7 +2076,7 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
1993
2076
|
pending.delete(path);
|
|
1994
2077
|
try {
|
|
1995
2078
|
for (const entry of expandWatchPath(path)) {
|
|
1996
|
-
await processItem(entry, hardlink,
|
|
2079
|
+
await processItem(entry, hardlink, language, auto);
|
|
1997
2080
|
}
|
|
1998
2081
|
} catch (err) {
|
|
1999
2082
|
spinner_default.fail(`error processing ${path}: ${err.message}`);
|
package/package.json
CHANGED