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/cli.js
CHANGED
|
@@ -403,6 +403,7 @@ var clean = async ({ dryRun, olderThan }) => {
|
|
|
403
403
|
var clean_default = clean;
|
|
404
404
|
|
|
405
405
|
// src/actions/config.ts
|
|
406
|
+
var import_fs5 = require("fs");
|
|
406
407
|
var import_path4 = require("path");
|
|
407
408
|
var import_termkit4 = require("termkit");
|
|
408
409
|
|
|
@@ -424,6 +425,24 @@ var formatEpisode = (season, episode, format = DEFAULT_EPISODE_FORMAT, double =
|
|
|
424
425
|
|
|
425
426
|
// src/actions/config.ts
|
|
426
427
|
var DEST_TYPES = ["movie", "tv", "ps3", "book"];
|
|
428
|
+
var getSourceEntries = (sources) => {
|
|
429
|
+
const entries = [];
|
|
430
|
+
for (const source of sources) {
|
|
431
|
+
if (!(0, import_fs5.existsSync)(source)) continue;
|
|
432
|
+
try {
|
|
433
|
+
for (const name of (0, import_fs5.readdirSync)(source)) {
|
|
434
|
+
if (name.startsWith(".")) continue;
|
|
435
|
+
try {
|
|
436
|
+
(0, import_fs5.lstatSync)((0, import_path4.resolve)(source, name));
|
|
437
|
+
entries.push(name);
|
|
438
|
+
} catch {
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return [...new Set(entries)].sort();
|
|
445
|
+
};
|
|
427
446
|
var sourceAdd = async ({ dir }) => {
|
|
428
447
|
const resolved = (0, import_path4.resolve)(dir);
|
|
429
448
|
const config = getConfig();
|
|
@@ -440,8 +459,20 @@ var sourceAdd = async ({ dir }) => {
|
|
|
440
459
|
spinner_default.stop();
|
|
441
460
|
};
|
|
442
461
|
var sourceRemove = async ({ dir }) => {
|
|
443
|
-
const resolved = (0, import_path4.resolve)(dir);
|
|
444
462
|
const config = getConfig();
|
|
463
|
+
if (!dir) {
|
|
464
|
+
if (config.sources.length === 0) {
|
|
465
|
+
spinner_default.start();
|
|
466
|
+
spinner_default.warn("no sources configured");
|
|
467
|
+
spinner_default.stop();
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const select = new import_termkit4.Select();
|
|
471
|
+
const picked = await select.ask("Which source do you want to remove?", config.sources.map((s) => ({ label: s, value: s })));
|
|
472
|
+
if (!picked) return;
|
|
473
|
+
dir = picked.value;
|
|
474
|
+
}
|
|
475
|
+
const resolved = (0, import_path4.resolve)(dir);
|
|
445
476
|
const index = config.sources.indexOf(resolved);
|
|
446
477
|
if (index === -1) {
|
|
447
478
|
spinner_default.start();
|
|
@@ -468,10 +499,23 @@ var destAdd = async ({ type, dir }) => {
|
|
|
468
499
|
spinner_default.stop();
|
|
469
500
|
};
|
|
470
501
|
var destRemove = async ({ type }) => {
|
|
502
|
+
const config = getConfig();
|
|
503
|
+
if (!type) {
|
|
504
|
+
const configured = DEST_TYPES.filter((t) => config.dest[t]);
|
|
505
|
+
if (configured.length === 0) {
|
|
506
|
+
spinner_default.start();
|
|
507
|
+
spinner_default.warn("no destinations configured");
|
|
508
|
+
spinner_default.stop();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const select = new import_termkit4.Select();
|
|
512
|
+
const picked = await select.ask("Which destination do you want to remove?", configured.map((t) => ({ label: `${t.padEnd(6)} ${config.dest[t]}`, value: t })));
|
|
513
|
+
if (!picked) return;
|
|
514
|
+
type = picked.value;
|
|
515
|
+
}
|
|
471
516
|
if (!DEST_TYPES.includes(type)) {
|
|
472
517
|
throw new Error(`unknown type '${type}', expected: ${DEST_TYPES.join(", ")}`);
|
|
473
518
|
}
|
|
474
|
-
const config = getConfig();
|
|
475
519
|
if (!config.dest[type]) {
|
|
476
520
|
spinner_default.start();
|
|
477
521
|
spinner_default.warn(`no ${type} destination configured`);
|
|
@@ -484,6 +528,77 @@ var destRemove = async ({ type }) => {
|
|
|
484
528
|
spinner_default.succeed(`removed ${type} destination`);
|
|
485
529
|
spinner_default.stop();
|
|
486
530
|
};
|
|
531
|
+
var ignore = async ({ name }) => {
|
|
532
|
+
const config = getConfig();
|
|
533
|
+
config.ignore = config.ignore ?? [];
|
|
534
|
+
let names = name ?? [];
|
|
535
|
+
if (names.length === 0) {
|
|
536
|
+
const entries = getSourceEntries(config.sources);
|
|
537
|
+
if (entries.length === 0) {
|
|
538
|
+
spinner_default.start();
|
|
539
|
+
spinner_default.warn("no files found in configured sources");
|
|
540
|
+
spinner_default.stop();
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
const ms = new import_termkit4.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
|
|
544
|
+
const items = entries.map((e) => ({ label: e }));
|
|
545
|
+
const picked = await ms.ask("Select files to ignore during scan:", items);
|
|
546
|
+
if (!picked || picked.length === 0) return;
|
|
547
|
+
names = picked.map((p) => p.label);
|
|
548
|
+
}
|
|
549
|
+
const added = [];
|
|
550
|
+
for (const n of names) {
|
|
551
|
+
if (!config.ignore.includes(n)) {
|
|
552
|
+
config.ignore.push(n);
|
|
553
|
+
added.push(n);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (added.length === 0) {
|
|
557
|
+
spinner_default.start();
|
|
558
|
+
spinner_default.info("all selected files are already ignored");
|
|
559
|
+
spinner_default.stop();
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
saveConfig(config);
|
|
563
|
+
spinner_default.start();
|
|
564
|
+
for (const n of added) spinner_default.succeed(`ignoring: ${import_termkit4.Color.white.encoder(n)}`);
|
|
565
|
+
spinner_default.stop();
|
|
566
|
+
};
|
|
567
|
+
var ignoreRemove = async ({ name }) => {
|
|
568
|
+
const config = getConfig();
|
|
569
|
+
const list2 = config.ignore ?? [];
|
|
570
|
+
if (!name) {
|
|
571
|
+
if (list2.length === 0) {
|
|
572
|
+
spinner_default.start();
|
|
573
|
+
spinner_default.warn("ignore list is empty");
|
|
574
|
+
spinner_default.stop();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
const ms = new import_termkit4.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
|
|
578
|
+
const items = list2.map((n) => ({ label: n }));
|
|
579
|
+
const picked = await ms.ask("Select files to remove from ignore list:", items);
|
|
580
|
+
if (!picked || picked.length === 0) return;
|
|
581
|
+
const toRemove = new Set(picked.map((p) => p.label));
|
|
582
|
+
config.ignore = list2.filter((n) => !toRemove.has(n));
|
|
583
|
+
saveConfig(config);
|
|
584
|
+
spinner_default.start();
|
|
585
|
+
for (const n of [...toRemove]) spinner_default.succeed(`removed from ignore list: ${import_termkit4.Color.white.encoder(n)}`);
|
|
586
|
+
spinner_default.stop();
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
const index = list2.indexOf(name);
|
|
590
|
+
if (index === -1) {
|
|
591
|
+
spinner_default.start();
|
|
592
|
+
spinner_default.warn(`not in ignore list: ${import_termkit4.Color.white.encoder(name)}`);
|
|
593
|
+
spinner_default.stop();
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
config.ignore.splice(index, 1);
|
|
597
|
+
saveConfig(config);
|
|
598
|
+
spinner_default.start();
|
|
599
|
+
spinner_default.succeed(`removed from ignore list: ${import_termkit4.Color.white.encoder(name)}`);
|
|
600
|
+
spinner_default.stop();
|
|
601
|
+
};
|
|
487
602
|
var configSet = async ({ key, subkey, value }) => {
|
|
488
603
|
const config = getConfig();
|
|
489
604
|
if (key === "language") {
|
|
@@ -549,24 +664,30 @@ Subtitle language: ${import_termkit4.Color.green.encoder(config.language ?? "eng
|
|
|
549
664
|
console.log(`Movie format: ${import_termkit4.Color.green.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
|
|
550
665
|
console.log(`Episode format: ${import_termkit4.Color.green.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
|
|
551
666
|
console.log(`Season folder: ${import_termkit4.Color.green.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
|
|
667
|
+
console.log("\nIgnored files:");
|
|
668
|
+
if (!config.ignore || config.ignore.length === 0) {
|
|
669
|
+
console.log(" (none)");
|
|
670
|
+
} else {
|
|
671
|
+
for (const name of config.ignore) console.log(` ${import_termkit4.Color.white.encoder(name)}`);
|
|
672
|
+
}
|
|
552
673
|
console.log();
|
|
553
674
|
};
|
|
554
675
|
|
|
555
676
|
// src/actions/differences.ts
|
|
556
|
-
var
|
|
677
|
+
var import_fs6 = require("fs");
|
|
557
678
|
var import_path5 = require("path");
|
|
558
679
|
var import_termkit5 = require("termkit");
|
|
559
|
-
var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore }) => {
|
|
680
|
+
var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore: ignore2 }) => {
|
|
560
681
|
let dir1 = rawDir1;
|
|
561
682
|
let dir2 = rawDir2;
|
|
562
683
|
spinner_default.text = `checking differences between ${import_termkit5.Color.white.encoder(dir1)} and ${import_termkit5.Color.white.encoder(dir2)}`;
|
|
563
684
|
spinner_default.start();
|
|
564
685
|
dir1 = (0, import_path5.resolve)(dir1);
|
|
565
686
|
dir2 = (0, import_path5.resolve)(dir2);
|
|
566
|
-
if (!(0,
|
|
567
|
-
if (!(0,
|
|
568
|
-
let list1 = (0,
|
|
569
|
-
let list2 = (0,
|
|
687
|
+
if (!(0, import_fs6.existsSync)(dir1)) throw new Error(`dir1 ${dir1} does not exist`);
|
|
688
|
+
if (!(0, import_fs6.existsSync)(dir2)) throw new Error(`dir2 ${dir2} does not exist`);
|
|
689
|
+
let list1 = (0, import_fs6.readdirSync)(dir1);
|
|
690
|
+
let list2 = (0, import_fs6.readdirSync)(dir2);
|
|
570
691
|
if (only && only.length) {
|
|
571
692
|
list1 = list1.filter((i) => {
|
|
572
693
|
for (const o of only) if (i.endsWith(o)) return true;
|
|
@@ -577,13 +698,13 @@ var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore }) => {
|
|
|
577
698
|
return false;
|
|
578
699
|
});
|
|
579
700
|
}
|
|
580
|
-
if (
|
|
701
|
+
if (ignore2 && ignore2.length) {
|
|
581
702
|
list1 = list1.filter((i) => {
|
|
582
|
-
for (const o of
|
|
703
|
+
for (const o of ignore2) if (i.endsWith(o)) return false;
|
|
583
704
|
return true;
|
|
584
705
|
});
|
|
585
706
|
list2 = list2.filter((i) => {
|
|
586
|
-
for (const o of
|
|
707
|
+
for (const o of ignore2) if (i.endsWith(o)) return false;
|
|
587
708
|
return true;
|
|
588
709
|
});
|
|
589
710
|
}
|
|
@@ -680,16 +801,16 @@ ${import_termkit7.Color.yellow.encoder(label)} (${folders.length} item${folders
|
|
|
680
801
|
var history_default = history;
|
|
681
802
|
|
|
682
803
|
// src/actions/link.ts
|
|
683
|
-
var
|
|
804
|
+
var import_fs7 = require("fs");
|
|
684
805
|
var import_path7 = require("path");
|
|
685
806
|
var import_termkit8 = require("termkit");
|
|
686
807
|
var parseShowTitle = (folderName) => {
|
|
687
808
|
const withoutYear = folderName.replace(/\s*\(\d{4}\)\s*$/, "").trim();
|
|
688
809
|
return withoutYear || folderName;
|
|
689
810
|
};
|
|
690
|
-
var subdirs = (dir) => (0,
|
|
811
|
+
var subdirs = (dir) => (0, import_fs7.readdirSync)(dir).filter((f) => {
|
|
691
812
|
try {
|
|
692
|
-
return (0,
|
|
813
|
+
return (0, import_fs7.lstatSync)((0, import_path7.resolve)(dir, f)).isDirectory();
|
|
693
814
|
} catch {
|
|
694
815
|
return false;
|
|
695
816
|
}
|
|
@@ -699,7 +820,7 @@ var link = async ({ force }) => {
|
|
|
699
820
|
if (!config.tmdbApiKey) throw new Error("TMDb API key required \u2014 run: reelsort config set tmdb-key <key>");
|
|
700
821
|
const destRoot = config.dest.tv;
|
|
701
822
|
if (!destRoot) throw new Error("no TV destination configured \u2014 run: reelsort config set dest tv <dir>");
|
|
702
|
-
if (!(0,
|
|
823
|
+
if (!(0, import_fs7.existsSync)(destRoot)) throw new Error(`TV destination not found: ${destRoot}`);
|
|
703
824
|
const shows2 = subdirs(destRoot);
|
|
704
825
|
let linked = 0, skipped = 0, notFound = 0;
|
|
705
826
|
for (const show of shows2) {
|
|
@@ -749,23 +870,23 @@ var link = async ({ force }) => {
|
|
|
749
870
|
var link_default = link;
|
|
750
871
|
|
|
751
872
|
// src/actions/list.ts
|
|
752
|
-
var
|
|
873
|
+
var import_fs9 = require("fs");
|
|
753
874
|
var import_path9 = require("path");
|
|
754
875
|
var import_termkit9 = require("termkit");
|
|
755
876
|
|
|
756
877
|
// src/helpers/dirSize.ts
|
|
757
|
-
var
|
|
878
|
+
var import_fs8 = require("fs");
|
|
758
879
|
var import_path8 = require("path");
|
|
759
880
|
var dirSize = (dir) => {
|
|
760
881
|
let total = 0;
|
|
761
882
|
try {
|
|
762
|
-
for (const entry of (0,
|
|
883
|
+
for (const entry of (0, import_fs8.readdirSync)(dir, { withFileTypes: true })) {
|
|
763
884
|
const full = (0, import_path8.resolve)(dir, entry.name);
|
|
764
885
|
if (entry.isDirectory()) {
|
|
765
886
|
total += dirSize(full);
|
|
766
887
|
} else if (entry.isFile()) {
|
|
767
888
|
try {
|
|
768
|
-
total += (0,
|
|
889
|
+
total += (0, import_fs8.statSync)(full).size;
|
|
769
890
|
} catch {
|
|
770
891
|
}
|
|
771
892
|
}
|
|
@@ -860,7 +981,7 @@ var videoExtensions_default = [
|
|
|
860
981
|
var DEST_TYPES2 = ["movie", "tv", "ps3"];
|
|
861
982
|
var findVideoFile = (dir) => {
|
|
862
983
|
try {
|
|
863
|
-
return (0,
|
|
984
|
+
return (0, import_fs9.readdirSync)(dir).find((f) => {
|
|
864
985
|
const ext = f.match(/([^.]+$)/)?.[0]?.toLowerCase();
|
|
865
986
|
return ext && videoExtensions_default.includes(ext);
|
|
866
987
|
}) ?? null;
|
|
@@ -870,7 +991,7 @@ var findVideoFile = (dir) => {
|
|
|
870
991
|
};
|
|
871
992
|
var hasSubtitle = (dir) => {
|
|
872
993
|
try {
|
|
873
|
-
return (0,
|
|
994
|
+
return (0, import_fs9.readdirSync)(dir).some((f) => {
|
|
874
995
|
const ext = f.match(/([^.]+$)/)?.[0]?.toLowerCase();
|
|
875
996
|
return ext && subtitleExtensions_default.includes(ext);
|
|
876
997
|
});
|
|
@@ -889,14 +1010,14 @@ var list = async ({ type, missingSubs, codec: codecFilter, resolution: resFilter
|
|
|
889
1010
|
if (types.length === 0) throw new Error("no destinations configured \u2014 run: reelsort config set dest movie <dir>");
|
|
890
1011
|
for (const t of types) {
|
|
891
1012
|
const destRoot = config.dest[t];
|
|
892
|
-
if (!(0,
|
|
1013
|
+
if (!(0, import_fs9.existsSync)(destRoot)) {
|
|
893
1014
|
console.log(`
|
|
894
1015
|
${t.toUpperCase()} ${import_termkit9.Color.white.encoder(destRoot)} (not found)`);
|
|
895
1016
|
continue;
|
|
896
1017
|
}
|
|
897
|
-
const folders = (0,
|
|
1018
|
+
const folders = (0, import_fs9.readdirSync)(destRoot).filter((f) => {
|
|
898
1019
|
try {
|
|
899
|
-
return (0,
|
|
1020
|
+
return (0, import_fs9.lstatSync)((0, import_path9.resolve)(destRoot, f)).isDirectory();
|
|
900
1021
|
} catch {
|
|
901
1022
|
return false;
|
|
902
1023
|
}
|
|
@@ -951,7 +1072,7 @@ ${import_termkit9.Color.yellow.encoder(t.toUpperCase())} ${import_termkit9.Colo
|
|
|
951
1072
|
var list_default = list;
|
|
952
1073
|
|
|
953
1074
|
// src/actions/missing.ts
|
|
954
|
-
var
|
|
1075
|
+
var import_fs10 = require("fs");
|
|
955
1076
|
var import_path10 = require("path");
|
|
956
1077
|
var import_termkit10 = require("termkit");
|
|
957
1078
|
var TODAY = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -961,7 +1082,7 @@ var parseSeasonNumber = (folderName) => {
|
|
|
961
1082
|
};
|
|
962
1083
|
var parseEpisodeNumbers = (dir) => {
|
|
963
1084
|
const episodes = /* @__PURE__ */ new Set();
|
|
964
|
-
for (const file of (0,
|
|
1085
|
+
for (const file of (0, import_fs10.readdirSync)(dir)) {
|
|
965
1086
|
const ext = file.match(/([^.]+$)/)?.[0]?.toLowerCase();
|
|
966
1087
|
if (!ext || !videoExtensions_default.includes(ext)) continue;
|
|
967
1088
|
const m = file.match(/(?:S\d+E|(?:\d+)x)(\d+)/i);
|
|
@@ -969,9 +1090,9 @@ var parseEpisodeNumbers = (dir) => {
|
|
|
969
1090
|
}
|
|
970
1091
|
return episodes;
|
|
971
1092
|
};
|
|
972
|
-
var subdirs2 = (dir) => (0,
|
|
1093
|
+
var subdirs2 = (dir) => (0, import_fs10.readdirSync)(dir).filter((f) => {
|
|
973
1094
|
try {
|
|
974
|
-
return (0,
|
|
1095
|
+
return (0, import_fs10.lstatSync)((0, import_path10.resolve)(dir, f)).isDirectory();
|
|
975
1096
|
} catch {
|
|
976
1097
|
return false;
|
|
977
1098
|
}
|
|
@@ -981,7 +1102,7 @@ var missing = async ({ show: showFilter }) => {
|
|
|
981
1102
|
if (!config.tmdbApiKey) throw new Error("TMDb API key required \u2014 run: reelsort config set tmdb-key <key>");
|
|
982
1103
|
const destRoot = config.dest.tv;
|
|
983
1104
|
if (!destRoot) throw new Error("no TV destination configured \u2014 run: reelsort config set dest tv <dir>");
|
|
984
|
-
if (!(0,
|
|
1105
|
+
if (!(0, import_fs10.existsSync)(destRoot)) throw new Error(`TV destination not found: ${destRoot}`);
|
|
985
1106
|
const filter = showFilter?.toLowerCase();
|
|
986
1107
|
const allShows = getShows();
|
|
987
1108
|
const endedPaths = new Set(allShows.filter((s) => s.ended).map((s) => s.path));
|
|
@@ -1034,9 +1155,18 @@ var missing_default = missing;
|
|
|
1034
1155
|
|
|
1035
1156
|
// src/actions/probe.ts
|
|
1036
1157
|
var import_child_process = require("child_process");
|
|
1037
|
-
var
|
|
1158
|
+
var import_fs11 = require("fs");
|
|
1038
1159
|
var import_path11 = require("path");
|
|
1039
1160
|
var import_termkit11 = require("termkit");
|
|
1161
|
+
|
|
1162
|
+
// src/refs/verbose.ts
|
|
1163
|
+
var _verbose = false;
|
|
1164
|
+
var setVerbose = (v) => {
|
|
1165
|
+
_verbose = v;
|
|
1166
|
+
};
|
|
1167
|
+
var isVerbose = () => _verbose;
|
|
1168
|
+
|
|
1169
|
+
// src/actions/probe.ts
|
|
1040
1170
|
var DEST_TYPES3 = ["movie", "tv", "ps3"];
|
|
1041
1171
|
var CODEC_MAP2 = {
|
|
1042
1172
|
hevc: "x265",
|
|
@@ -1082,12 +1212,12 @@ var runFfprobe = (filePath) => {
|
|
|
1082
1212
|
}
|
|
1083
1213
|
};
|
|
1084
1214
|
var walkVideoFiles = (dir, depth = 0, maxDepth = 3) => {
|
|
1085
|
-
if (!(0,
|
|
1215
|
+
if (!(0, import_fs11.existsSync)(dir) || depth > maxDepth) return [];
|
|
1086
1216
|
const results = [];
|
|
1087
|
-
for (const entry of (0,
|
|
1217
|
+
for (const entry of (0, import_fs11.readdirSync)(dir)) {
|
|
1088
1218
|
const entryPath = (0, import_path11.resolve)(dir, entry);
|
|
1089
1219
|
try {
|
|
1090
|
-
if ((0,
|
|
1220
|
+
if ((0, import_fs11.lstatSync)(entryPath).isDirectory()) {
|
|
1091
1221
|
results.push(...walkVideoFiles(entryPath, depth + 1, maxDepth));
|
|
1092
1222
|
} else {
|
|
1093
1223
|
const ext = entry.match(/([^.]+$)/)?.[0]?.toLowerCase();
|
|
@@ -1098,7 +1228,7 @@ var walkVideoFiles = (dir, depth = 0, maxDepth = 3) => {
|
|
|
1098
1228
|
}
|
|
1099
1229
|
return results;
|
|
1100
1230
|
};
|
|
1101
|
-
var probe = async ({ type, force
|
|
1231
|
+
var probe = async ({ type, force }) => {
|
|
1102
1232
|
spinner_default.start();
|
|
1103
1233
|
if (!isFfprobeAvailable()) {
|
|
1104
1234
|
spinner_default.fail("ffprobe not found \u2014 install ffmpeg to use this command");
|
|
@@ -1111,24 +1241,24 @@ var probe = async ({ type, force, verbose }) => {
|
|
|
1111
1241
|
let probed = 0, skipped = 0, failed = 0;
|
|
1112
1242
|
for (const t of types) {
|
|
1113
1243
|
const destRoot = config.dest[t];
|
|
1114
|
-
if (!(0,
|
|
1244
|
+
if (!(0, import_fs11.existsSync)(destRoot)) continue;
|
|
1115
1245
|
spinner_default.text = `scanning ${import_termkit11.Color.white.encoder(destRoot)}`;
|
|
1116
1246
|
const files = walkVideoFiles(destRoot);
|
|
1117
1247
|
for (const filePath of files) {
|
|
1118
1248
|
if (!force && getMediaInfo(filePath)) {
|
|
1119
|
-
if (
|
|
1249
|
+
if (isVerbose()) spinner_default.info(`already probed: ${filePath}`);
|
|
1120
1250
|
skipped++;
|
|
1121
1251
|
continue;
|
|
1122
1252
|
}
|
|
1123
1253
|
spinner_default.text = `probing ${import_termkit11.Color.white.encoder(filePath)}`;
|
|
1124
1254
|
const result = runFfprobe(filePath);
|
|
1125
1255
|
if (!result) {
|
|
1126
|
-
if (
|
|
1256
|
+
if (isVerbose()) spinner_default.warn(`ffprobe failed: ${filePath}`);
|
|
1127
1257
|
failed++;
|
|
1128
1258
|
continue;
|
|
1129
1259
|
}
|
|
1130
1260
|
upsertMediaInfo(filePath, result.codec, result.resolution, result.width, result.height, result.duration);
|
|
1131
|
-
if (
|
|
1261
|
+
if (isVerbose()) spinner_default.succeed(`${result.resolution ?? "?"} ${result.codec ?? "?"} ${filePath}`);
|
|
1132
1262
|
probed++;
|
|
1133
1263
|
}
|
|
1134
1264
|
}
|
|
@@ -1140,7 +1270,7 @@ var probe = async ({ type, force, verbose }) => {
|
|
|
1140
1270
|
var probe_default = probe;
|
|
1141
1271
|
|
|
1142
1272
|
// src/actions/rename.ts
|
|
1143
|
-
var
|
|
1273
|
+
var import_fs12 = require("fs");
|
|
1144
1274
|
var import_path12 = require("path");
|
|
1145
1275
|
var import_rimraf = require("rimraf");
|
|
1146
1276
|
var import_termkit12 = require("termkit");
|
|
@@ -1198,7 +1328,7 @@ var titleCase_default = (s) => {
|
|
|
1198
1328
|
};
|
|
1199
1329
|
|
|
1200
1330
|
// src/actions/rename.ts
|
|
1201
|
-
var rename = async ({ dir: inputDir, type
|
|
1331
|
+
var rename = async ({ dir: inputDir, type }) => {
|
|
1202
1332
|
const dir = (0, import_path12.resolve)(inputDir);
|
|
1203
1333
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1204
1334
|
const config = getConfig();
|
|
@@ -1206,13 +1336,13 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
1206
1336
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
1207
1337
|
spinner_default.text = `renaming in ${import_termkit12.Color.white.encoder(dir)}`;
|
|
1208
1338
|
spinner_default.start();
|
|
1209
|
-
if (!(0,
|
|
1210
|
-
const list2 = (0,
|
|
1339
|
+
if (!(0, import_fs12.existsSync)(dir)) throw new Error(`dir ${dir} does not exist`);
|
|
1340
|
+
const list2 = (0, import_fs12.readdirSync)(dir);
|
|
1211
1341
|
let renamed = 0, removed = 0, skipped = 0;
|
|
1212
1342
|
for (const [index, entry] of list2.entries()) {
|
|
1213
1343
|
spinner_default.text = `renaming in ${import_termkit12.Color.white.encoder(dir)} ${index + 1}/${list2.length}`;
|
|
1214
|
-
if (!(0,
|
|
1215
|
-
if (
|
|
1344
|
+
if (!(0, import_fs12.lstatSync)((0, import_path12.resolve)(dir, entry)).isDirectory()) {
|
|
1345
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1216
1346
|
skipped++;
|
|
1217
1347
|
continue;
|
|
1218
1348
|
}
|
|
@@ -1222,13 +1352,13 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
1222
1352
|
const nameMatch = entry.match(/(?<=\[).+?(?=\])/);
|
|
1223
1353
|
const id = entry.split("-")[0];
|
|
1224
1354
|
if (!nameMatch || !id) {
|
|
1225
|
-
if (
|
|
1355
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1226
1356
|
skipped++;
|
|
1227
1357
|
continue;
|
|
1228
1358
|
}
|
|
1229
1359
|
const ps3Old = (0, import_path12.resolve)(dir, entry);
|
|
1230
1360
|
const ps3New = (0, import_path12.resolve)(dir, `${nameMatch[0]} [${id}]`);
|
|
1231
|
-
(0,
|
|
1361
|
+
(0, import_fs12.renameSync)(ps3Old, ps3New);
|
|
1232
1362
|
recordRename(sessionId, ps3Old, ps3New);
|
|
1233
1363
|
spinner_default.succeed(`${nameMatch[0]} [${id}]`);
|
|
1234
1364
|
renamed++;
|
|
@@ -1236,37 +1366,37 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
1236
1366
|
}
|
|
1237
1367
|
const yearMatch = entry.match(/\([^\d]*(\d+)[^\d]*\)/);
|
|
1238
1368
|
if (!yearMatch) {
|
|
1239
|
-
if (
|
|
1369
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1240
1370
|
skipped++;
|
|
1241
1371
|
continue;
|
|
1242
1372
|
}
|
|
1243
1373
|
const year = yearMatch[0];
|
|
1244
1374
|
if (year.length !== 6) {
|
|
1245
|
-
if (
|
|
1375
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1246
1376
|
skipped++;
|
|
1247
1377
|
continue;
|
|
1248
1378
|
}
|
|
1249
1379
|
const title = titleCase_default(entry.substring(0, entry.indexOf(year)).trim());
|
|
1250
|
-
const sublist = (0,
|
|
1380
|
+
const sublist = (0, import_fs12.readdirSync)((0, import_path12.resolve)(dir, entry));
|
|
1251
1381
|
const video = sublist.find((f) => {
|
|
1252
1382
|
const ext2 = f.match(/([^.]+$)/)?.[0];
|
|
1253
1383
|
return videoExtensions_default.includes(ext2) && title.split(" ").reduce((a, w) => f.toLowerCase().includes(w.toLowerCase()) ? a : false, true);
|
|
1254
1384
|
});
|
|
1255
1385
|
if (!video) {
|
|
1256
|
-
if (
|
|
1386
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1257
1387
|
skipped++;
|
|
1258
1388
|
continue;
|
|
1259
1389
|
}
|
|
1260
1390
|
const ext = video.match(/([^.]+$)/)?.[0];
|
|
1261
1391
|
if (!ext) {
|
|
1262
|
-
if (
|
|
1392
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1263
1393
|
skipped++;
|
|
1264
1394
|
continue;
|
|
1265
1395
|
}
|
|
1266
1396
|
const yearNum = parseInt(year.replace(/\D/g, ""));
|
|
1267
1397
|
const formatted = formatMovieName(movieFormat, title, yearNum);
|
|
1268
1398
|
if (entry === formatted && video === `${formatted}.${ext}`) {
|
|
1269
|
-
if (
|
|
1399
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1270
1400
|
skipped++;
|
|
1271
1401
|
continue;
|
|
1272
1402
|
}
|
|
@@ -1282,11 +1412,11 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
1282
1412
|
const fileNew = (0, import_path12.resolve)(dir, entry, `${formatted}.${ext}`);
|
|
1283
1413
|
const folderOld = (0, import_path12.resolve)(dir, entry);
|
|
1284
1414
|
const folderNew = (0, import_path12.resolve)(dir, formatted);
|
|
1285
|
-
(0,
|
|
1415
|
+
(0, import_fs12.renameSync)(fileOld, fileNew);
|
|
1286
1416
|
if (subtitle && subtitleExt) {
|
|
1287
|
-
(0,
|
|
1417
|
+
(0, import_fs12.renameSync)((0, import_path12.resolve)(dir, entry, subtitle), (0, import_path12.resolve)(dir, entry, `${formatted}.${subtitleExt}`));
|
|
1288
1418
|
}
|
|
1289
|
-
(0,
|
|
1419
|
+
(0, import_fs12.renameSync)(folderOld, folderNew);
|
|
1290
1420
|
recordRename(sessionId, fileOld, fileNew);
|
|
1291
1421
|
recordRename(sessionId, folderOld, folderNew);
|
|
1292
1422
|
spinner_default.succeed(formatted);
|
|
@@ -1301,7 +1431,7 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
1301
1431
|
var rename_default = rename;
|
|
1302
1432
|
|
|
1303
1433
|
// src/actions/reset.ts
|
|
1304
|
-
var
|
|
1434
|
+
var import_fs13 = require("fs");
|
|
1305
1435
|
var import_path13 = require("path");
|
|
1306
1436
|
var import_termkit13 = require("termkit");
|
|
1307
1437
|
var reset = async ({ dir: inputDir, double }) => {
|
|
@@ -1309,8 +1439,8 @@ var reset = async ({ dir: inputDir, double }) => {
|
|
|
1309
1439
|
spinner_default.text = `resetting episodes in ${import_termkit13.Color.white.encoder(dir)}`;
|
|
1310
1440
|
spinner_default.start();
|
|
1311
1441
|
dir = (0, import_path13.resolve)(dir);
|
|
1312
|
-
if (!(0,
|
|
1313
|
-
const list2 = (0,
|
|
1442
|
+
if (!(0, import_fs13.existsSync)(dir)) throw new Error(`dir ${dir} does not exist`);
|
|
1443
|
+
const list2 = (0, import_fs13.readdirSync)(dir).sort();
|
|
1314
1444
|
const folder = dir.replace(/\./g, " ").split(import_path13.sep).pop();
|
|
1315
1445
|
let season;
|
|
1316
1446
|
let sub = folder.includes("season") ? folder.substring(folder.indexOf("season") + "season".length, folder.length).trim() : /s\d/i.test(folder) ? folder.substring(folder.search(/s\d/i) + 1, folder.length).trim() : null;
|
|
@@ -1343,7 +1473,7 @@ var reset = async ({ dir: inputDir, double }) => {
|
|
|
1343
1473
|
skipped++;
|
|
1344
1474
|
continue;
|
|
1345
1475
|
}
|
|
1346
|
-
(0,
|
|
1476
|
+
(0, import_fs13.renameSync)((0, import_path13.resolve)(dir, i), (0, import_path13.resolve)(dir, name));
|
|
1347
1477
|
renamed++;
|
|
1348
1478
|
}
|
|
1349
1479
|
spinner_default.succeed(`renamed ${renamed} files`);
|
|
@@ -1354,7 +1484,7 @@ var reset = async ({ dir: inputDir, double }) => {
|
|
|
1354
1484
|
var reset_default = reset;
|
|
1355
1485
|
|
|
1356
1486
|
// src/actions/scan.ts
|
|
1357
|
-
var
|
|
1487
|
+
var import_fs14 = require("fs");
|
|
1358
1488
|
var import_path14 = require("path");
|
|
1359
1489
|
var import_termkit14 = require("termkit");
|
|
1360
1490
|
|
|
@@ -1423,33 +1553,134 @@ var bookExtensions_default = ["epub", "mobi", "azw3", "azw"];
|
|
|
1423
1553
|
var sameDev = (a, b) => {
|
|
1424
1554
|
try {
|
|
1425
1555
|
let bExisting = b;
|
|
1426
|
-
while (!(0,
|
|
1427
|
-
return (0,
|
|
1556
|
+
while (!(0, import_fs14.existsSync)(bExisting)) bExisting = (0, import_path14.dirname)(bExisting);
|
|
1557
|
+
return (0, import_fs14.statSync)(a).dev === (0, import_fs14.statSync)(bExisting).dev;
|
|
1428
1558
|
} catch {
|
|
1429
1559
|
return false;
|
|
1430
1560
|
}
|
|
1431
1561
|
};
|
|
1432
1562
|
var moveFolder = (src, dest) => {
|
|
1433
1563
|
if (sameDev(src, dest)) {
|
|
1434
|
-
(0,
|
|
1564
|
+
(0, import_fs14.renameSync)(src, dest);
|
|
1435
1565
|
} else {
|
|
1436
|
-
(0,
|
|
1437
|
-
(0,
|
|
1566
|
+
(0, import_fs14.cpSync)(src, dest, { recursive: true });
|
|
1567
|
+
(0, import_fs14.rmSync)(src, { recursive: true, force: true });
|
|
1438
1568
|
}
|
|
1439
1569
|
};
|
|
1440
|
-
var findVideo = (dir) => (0,
|
|
1570
|
+
var findVideo = (dir) => (0, import_fs14.readdirSync)(dir).find((f) => {
|
|
1441
1571
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1442
1572
|
return ext && videoExtensions_default.includes(ext);
|
|
1443
1573
|
}) ?? null;
|
|
1444
|
-
var containsBook = (dir) => (0,
|
|
1574
|
+
var containsBook = (dir, depth = 2) => (0, import_fs14.readdirSync)(dir).some((f) => {
|
|
1445
1575
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1446
|
-
|
|
1576
|
+
if (ext && bookExtensions_default.includes(ext)) return true;
|
|
1577
|
+
if (depth > 1) {
|
|
1578
|
+
try {
|
|
1579
|
+
const sub = (0, import_path14.resolve)(dir, f);
|
|
1580
|
+
if ((0, import_fs14.lstatSync)(sub).isDirectory()) return containsBook(sub, depth - 1);
|
|
1581
|
+
} catch {
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
return false;
|
|
1447
1585
|
});
|
|
1586
|
+
var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
|
|
1587
|
+
var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
|
|
1588
|
+
var gatherEntries = (source) => {
|
|
1589
|
+
const result = [];
|
|
1590
|
+
for (const name of (0, import_fs14.readdirSync)(source)) {
|
|
1591
|
+
const fullPath = (0, import_path14.resolve)(source, name);
|
|
1592
|
+
let isDir;
|
|
1593
|
+
try {
|
|
1594
|
+
isDir = (0, import_fs14.lstatSync)(fullPath).isDirectory();
|
|
1595
|
+
} catch {
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
const ext = name.match(/([^.]+$)/)?.[0];
|
|
1599
|
+
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
1600
|
+
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1601
|
+
if (!isDir && !isVideo && !isBook) continue;
|
|
1602
|
+
if (!isDir) {
|
|
1603
|
+
result.push({ entry: name, entryPath: fullPath, isDir: false });
|
|
1604
|
+
continue;
|
|
1605
|
+
}
|
|
1606
|
+
if (/(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name) || isTvEpisodeName(name)) {
|
|
1607
|
+
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
let children;
|
|
1611
|
+
try {
|
|
1612
|
+
children = (0, import_fs14.readdirSync)(fullPath);
|
|
1613
|
+
} catch {
|
|
1614
|
+
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
if (children.some((c) => isTvEpisodeName(c))) {
|
|
1618
|
+
for (const child of children) {
|
|
1619
|
+
const childPath = (0, import_path14.resolve)(fullPath, child);
|
|
1620
|
+
let childIsDir;
|
|
1621
|
+
try {
|
|
1622
|
+
childIsDir = (0, import_fs14.lstatSync)(childPath).isDirectory();
|
|
1623
|
+
} catch {
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
const childExt = child.match(/([^.]+$)/)?.[0];
|
|
1627
|
+
if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
|
|
1628
|
+
result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
|
|
1629
|
+
}
|
|
1630
|
+
continue;
|
|
1631
|
+
}
|
|
1632
|
+
const seasonDirs = children.filter((c) => {
|
|
1633
|
+
try {
|
|
1634
|
+
return isSeasonDirName(c) && (0, import_fs14.lstatSync)((0, import_path14.resolve)(fullPath, c)).isDirectory();
|
|
1635
|
+
} catch {
|
|
1636
|
+
return false;
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1639
|
+
if (seasonDirs.length > 0) {
|
|
1640
|
+
for (const seasonDir of seasonDirs) {
|
|
1641
|
+
const seasonPath = (0, import_path14.resolve)(fullPath, seasonDir);
|
|
1642
|
+
let seasonChildren;
|
|
1643
|
+
try {
|
|
1644
|
+
seasonChildren = (0, import_fs14.readdirSync)(seasonPath);
|
|
1645
|
+
} catch {
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
for (const child of seasonChildren) {
|
|
1649
|
+
const childPath = (0, import_path14.resolve)(seasonPath, child);
|
|
1650
|
+
let childIsDir;
|
|
1651
|
+
try {
|
|
1652
|
+
childIsDir = (0, import_fs14.lstatSync)(childPath).isDirectory();
|
|
1653
|
+
} catch {
|
|
1654
|
+
continue;
|
|
1655
|
+
}
|
|
1656
|
+
const childExt = child.match(/([^.]+$)/)?.[0];
|
|
1657
|
+
if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
|
|
1658
|
+
result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
continue;
|
|
1662
|
+
}
|
|
1663
|
+
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1664
|
+
}
|
|
1665
|
+
return result;
|
|
1666
|
+
};
|
|
1667
|
+
var findShowFolder = (destRoot, title) => {
|
|
1668
|
+
if (!(0, import_fs14.existsSync)(destRoot)) return null;
|
|
1669
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1670
|
+
const target = normalize(title);
|
|
1671
|
+
return (0, import_fs14.readdirSync)(destRoot).filter((f) => {
|
|
1672
|
+
try {
|
|
1673
|
+
return (0, import_fs14.lstatSync)((0, import_path14.resolve)(destRoot, f)).isDirectory();
|
|
1674
|
+
} catch {
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
}).find((f) => normalize(f) === target) ?? null;
|
|
1678
|
+
};
|
|
1448
1679
|
var findSeasonFolder = (showPath, season) => {
|
|
1449
|
-
if (!(0,
|
|
1450
|
-
const folders = (0,
|
|
1680
|
+
if (!(0, import_fs14.existsSync)(showPath)) return null;
|
|
1681
|
+
const folders = (0, import_fs14.readdirSync)(showPath).filter((f) => {
|
|
1451
1682
|
try {
|
|
1452
|
-
return (0,
|
|
1683
|
+
return (0, import_fs14.lstatSync)((0, import_path14.resolve)(showPath, f)).isDirectory();
|
|
1453
1684
|
} catch {
|
|
1454
1685
|
return false;
|
|
1455
1686
|
}
|
|
@@ -1468,7 +1699,14 @@ var classifyMovieConfidence = (entry) => {
|
|
|
1468
1699
|
if (/\.\d{4}\..*?(?:2160p|1080p|720p|BluRay|WEBRip|WEB-DL|HDTV)/i.test(entry)) return "auto";
|
|
1469
1700
|
return "ambiguous";
|
|
1470
1701
|
};
|
|
1471
|
-
var
|
|
1702
|
+
var typeColor = {
|
|
1703
|
+
movie: import_termkit14.Color.white.cyan,
|
|
1704
|
+
tv: import_termkit14.Color.white.green,
|
|
1705
|
+
book: import_termkit14.Color.white.yellow,
|
|
1706
|
+
ps3: import_termkit14.Color.white.magenta
|
|
1707
|
+
};
|
|
1708
|
+
var typeTag = (t) => isVerbose() ? import_termkit14.Color.white.faint.encoder(` (${t})`) : "";
|
|
1709
|
+
var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
|
|
1472
1710
|
const config = getConfig();
|
|
1473
1711
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1474
1712
|
const language = config.language ?? "eng";
|
|
@@ -1507,90 +1745,90 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1507
1745
|
const edition = detectEdition(entry);
|
|
1508
1746
|
const folderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear, edition);
|
|
1509
1747
|
const destFolder = (0, import_path14.resolve)(destRoot, folderName);
|
|
1510
|
-
if ((0,
|
|
1748
|
+
if ((0, import_fs14.existsSync)(destFolder)) {
|
|
1511
1749
|
spinner_default.warn(`already exists: ${folderName}`);
|
|
1512
1750
|
return false;
|
|
1513
1751
|
}
|
|
1514
1752
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1515
1753
|
if (!videoFile) {
|
|
1516
|
-
if (
|
|
1754
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1517
1755
|
return false;
|
|
1518
1756
|
}
|
|
1519
1757
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
1520
1758
|
const destVideoName = `${folderName}.${videoExt}`;
|
|
1521
1759
|
const videoSourcePath = isDir ? (0, import_path14.resolve)(entryPath, videoFile) : entryPath;
|
|
1522
|
-
const dirFiles = isDir ? (0,
|
|
1760
|
+
const dirFiles = isDir ? (0, import_fs14.readdirSync)(entryPath) : [];
|
|
1523
1761
|
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1524
1762
|
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1525
1763
|
const subtitleSourcePath = subtitle ? (0, import_path14.resolve)(entryPath, subtitle) : null;
|
|
1526
1764
|
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
1527
1765
|
if (!dryRun) {
|
|
1528
1766
|
if (useHardlink) {
|
|
1529
|
-
(0,
|
|
1767
|
+
(0, import_fs14.mkdirSync)(destFolder, { recursive: true });
|
|
1530
1768
|
const destVideoPath = (0, import_path14.resolve)(destFolder, destVideoName);
|
|
1531
1769
|
let mode;
|
|
1532
1770
|
try {
|
|
1533
1771
|
if (!sameDev(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
|
|
1534
|
-
(0,
|
|
1772
|
+
(0, import_fs14.linkSync)(videoSourcePath, destVideoPath);
|
|
1535
1773
|
mode = "hardlink";
|
|
1536
1774
|
} catch {
|
|
1537
1775
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
|
|
1538
|
-
(0,
|
|
1776
|
+
(0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
|
|
1539
1777
|
mode = "copy";
|
|
1540
1778
|
}
|
|
1541
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
1779
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs14.cpSync)(subtitleSourcePath, (0, import_path14.resolve)(destFolder, destSubtitleName));
|
|
1542
1780
|
recordImport(sessionId, entryPath, destFolder, mode, tmdbId);
|
|
1543
1781
|
} else {
|
|
1544
1782
|
if (isDir) {
|
|
1545
1783
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
1546
|
-
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0,
|
|
1547
|
-
(0,
|
|
1548
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
1784
|
+
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs14.rmSync)((0, import_path14.resolve)(entryPath, f), { recursive: true, force: true });
|
|
1785
|
+
(0, import_fs14.renameSync)(videoSourcePath, (0, import_path14.resolve)(entryPath, destVideoName));
|
|
1786
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs14.renameSync)(subtitleSourcePath, (0, import_path14.resolve)(entryPath, destSubtitleName));
|
|
1549
1787
|
moveFolder(entryPath, destFolder);
|
|
1550
1788
|
} else {
|
|
1551
|
-
(0,
|
|
1789
|
+
(0, import_fs14.mkdirSync)(destFolder, { recursive: true });
|
|
1552
1790
|
const destVideoPath = (0, import_path14.resolve)(destFolder, destVideoName);
|
|
1553
1791
|
if (sameDev(videoSourcePath, destRoot)) {
|
|
1554
|
-
(0,
|
|
1792
|
+
(0, import_fs14.renameSync)(videoSourcePath, destVideoPath);
|
|
1555
1793
|
} else {
|
|
1556
|
-
(0,
|
|
1557
|
-
(0,
|
|
1794
|
+
(0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
|
|
1795
|
+
(0, import_fs14.rmSync)(videoSourcePath);
|
|
1558
1796
|
}
|
|
1559
1797
|
}
|
|
1560
1798
|
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1561
1799
|
}
|
|
1562
1800
|
}
|
|
1563
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${folderName}`);
|
|
1801
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
|
|
1564
1802
|
return true;
|
|
1565
1803
|
};
|
|
1566
1804
|
spinner_default.start();
|
|
1567
1805
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
1568
1806
|
let imported = 0, skipped = 0;
|
|
1569
1807
|
const pendingMovies = [];
|
|
1808
|
+
const pendingTv = [];
|
|
1809
|
+
const ignoreSet = new Set(config.ignore ?? []);
|
|
1810
|
+
const seenIgnored = /* @__PURE__ */ new Set();
|
|
1570
1811
|
for (const source of config.sources) {
|
|
1571
|
-
if (!(0,
|
|
1812
|
+
if (!(0, import_fs14.existsSync)(source)) {
|
|
1572
1813
|
spinner_default.warn(`source not found: ${import_termkit14.Color.white.encoder(source)}`);
|
|
1573
1814
|
continue;
|
|
1574
1815
|
}
|
|
1575
1816
|
spinner_default.text = `scanning ${import_termkit14.Color.white.encoder(source)}`;
|
|
1576
|
-
for (const entry of (
|
|
1577
|
-
|
|
1578
|
-
|
|
1817
|
+
for (const { entry, entryPath, isDir } of gatherEntries(source)) {
|
|
1818
|
+
if (ignoreSet.has(entry)) {
|
|
1819
|
+
seenIgnored.add(entry);
|
|
1820
|
+
if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
|
|
1821
|
+
continue;
|
|
1822
|
+
}
|
|
1579
1823
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
1580
|
-
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
1581
1824
|
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1582
1825
|
const isBookDir = isDir && containsBook(entryPath);
|
|
1583
|
-
if (!isDir && !isVideo && !isBook) {
|
|
1584
|
-
if (verbose) spinner_default.info(`skipped ${entry}`);
|
|
1585
|
-
skipped++;
|
|
1586
|
-
continue;
|
|
1587
|
-
}
|
|
1588
1826
|
let detectedType;
|
|
1589
1827
|
if (type) {
|
|
1590
1828
|
detectedType = type;
|
|
1591
1829
|
} else if (isBook || isBookDir) {
|
|
1592
1830
|
detectedType = "book";
|
|
1593
|
-
} else if (isDir &&
|
|
1831
|
+
} else if (isDir && /^[A-Z]{4}\d{5}/i.test(entry)) {
|
|
1594
1832
|
detectedType = "ps3";
|
|
1595
1833
|
} else if (/S\d{2,3}E\d{2,3}/i.test(entry) || /\d+x\d{2,3}/i.test(entry)) {
|
|
1596
1834
|
detectedType = "tv";
|
|
@@ -1599,7 +1837,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1599
1837
|
}
|
|
1600
1838
|
const destRoot = config.dest[detectedType];
|
|
1601
1839
|
if (!destRoot) {
|
|
1602
|
-
if (
|
|
1840
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
1603
1841
|
skipped++;
|
|
1604
1842
|
continue;
|
|
1605
1843
|
}
|
|
@@ -1612,7 +1850,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1612
1850
|
}
|
|
1613
1851
|
const destName = `${nameMatch[0]} [${id}]`;
|
|
1614
1852
|
const destPath = (0, import_path14.resolve)(destRoot, destName);
|
|
1615
|
-
if ((0,
|
|
1853
|
+
if ((0, import_fs14.existsSync)(destPath)) {
|
|
1616
1854
|
spinner_default.warn(`already exists: ${destName}`);
|
|
1617
1855
|
skipped++;
|
|
1618
1856
|
continue;
|
|
@@ -1621,13 +1859,13 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1621
1859
|
moveFolder(entryPath, destPath);
|
|
1622
1860
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1623
1861
|
}
|
|
1624
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${destName}`);
|
|
1862
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
|
|
1625
1863
|
imported++;
|
|
1626
1864
|
continue;
|
|
1627
1865
|
}
|
|
1628
1866
|
if (detectedType === "book") {
|
|
1629
1867
|
const destPath = (0, import_path14.resolve)(destRoot, entry);
|
|
1630
|
-
if ((0,
|
|
1868
|
+
if ((0, import_fs14.existsSync)(destPath)) {
|
|
1631
1869
|
spinner_default.warn(`already exists: ${entry}`);
|
|
1632
1870
|
skipped++;
|
|
1633
1871
|
continue;
|
|
@@ -1636,30 +1874,30 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1636
1874
|
if (isDir || isBookDir) {
|
|
1637
1875
|
moveFolder(entryPath, destPath);
|
|
1638
1876
|
} else {
|
|
1639
|
-
(0,
|
|
1877
|
+
(0, import_fs14.mkdirSync)(destRoot, { recursive: true });
|
|
1640
1878
|
if (sameDev(entryPath, destRoot)) {
|
|
1641
|
-
(0,
|
|
1879
|
+
(0, import_fs14.renameSync)(entryPath, destPath);
|
|
1642
1880
|
} else {
|
|
1643
|
-
(0,
|
|
1644
|
-
(0,
|
|
1881
|
+
(0, import_fs14.cpSync)(entryPath, destPath);
|
|
1882
|
+
(0, import_fs14.rmSync)(entryPath);
|
|
1645
1883
|
}
|
|
1646
1884
|
}
|
|
1647
1885
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1648
1886
|
}
|
|
1649
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${entry}`);
|
|
1887
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.book.encoder(entry)}${typeTag("book")}`);
|
|
1650
1888
|
imported++;
|
|
1651
1889
|
continue;
|
|
1652
1890
|
}
|
|
1653
1891
|
const parsed = parseDownloadName(entry);
|
|
1654
1892
|
if (!parsed) {
|
|
1655
|
-
if (
|
|
1893
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
1656
1894
|
skipped++;
|
|
1657
1895
|
continue;
|
|
1658
1896
|
}
|
|
1659
1897
|
if (detectedType === "movie") {
|
|
1660
1898
|
const confidence = classifyMovieConfidence(entry);
|
|
1661
1899
|
if (confidence === "skip") {
|
|
1662
|
-
if (
|
|
1900
|
+
if (isVerbose()) spinner_default.info(`skipped (uncertain): ${entry}`);
|
|
1663
1901
|
skipped++;
|
|
1664
1902
|
continue;
|
|
1665
1903
|
}
|
|
@@ -1703,7 +1941,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1703
1941
|
}
|
|
1704
1942
|
if (detectedType === "tv") {
|
|
1705
1943
|
if (parsed.season === void 0) {
|
|
1706
|
-
if (
|
|
1944
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
1707
1945
|
skipped++;
|
|
1708
1946
|
continue;
|
|
1709
1947
|
}
|
|
@@ -1713,20 +1951,25 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1713
1951
|
if (registeredShow) {
|
|
1714
1952
|
showPath = registeredShow.path;
|
|
1715
1953
|
showFolderName = showPath.split("/").pop() ?? registeredShow.path;
|
|
1716
|
-
} else if (auto) {
|
|
1717
|
-
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1718
|
-
showPath = (0, import_path14.resolve)(destRoot, showFolderName);
|
|
1719
|
-
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1720
1954
|
} else {
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1955
|
+
const existingFolder = findShowFolder(destRoot, resolvedTitle);
|
|
1956
|
+
if (existingFolder) {
|
|
1957
|
+
showFolderName = existingFolder;
|
|
1958
|
+
showPath = (0, import_path14.resolve)(destRoot, existingFolder);
|
|
1959
|
+
} else if (auto) {
|
|
1960
|
+
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1961
|
+
showPath = (0, import_path14.resolve)(destRoot, showFolderName);
|
|
1962
|
+
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1963
|
+
} else {
|
|
1964
|
+
pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
|
|
1965
|
+
continue;
|
|
1966
|
+
}
|
|
1724
1967
|
}
|
|
1725
1968
|
const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1726
1969
|
const seasonPath = (0, import_path14.resolve)(showPath, seasonFolderName);
|
|
1727
1970
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1728
1971
|
if (!videoFile) {
|
|
1729
|
-
if (
|
|
1972
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1730
1973
|
skipped++;
|
|
1731
1974
|
continue;
|
|
1732
1975
|
}
|
|
@@ -1736,7 +1979,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1736
1979
|
const destVideoName = `${episodeName}.${videoExt}`;
|
|
1737
1980
|
const destVideoPath = (0, import_path14.resolve)(seasonPath, destVideoName);
|
|
1738
1981
|
const videoSourcePath = isDir ? (0, import_path14.resolve)(entryPath, videoFile) : entryPath;
|
|
1739
|
-
if ((0,
|
|
1982
|
+
if ((0, import_fs14.existsSync)(destVideoPath)) {
|
|
1740
1983
|
let shouldReplace = force;
|
|
1741
1984
|
if (!shouldReplace && interactive) {
|
|
1742
1985
|
spinner_default.stop();
|
|
@@ -1754,43 +1997,43 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1754
1997
|
continue;
|
|
1755
1998
|
}
|
|
1756
1999
|
if (!dryRun) {
|
|
1757
|
-
for (const f of (0,
|
|
1758
|
-
if (f.startsWith(`${episodeName}.`)) (0,
|
|
2000
|
+
for (const f of (0, import_fs14.readdirSync)(seasonPath)) {
|
|
2001
|
+
if (f.startsWith(`${episodeName}.`)) (0, import_fs14.rmSync)((0, import_path14.resolve)(seasonPath, f));
|
|
1759
2002
|
}
|
|
1760
2003
|
}
|
|
1761
2004
|
}
|
|
1762
|
-
const dirFiles = isDir ? (0,
|
|
2005
|
+
const dirFiles = isDir ? (0, import_fs14.readdirSync)(entryPath) : [];
|
|
1763
2006
|
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1764
2007
|
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1765
2008
|
const subtitleSourcePath = subtitle ? (0, import_path14.resolve)(entryPath, subtitle) : null;
|
|
1766
2009
|
const destSubtitleName = subtitle && subtitleExt ? `${episodeName}.${subtitleExt}` : null;
|
|
1767
2010
|
if (!dryRun) {
|
|
1768
|
-
(0,
|
|
2011
|
+
(0, import_fs14.mkdirSync)(seasonPath, { recursive: true });
|
|
1769
2012
|
let mode = "move";
|
|
1770
2013
|
if (useHardlink) {
|
|
1771
2014
|
try {
|
|
1772
2015
|
if (!sameDev(videoSourcePath, seasonPath)) throw new Error("cross-filesystem");
|
|
1773
|
-
(0,
|
|
2016
|
+
(0, import_fs14.linkSync)(videoSourcePath, destVideoPath);
|
|
1774
2017
|
mode = "hardlink";
|
|
1775
2018
|
} catch {
|
|
1776
2019
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
|
|
1777
|
-
(0,
|
|
2020
|
+
(0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
|
|
1778
2021
|
mode = "copy";
|
|
1779
2022
|
}
|
|
1780
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
2023
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs14.cpSync)(subtitleSourcePath, (0, import_path14.resolve)(seasonPath, destSubtitleName));
|
|
1781
2024
|
} else {
|
|
1782
2025
|
if (sameDev(videoSourcePath, seasonPath)) {
|
|
1783
|
-
(0,
|
|
2026
|
+
(0, import_fs14.renameSync)(videoSourcePath, destVideoPath);
|
|
1784
2027
|
} else {
|
|
1785
|
-
(0,
|
|
1786
|
-
(0,
|
|
2028
|
+
(0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
|
|
2029
|
+
(0, import_fs14.rmSync)(videoSourcePath);
|
|
1787
2030
|
}
|
|
1788
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
1789
|
-
if (isDir) (0,
|
|
2031
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs14.renameSync)(subtitleSourcePath, (0, import_path14.resolve)(seasonPath, destSubtitleName));
|
|
2032
|
+
if (isDir) (0, import_fs14.rmSync)(entryPath, { recursive: true, force: true });
|
|
1790
2033
|
}
|
|
1791
2034
|
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
1792
2035
|
}
|
|
1793
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${showFolderName} / ${seasonFolderName} / ${episodeName}`);
|
|
2036
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
|
|
1794
2037
|
imported++;
|
|
1795
2038
|
continue;
|
|
1796
2039
|
}
|
|
@@ -1803,7 +2046,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1803
2046
|
}
|
|
1804
2047
|
if (pendingMovies.length > 0) {
|
|
1805
2048
|
spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
|
|
1806
|
-
for (const p of pendingMovies) spinner_default.info(` ? ${p.entry.replace(/\/$/, "")}`);
|
|
2049
|
+
for (const p of pendingMovies) spinner_default.info(` ${typeColor.movie.encoder("?")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
|
|
1807
2050
|
let toProcess = [];
|
|
1808
2051
|
if (interactive) {
|
|
1809
2052
|
spinner_default.stop();
|
|
@@ -1830,14 +2073,28 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1830
2073
|
}
|
|
1831
2074
|
}
|
|
1832
2075
|
}
|
|
1833
|
-
|
|
2076
|
+
if (pendingTv.length > 0) {
|
|
2077
|
+
spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
|
|
2078
|
+
for (const p of pendingTv) spinner_default.info(` ${typeColor.tv.encoder("?")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
2079
|
+
skipped += pendingTv.length;
|
|
2080
|
+
}
|
|
2081
|
+
if (ignoreSet.size > 0) {
|
|
2082
|
+
const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
|
|
2083
|
+
if (stale.length > 0 && !dryRun) {
|
|
2084
|
+
const updated = config.ignore.filter((name) => !stale.includes(name));
|
|
2085
|
+
config.ignore = updated;
|
|
2086
|
+
saveConfig(config);
|
|
2087
|
+
for (const name of stale) spinner_default.info(`removed from ignore list (not found): ${import_termkit14.Color.white.encoder(name)}`);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
|
|
1834
2091
|
if (skipped) spinner_default.info(`skipped ${skipped} items`);
|
|
1835
2092
|
spinner_default.stop();
|
|
1836
2093
|
};
|
|
1837
2094
|
var scan_default = scan;
|
|
1838
2095
|
|
|
1839
2096
|
// src/actions/shows.ts
|
|
1840
|
-
var
|
|
2097
|
+
var import_fs15 = require("fs");
|
|
1841
2098
|
var import_termkit15 = require("termkit");
|
|
1842
2099
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
1843
2100
|
var shows = async () => {
|
|
@@ -1854,7 +2111,7 @@ ${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termki
|
|
|
1854
2111
|
new import_termkit15.Table(
|
|
1855
2112
|
allShows.map((show) => ({
|
|
1856
2113
|
name: show.path.split("/").pop() ?? show.path,
|
|
1857
|
-
size: (0,
|
|
2114
|
+
size: (0, import_fs15.existsSync)(show.path) ? formatSize(dirSize(show.path)) : "\u2014",
|
|
1858
2115
|
tmdbId: show.tmdbId,
|
|
1859
2116
|
ended: show.ended
|
|
1860
2117
|
})),
|
|
@@ -1887,13 +2144,13 @@ ${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termki
|
|
|
1887
2144
|
var shows_default = shows;
|
|
1888
2145
|
|
|
1889
2146
|
// src/actions/stats.ts
|
|
1890
|
-
var
|
|
2147
|
+
var import_fs16 = require("fs");
|
|
1891
2148
|
var import_path15 = require("path");
|
|
1892
2149
|
var import_termkit16 = require("termkit");
|
|
1893
2150
|
var countVideos = (dir) => {
|
|
1894
2151
|
let count = 0;
|
|
1895
2152
|
try {
|
|
1896
|
-
for (const entry of (0,
|
|
2153
|
+
for (const entry of (0, import_fs16.readdirSync)(dir, { withFileTypes: true })) {
|
|
1897
2154
|
if (entry.isDirectory()) {
|
|
1898
2155
|
count += countVideos((0, import_path15.resolve)(dir, entry.name));
|
|
1899
2156
|
} else {
|
|
@@ -1907,9 +2164,9 @@ var countVideos = (dir) => {
|
|
|
1907
2164
|
};
|
|
1908
2165
|
var countDirs = (dir) => {
|
|
1909
2166
|
try {
|
|
1910
|
-
return (0,
|
|
2167
|
+
return (0, import_fs16.readdirSync)(dir).filter((f) => {
|
|
1911
2168
|
try {
|
|
1912
|
-
return (0,
|
|
2169
|
+
return (0, import_fs16.lstatSync)((0, import_path15.resolve)(dir, f)).isDirectory();
|
|
1913
2170
|
} catch {
|
|
1914
2171
|
return false;
|
|
1915
2172
|
}
|
|
@@ -1923,16 +2180,16 @@ var stats = async () => {
|
|
|
1923
2180
|
const shows2 = getShows();
|
|
1924
2181
|
const rows = [];
|
|
1925
2182
|
const movieDest = config.dest.movie;
|
|
1926
|
-
if (movieDest && (0,
|
|
2183
|
+
if (movieDest && (0, import_fs16.existsSync)(movieDest)) {
|
|
1927
2184
|
rows.push({ category: "Movies", count: countDirs(movieDest), size: formatSize(dirSize(movieDest)) });
|
|
1928
2185
|
}
|
|
1929
2186
|
const tvDest = config.dest.tv;
|
|
1930
|
-
if (tvDest && (0,
|
|
2187
|
+
if (tvDest && (0, import_fs16.existsSync)(tvDest)) {
|
|
1931
2188
|
rows.push({ category: "Shows", count: shows2.length, size: formatSize(dirSize(tvDest)) });
|
|
1932
2189
|
rows.push({ category: "Episodes", count: countVideos(tvDest) });
|
|
1933
2190
|
}
|
|
1934
2191
|
const ps3Dest = config.dest.ps3;
|
|
1935
|
-
if (ps3Dest && (0,
|
|
2192
|
+
if (ps3Dest && (0, import_fs16.existsSync)(ps3Dest)) {
|
|
1936
2193
|
rows.push({ category: "PS3", count: countDirs(ps3Dest), size: formatSize(dirSize(ps3Dest)) });
|
|
1937
2194
|
}
|
|
1938
2195
|
if (rows.length === 0) return;
|
|
@@ -1951,7 +2208,7 @@ var stats = async () => {
|
|
|
1951
2208
|
var stats_default = stats;
|
|
1952
2209
|
|
|
1953
2210
|
// src/actions/undo.ts
|
|
1954
|
-
var
|
|
2211
|
+
var import_fs17 = require("fs");
|
|
1955
2212
|
var import_termkit17 = require("termkit");
|
|
1956
2213
|
var undo = async () => {
|
|
1957
2214
|
spinner_default.start();
|
|
@@ -1963,7 +2220,7 @@ var undo = async () => {
|
|
|
1963
2220
|
}
|
|
1964
2221
|
let undone = 0;
|
|
1965
2222
|
for (const record of records) {
|
|
1966
|
-
(0,
|
|
2223
|
+
(0, import_fs17.renameSync)(record.newPath, record.oldPath);
|
|
1967
2224
|
spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.newPath)} \u2192 ${import_termkit17.Color.white.encoder(record.oldPath)}`);
|
|
1968
2225
|
undone++;
|
|
1969
2226
|
}
|
|
@@ -1975,39 +2232,115 @@ var undo_default = undo;
|
|
|
1975
2232
|
|
|
1976
2233
|
// src/actions/watch.ts
|
|
1977
2234
|
var import_chokidar = __toESM(require("chokidar"));
|
|
1978
|
-
var
|
|
2235
|
+
var import_fs18 = require("fs");
|
|
1979
2236
|
var import_path16 = require("path");
|
|
1980
2237
|
var import_termkit18 = require("termkit");
|
|
1981
2238
|
var sameDev2 = (a, b) => {
|
|
1982
2239
|
try {
|
|
1983
2240
|
let bExisting = b;
|
|
1984
|
-
while (!(0,
|
|
1985
|
-
return (0,
|
|
2241
|
+
while (!(0, import_fs18.existsSync)(bExisting)) bExisting = (0, import_path16.dirname)(bExisting);
|
|
2242
|
+
return (0, import_fs18.statSync)(a).dev === (0, import_fs18.statSync)(bExisting).dev;
|
|
1986
2243
|
} catch {
|
|
1987
2244
|
return false;
|
|
1988
2245
|
}
|
|
1989
2246
|
};
|
|
1990
2247
|
var moveItem = (src, dest) => {
|
|
1991
2248
|
if (sameDev2(src, dest)) {
|
|
1992
|
-
(0,
|
|
2249
|
+
(0, import_fs18.renameSync)(src, dest);
|
|
1993
2250
|
} else {
|
|
1994
|
-
(0,
|
|
1995
|
-
(0,
|
|
2251
|
+
(0, import_fs18.cpSync)(src, dest, { recursive: true });
|
|
2252
|
+
(0, import_fs18.rmSync)(src, { recursive: true, force: true });
|
|
1996
2253
|
}
|
|
1997
2254
|
};
|
|
1998
|
-
var findVideo2 = (dir) => (0,
|
|
2255
|
+
var findVideo2 = (dir) => (0, import_fs18.readdirSync)(dir).find((f) => {
|
|
1999
2256
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
2000
2257
|
return ext && videoExtensions_default.includes(ext);
|
|
2001
2258
|
}) ?? null;
|
|
2002
|
-
var containsBook2 = (dir) => (0,
|
|
2259
|
+
var containsBook2 = (dir, depth = 2) => (0, import_fs18.readdirSync)(dir).some((f) => {
|
|
2003
2260
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
2004
|
-
|
|
2261
|
+
if (ext && bookExtensions_default.includes(ext)) return true;
|
|
2262
|
+
if (depth > 1) {
|
|
2263
|
+
try {
|
|
2264
|
+
const sub = (0, import_path16.resolve)(dir, f);
|
|
2265
|
+
if ((0, import_fs18.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
|
|
2266
|
+
} catch {
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
return false;
|
|
2005
2270
|
});
|
|
2271
|
+
var isTvEpisodeName2 = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
|
|
2272
|
+
var isSeasonDirName2 = (name) => !isTvEpisodeName2(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
|
|
2273
|
+
var expandWatchPath = (p) => {
|
|
2274
|
+
let isDir;
|
|
2275
|
+
try {
|
|
2276
|
+
isDir = (0, import_fs18.lstatSync)(p).isDirectory();
|
|
2277
|
+
} catch {
|
|
2278
|
+
return [p];
|
|
2279
|
+
}
|
|
2280
|
+
if (!isDir) return [p];
|
|
2281
|
+
const name = (0, import_path16.basename)(p);
|
|
2282
|
+
if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
|
|
2283
|
+
let children;
|
|
2284
|
+
try {
|
|
2285
|
+
children = (0, import_fs18.readdirSync)(p);
|
|
2286
|
+
} catch {
|
|
2287
|
+
return [p];
|
|
2288
|
+
}
|
|
2289
|
+
if (children.some((c) => isTvEpisodeName2(c))) {
|
|
2290
|
+
const entries = [];
|
|
2291
|
+
for (const child of children) {
|
|
2292
|
+
const cp = (0, import_path16.resolve)(p, child);
|
|
2293
|
+
let cd;
|
|
2294
|
+
try {
|
|
2295
|
+
cd = (0, import_fs18.lstatSync)(cp).isDirectory();
|
|
2296
|
+
} catch {
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
const ext = child.match(/([^.]+$)/)?.[0];
|
|
2300
|
+
if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
|
|
2301
|
+
entries.push(cp);
|
|
2302
|
+
}
|
|
2303
|
+
return entries.length > 0 ? entries : [p];
|
|
2304
|
+
}
|
|
2305
|
+
const seasonDirs = children.filter((c) => {
|
|
2306
|
+
try {
|
|
2307
|
+
return isSeasonDirName2(c) && (0, import_fs18.lstatSync)((0, import_path16.resolve)(p, c)).isDirectory();
|
|
2308
|
+
} catch {
|
|
2309
|
+
return false;
|
|
2310
|
+
}
|
|
2311
|
+
});
|
|
2312
|
+
if (seasonDirs.length > 0) {
|
|
2313
|
+
const entries = [];
|
|
2314
|
+
for (const sd of seasonDirs) {
|
|
2315
|
+
const sp = (0, import_path16.resolve)(p, sd);
|
|
2316
|
+
let sc;
|
|
2317
|
+
try {
|
|
2318
|
+
sc = (0, import_fs18.readdirSync)(sp);
|
|
2319
|
+
} catch {
|
|
2320
|
+
continue;
|
|
2321
|
+
}
|
|
2322
|
+
for (const child of sc) {
|
|
2323
|
+
const cp = (0, import_path16.resolve)(sp, child);
|
|
2324
|
+
let cd;
|
|
2325
|
+
try {
|
|
2326
|
+
cd = (0, import_fs18.lstatSync)(cp).isDirectory();
|
|
2327
|
+
} catch {
|
|
2328
|
+
continue;
|
|
2329
|
+
}
|
|
2330
|
+
const ext = child.match(/([^.]+$)/)?.[0];
|
|
2331
|
+
if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
|
|
2332
|
+
entries.push(cp);
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
return entries.length > 0 ? entries : [p];
|
|
2336
|
+
}
|
|
2337
|
+
return [p];
|
|
2338
|
+
};
|
|
2006
2339
|
var findSeasonFolder2 = (showPath, season) => {
|
|
2007
|
-
if (!(0,
|
|
2008
|
-
const folders = (0,
|
|
2340
|
+
if (!(0, import_fs18.existsSync)(showPath)) return null;
|
|
2341
|
+
const folders = (0, import_fs18.readdirSync)(showPath).filter((f) => {
|
|
2009
2342
|
try {
|
|
2010
|
-
return (0,
|
|
2343
|
+
return (0, import_fs18.lstatSync)((0, import_path16.resolve)(showPath, f)).isDirectory();
|
|
2011
2344
|
} catch {
|
|
2012
2345
|
return false;
|
|
2013
2346
|
}
|
|
@@ -2017,13 +2350,13 @@ var findSeasonFolder2 = (showPath, season) => {
|
|
|
2017
2350
|
return match && parseInt(match[1]) === season;
|
|
2018
2351
|
}) ?? null;
|
|
2019
2352
|
};
|
|
2020
|
-
var processItem = async (entryPath, useHardlink,
|
|
2353
|
+
var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
2021
2354
|
const config = getConfig();
|
|
2022
2355
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
2023
2356
|
const entry = (0, import_path16.basename)(entryPath);
|
|
2024
2357
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
2025
2358
|
const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
|
|
2026
|
-
const isDir = (0,
|
|
2359
|
+
const isDir = (0, import_fs18.lstatSync)(entryPath).isDirectory();
|
|
2027
2360
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
2028
2361
|
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
2029
2362
|
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
@@ -2041,7 +2374,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2041
2374
|
}
|
|
2042
2375
|
const destRoot = config.dest[detectedType];
|
|
2043
2376
|
if (!destRoot) {
|
|
2044
|
-
if (
|
|
2377
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
2045
2378
|
return;
|
|
2046
2379
|
}
|
|
2047
2380
|
if (detectedType === "ps3") {
|
|
@@ -2050,7 +2383,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2050
2383
|
if (!nameMatch || !id) return;
|
|
2051
2384
|
const destName = `${nameMatch[0]} [${id}]`;
|
|
2052
2385
|
const destPath = (0, import_path16.resolve)(destRoot, destName);
|
|
2053
|
-
if ((0,
|
|
2386
|
+
if ((0, import_fs18.existsSync)(destPath)) {
|
|
2054
2387
|
spinner_default.warn(`already exists: ${destName}`);
|
|
2055
2388
|
return;
|
|
2056
2389
|
}
|
|
@@ -2061,19 +2394,19 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2061
2394
|
}
|
|
2062
2395
|
if (detectedType === "book") {
|
|
2063
2396
|
const destPath = (0, import_path16.resolve)(destRoot, entry);
|
|
2064
|
-
if ((0,
|
|
2397
|
+
if ((0, import_fs18.existsSync)(destPath)) {
|
|
2065
2398
|
spinner_default.warn(`already exists: ${entry}`);
|
|
2066
2399
|
return;
|
|
2067
2400
|
}
|
|
2068
2401
|
if (isDir || isBookDir) {
|
|
2069
2402
|
moveItem(entryPath, destPath);
|
|
2070
2403
|
} else {
|
|
2071
|
-
(0,
|
|
2404
|
+
(0, import_fs18.mkdirSync)(destRoot, { recursive: true });
|
|
2072
2405
|
if (sameDev2(entryPath, destRoot)) {
|
|
2073
|
-
(0,
|
|
2406
|
+
(0, import_fs18.renameSync)(entryPath, destPath);
|
|
2074
2407
|
} else {
|
|
2075
|
-
(0,
|
|
2076
|
-
(0,
|
|
2408
|
+
(0, import_fs18.cpSync)(entryPath, destPath);
|
|
2409
|
+
(0, import_fs18.rmSync)(entryPath);
|
|
2077
2410
|
}
|
|
2078
2411
|
}
|
|
2079
2412
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
@@ -2082,12 +2415,12 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2082
2415
|
}
|
|
2083
2416
|
const parsed = parseDownloadName(entry);
|
|
2084
2417
|
if (!parsed) {
|
|
2085
|
-
if (
|
|
2418
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
2086
2419
|
return;
|
|
2087
2420
|
}
|
|
2088
2421
|
if (detectedType === "tv") {
|
|
2089
2422
|
if (parsed.season === void 0) {
|
|
2090
|
-
if (
|
|
2423
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
2091
2424
|
return;
|
|
2092
2425
|
}
|
|
2093
2426
|
const registeredShow = getShowByTitle(parsed.title);
|
|
@@ -2101,14 +2434,14 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2101
2434
|
showPath = (0, import_path16.resolve)(destRoot, showFolderName);
|
|
2102
2435
|
upsertShow(showPath, null, parsed.title);
|
|
2103
2436
|
} else {
|
|
2104
|
-
if (
|
|
2437
|
+
if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
|
|
2105
2438
|
return;
|
|
2106
2439
|
}
|
|
2107
2440
|
const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
2108
2441
|
const seasonPath = (0, import_path16.resolve)(showPath, seasonFolderName);
|
|
2109
2442
|
const videoFile2 = isDir ? findVideo2(entryPath) : entry;
|
|
2110
2443
|
if (!videoFile2) {
|
|
2111
|
-
if (
|
|
2444
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
2112
2445
|
return;
|
|
2113
2446
|
}
|
|
2114
2447
|
const videoExt2 = videoFile2.match(/([^.]+$)/)?.[0];
|
|
@@ -2117,37 +2450,37 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2117
2450
|
const destVideoName2 = `${episodeName}.${videoExt2}`;
|
|
2118
2451
|
const destVideoPath = (0, import_path16.resolve)(seasonPath, destVideoName2);
|
|
2119
2452
|
const videoSourcePath2 = isDir ? (0, import_path16.resolve)(entryPath, videoFile2) : entryPath;
|
|
2120
|
-
if ((0,
|
|
2453
|
+
if ((0, import_fs18.existsSync)(destVideoPath)) {
|
|
2121
2454
|
spinner_default.warn(`already exists: ${episodeName}`);
|
|
2122
2455
|
return;
|
|
2123
2456
|
}
|
|
2124
|
-
const dirFiles2 = isDir ? (0,
|
|
2457
|
+
const dirFiles2 = isDir ? (0, import_fs18.readdirSync)(entryPath) : [];
|
|
2125
2458
|
const subtitle2 = isDir ? findSubtitle(dirFiles2, language) : null;
|
|
2126
2459
|
const subtitleExt2 = subtitle2?.match(/([^.]+$)/)?.[0];
|
|
2127
2460
|
const subtitleSourcePath2 = subtitle2 ? (0, import_path16.resolve)(entryPath, subtitle2) : null;
|
|
2128
2461
|
const destSubtitleName2 = subtitle2 && subtitleExt2 ? `${episodeName}.${subtitleExt2}` : null;
|
|
2129
|
-
(0,
|
|
2462
|
+
(0, import_fs18.mkdirSync)(seasonPath, { recursive: true });
|
|
2130
2463
|
let mode = "move";
|
|
2131
2464
|
if (useHardlink) {
|
|
2132
2465
|
try {
|
|
2133
2466
|
if (!sameDev2(videoSourcePath2, seasonPath)) throw new Error("cross-filesystem");
|
|
2134
|
-
(0,
|
|
2467
|
+
(0, import_fs18.linkSync)(videoSourcePath2, destVideoPath);
|
|
2135
2468
|
mode = "hardlink";
|
|
2136
2469
|
} catch {
|
|
2137
2470
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
|
|
2138
|
-
(0,
|
|
2471
|
+
(0, import_fs18.cpSync)(videoSourcePath2, destVideoPath);
|
|
2139
2472
|
mode = "copy";
|
|
2140
2473
|
}
|
|
2141
|
-
if (subtitleSourcePath2 && destSubtitleName2) (0,
|
|
2474
|
+
if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs18.cpSync)(subtitleSourcePath2, (0, import_path16.resolve)(seasonPath, destSubtitleName2));
|
|
2142
2475
|
} else {
|
|
2143
2476
|
if (sameDev2(videoSourcePath2, seasonPath)) {
|
|
2144
|
-
(0,
|
|
2477
|
+
(0, import_fs18.renameSync)(videoSourcePath2, destVideoPath);
|
|
2145
2478
|
} else {
|
|
2146
|
-
(0,
|
|
2147
|
-
(0,
|
|
2479
|
+
(0, import_fs18.cpSync)(videoSourcePath2, destVideoPath);
|
|
2480
|
+
(0, import_fs18.rmSync)(videoSourcePath2);
|
|
2148
2481
|
}
|
|
2149
|
-
if (subtitleSourcePath2 && destSubtitleName2) (0,
|
|
2150
|
-
if (isDir) (0,
|
|
2482
|
+
if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs18.renameSync)(subtitleSourcePath2, (0, import_path16.resolve)(seasonPath, destSubtitleName2));
|
|
2483
|
+
if (isDir) (0, import_fs18.rmSync)(entryPath, { recursive: true, force: true });
|
|
2151
2484
|
}
|
|
2152
2485
|
recordImport(sessionId, entryPath, seasonPath, mode);
|
|
2153
2486
|
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
|
|
@@ -2156,60 +2489,60 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2156
2489
|
const edition = detectEdition(entry);
|
|
2157
2490
|
const folderName = formatMovieName(movieFormat, parsed.title, parsed.year, edition);
|
|
2158
2491
|
const destFolder = (0, import_path16.resolve)(destRoot, folderName);
|
|
2159
|
-
if ((0,
|
|
2492
|
+
if ((0, import_fs18.existsSync)(destFolder)) {
|
|
2160
2493
|
spinner_default.warn(`already exists: ${folderName}`);
|
|
2161
2494
|
return;
|
|
2162
2495
|
}
|
|
2163
2496
|
const videoFile = isDir ? findVideo2(entryPath) : entry;
|
|
2164
2497
|
if (!videoFile) {
|
|
2165
|
-
if (
|
|
2498
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
2166
2499
|
return;
|
|
2167
2500
|
}
|
|
2168
2501
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
2169
2502
|
const destVideoName = `${folderName}.${videoExt}`;
|
|
2170
2503
|
const videoSourcePath = isDir ? (0, import_path16.resolve)(entryPath, videoFile) : entryPath;
|
|
2171
|
-
const dirFiles = isDir ? (0,
|
|
2504
|
+
const dirFiles = isDir ? (0, import_fs18.readdirSync)(entryPath) : [];
|
|
2172
2505
|
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
2173
2506
|
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
2174
2507
|
const subtitleSourcePath = subtitle ? (0, import_path16.resolve)(entryPath, subtitle) : null;
|
|
2175
2508
|
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
2176
2509
|
if (useHardlink) {
|
|
2177
|
-
(0,
|
|
2510
|
+
(0, import_fs18.mkdirSync)(destFolder, { recursive: true });
|
|
2178
2511
|
const destVideoPath = (0, import_path16.resolve)(destFolder, destVideoName);
|
|
2179
2512
|
let mode;
|
|
2180
2513
|
try {
|
|
2181
2514
|
if (!sameDev2(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
|
|
2182
|
-
(0,
|
|
2515
|
+
(0, import_fs18.linkSync)(videoSourcePath, destVideoPath);
|
|
2183
2516
|
mode = "hardlink";
|
|
2184
2517
|
} catch {
|
|
2185
2518
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
|
|
2186
|
-
(0,
|
|
2519
|
+
(0, import_fs18.cpSync)(videoSourcePath, destVideoPath);
|
|
2187
2520
|
mode = "copy";
|
|
2188
2521
|
}
|
|
2189
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
2522
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs18.cpSync)(subtitleSourcePath, (0, import_path16.resolve)(destFolder, destSubtitleName));
|
|
2190
2523
|
recordImport(sessionId, entryPath, destFolder, mode);
|
|
2191
2524
|
} else {
|
|
2192
2525
|
if (isDir) {
|
|
2193
2526
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
2194
|
-
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0,
|
|
2195
|
-
(0,
|
|
2196
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
2527
|
+
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs18.rmSync)((0, import_path16.resolve)(entryPath, f), { recursive: true, force: true });
|
|
2528
|
+
(0, import_fs18.renameSync)(videoSourcePath, (0, import_path16.resolve)(entryPath, destVideoName));
|
|
2529
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs18.renameSync)(subtitleSourcePath, (0, import_path16.resolve)(entryPath, destSubtitleName));
|
|
2197
2530
|
moveItem(entryPath, destFolder);
|
|
2198
2531
|
} else {
|
|
2199
|
-
(0,
|
|
2532
|
+
(0, import_fs18.mkdirSync)(destFolder, { recursive: true });
|
|
2200
2533
|
const destVideoPath = (0, import_path16.resolve)(destFolder, destVideoName);
|
|
2201
2534
|
if (sameDev2(videoSourcePath, destRoot)) {
|
|
2202
|
-
(0,
|
|
2535
|
+
(0, import_fs18.renameSync)(videoSourcePath, destVideoPath);
|
|
2203
2536
|
} else {
|
|
2204
|
-
(0,
|
|
2205
|
-
(0,
|
|
2537
|
+
(0, import_fs18.cpSync)(videoSourcePath, destVideoPath);
|
|
2538
|
+
(0, import_fs18.rmSync)(videoSourcePath);
|
|
2206
2539
|
}
|
|
2207
2540
|
}
|
|
2208
2541
|
recordImport(sessionId, entryPath, destFolder, "move");
|
|
2209
2542
|
}
|
|
2210
2543
|
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(folderName)}`);
|
|
2211
2544
|
};
|
|
2212
|
-
var watch = async ({ hardlink = false,
|
|
2545
|
+
var watch = async ({ hardlink = false, auto = false }) => {
|
|
2213
2546
|
const config = getConfig();
|
|
2214
2547
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
2215
2548
|
const language = config.language ?? "eng";
|
|
@@ -2222,7 +2555,9 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
2222
2555
|
setTimeout(async () => {
|
|
2223
2556
|
pending.delete(path);
|
|
2224
2557
|
try {
|
|
2225
|
-
|
|
2558
|
+
for (const entry of expandWatchPath(path)) {
|
|
2559
|
+
await processItem(entry, hardlink, language, auto);
|
|
2560
|
+
}
|
|
2226
2561
|
} catch (err) {
|
|
2227
2562
|
spinner_default.fail(`error processing ${path}: ${err.message}`);
|
|
2228
2563
|
}
|
|
@@ -2245,19 +2580,20 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
2245
2580
|
var watch_default = watch;
|
|
2246
2581
|
|
|
2247
2582
|
// package.json
|
|
2248
|
-
var version = "0.2.
|
|
2583
|
+
var version = "0.2.3";
|
|
2249
2584
|
|
|
2250
2585
|
// src/program.ts
|
|
2251
2586
|
var toCamel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
2252
2587
|
var adapt = (fn) => (options) => {
|
|
2253
2588
|
const camel = Object.fromEntries(Object.entries(options).map(([k, v]) => [toCamel(k), v]));
|
|
2589
|
+
setVerbose(!!camel.verbose);
|
|
2254
2590
|
return fn(camel);
|
|
2255
2591
|
};
|
|
2256
2592
|
var { command, option } = import_termkit19.Program;
|
|
2257
2593
|
var program = import_termkit19.Program.command("reelsort").version(version).description("a cli to manage media").commands([
|
|
2258
2594
|
command("config").description("manage configuration").commands([
|
|
2259
|
-
command("source").description("manage source directories").commands([command("add", "<dir>").description("add a source directory").action(adapt(sourceAdd)), command("remove", "
|
|
2260
|
-
command("dest").description("manage destinations").commands([command("add", "<type> <dir>").description("set a destination (movie, tv, ps3, book)").action(adapt(destAdd)), command("remove", "
|
|
2595
|
+
command("source").description("manage source directories").commands([command("add", "<dir>").description("add a source directory").action(adapt(sourceAdd)), command("remove", "[dir]").description("remove a source directory").action(adapt(sourceRemove))]),
|
|
2596
|
+
command("dest").description("manage destinations").commands([command("add", "<type> <dir>").description("set a destination (movie, tv, ps3, book)").action(adapt(destAdd)), command("remove", "[type]").description("remove a destination (movie, tv, ps3, book)").action(adapt(destRemove))]),
|
|
2261
2597
|
command("set", "<key> <subkey> [value]").description("set a value (e.g. set language eng)").action(adapt(configSet)),
|
|
2262
2598
|
command("show").description("show current configuration").action(adapt(configShow))
|
|
2263
2599
|
]),
|
|
@@ -2267,6 +2603,7 @@ var program = import_termkit19.Program.command("reelsort").version(version).desc
|
|
|
2267
2603
|
command("list").description("list library contents").options([option("t", "type", "<type>", "media type: movie, tv, ps3, book"), option("m", "missing-subs", null, "only show items without subtitles"), option("c", "codec", "<codec>", "filter by codec (e.g. x265)"), option("r", "resolution", "<res>", "filter by resolution (e.g. 1080p, 4K)"), option("s", "sort", "<field>", "sort by: year (default), title")]).action(adapt(list_default)),
|
|
2268
2604
|
command("watch").description("watch sources and auto-import new media").options([option("H", "hardlink", null, "hardlink instead of moving (falls back to copy across filesystems)"), option("v", "verbose", null, "additional output"), option("a", "auto", null, "auto-register unrecognised TV shows instead of skipping them")]).action(adapt(watch_default)),
|
|
2269
2605
|
command("scan").description("import media from configured sources to destinations").options([option("t", "type", "<type>", "only process this media type: movie, tv, ps3, book"), option("H", "hardlink", null, "hardlink instead of moving (falls back to copy across filesystems)"), option("n", "dry-run", null, "show what would be imported without doing it"), option("v", "verbose", null, "additional output"), option("a", "auto", null, "auto-register unrecognised TV shows instead of skipping them"), option("f", "force", null, "overwrite existing files and import all uncertain movie matches without prompting"), option("i", "interactive", null, "review uncertain movie matches and existing-file conflicts with an interactive picker")]).action(adapt(scan_default)),
|
|
2606
|
+
command("ignore", "[name...]").description("ignore source files during scan (interactive picker if no names given)").action(adapt(ignore)).commands([command("remove", "[name]").description("remove files from the ignore list").action(adapt(ignoreRemove))]),
|
|
2270
2607
|
command("clean").description("remove source files that have already been imported").options([option("n", "dry-run", null, "show what would be removed without doing it"), option("o", "older-than", "<age>", "only clean imports older than age (e.g. 14d, 6h, 30m)")]).action(adapt(clean_default)),
|
|
2271
2608
|
command("undo").description("undo the last rename session").action(adapt(undo_default)),
|
|
2272
2609
|
command("history").description("show rename or import history").options([option("l", "limit", "<n>", "number of sessions to show (default 10)"), option("i", "imports", null, "show import history instead of rename history")]).action(adapt(history_default)),
|