reelsort 0.2.2 → 0.2.4
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 +416 -202
- package/dist/index.d.mts +7 -10
- package/dist/index.d.ts +7 -10
- package/dist/index.js +155 -42
- package/dist/index.mjs +161 -48
- 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,31 +1553,31 @@ 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, depth = 2) => (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;
|
|
1447
1577
|
if (depth > 1) {
|
|
1448
1578
|
try {
|
|
1449
1579
|
const sub = (0, import_path14.resolve)(dir, f);
|
|
1450
|
-
if ((0,
|
|
1580
|
+
if ((0, import_fs14.lstatSync)(sub).isDirectory()) return containsBook(sub, depth - 1);
|
|
1451
1581
|
} catch {
|
|
1452
1582
|
}
|
|
1453
1583
|
}
|
|
@@ -1457,11 +1587,11 @@ var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i
|
|
|
1457
1587
|
var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
|
|
1458
1588
|
var gatherEntries = (source) => {
|
|
1459
1589
|
const result = [];
|
|
1460
|
-
for (const name of (0,
|
|
1590
|
+
for (const name of (0, import_fs14.readdirSync)(source)) {
|
|
1461
1591
|
const fullPath = (0, import_path14.resolve)(source, name);
|
|
1462
1592
|
let isDir;
|
|
1463
1593
|
try {
|
|
1464
|
-
isDir = (0,
|
|
1594
|
+
isDir = (0, import_fs14.lstatSync)(fullPath).isDirectory();
|
|
1465
1595
|
} catch {
|
|
1466
1596
|
continue;
|
|
1467
1597
|
}
|
|
@@ -1479,7 +1609,7 @@ var gatherEntries = (source) => {
|
|
|
1479
1609
|
}
|
|
1480
1610
|
let children;
|
|
1481
1611
|
try {
|
|
1482
|
-
children = (0,
|
|
1612
|
+
children = (0, import_fs14.readdirSync)(fullPath);
|
|
1483
1613
|
} catch {
|
|
1484
1614
|
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1485
1615
|
continue;
|
|
@@ -1489,7 +1619,7 @@ var gatherEntries = (source) => {
|
|
|
1489
1619
|
const childPath = (0, import_path14.resolve)(fullPath, child);
|
|
1490
1620
|
let childIsDir;
|
|
1491
1621
|
try {
|
|
1492
|
-
childIsDir = (0,
|
|
1622
|
+
childIsDir = (0, import_fs14.lstatSync)(childPath).isDirectory();
|
|
1493
1623
|
} catch {
|
|
1494
1624
|
continue;
|
|
1495
1625
|
}
|
|
@@ -1501,7 +1631,7 @@ var gatherEntries = (source) => {
|
|
|
1501
1631
|
}
|
|
1502
1632
|
const seasonDirs = children.filter((c) => {
|
|
1503
1633
|
try {
|
|
1504
|
-
return isSeasonDirName(c) && (0,
|
|
1634
|
+
return isSeasonDirName(c) && (0, import_fs14.lstatSync)((0, import_path14.resolve)(fullPath, c)).isDirectory();
|
|
1505
1635
|
} catch {
|
|
1506
1636
|
return false;
|
|
1507
1637
|
}
|
|
@@ -1511,7 +1641,7 @@ var gatherEntries = (source) => {
|
|
|
1511
1641
|
const seasonPath = (0, import_path14.resolve)(fullPath, seasonDir);
|
|
1512
1642
|
let seasonChildren;
|
|
1513
1643
|
try {
|
|
1514
|
-
seasonChildren = (0,
|
|
1644
|
+
seasonChildren = (0, import_fs14.readdirSync)(seasonPath);
|
|
1515
1645
|
} catch {
|
|
1516
1646
|
continue;
|
|
1517
1647
|
}
|
|
@@ -1519,7 +1649,7 @@ var gatherEntries = (source) => {
|
|
|
1519
1649
|
const childPath = (0, import_path14.resolve)(seasonPath, child);
|
|
1520
1650
|
let childIsDir;
|
|
1521
1651
|
try {
|
|
1522
|
-
childIsDir = (0,
|
|
1652
|
+
childIsDir = (0, import_fs14.lstatSync)(childPath).isDirectory();
|
|
1523
1653
|
} catch {
|
|
1524
1654
|
continue;
|
|
1525
1655
|
}
|
|
@@ -1534,11 +1664,52 @@ var gatherEntries = (source) => {
|
|
|
1534
1664
|
}
|
|
1535
1665
|
return result;
|
|
1536
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
|
+
};
|
|
1679
|
+
var findShowFolderByContent = (destRoot, title) => {
|
|
1680
|
+
if (!(0, import_fs14.existsSync)(destRoot)) return null;
|
|
1681
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1682
|
+
const target = normalize(title);
|
|
1683
|
+
const matchesTitle = (name) => {
|
|
1684
|
+
if (!isTvEpisodeName(name)) return false;
|
|
1685
|
+
const p = parseDownloadName(name);
|
|
1686
|
+
return !!p && normalize(p.title) === target;
|
|
1687
|
+
};
|
|
1688
|
+
for (const folder of (0, import_fs14.readdirSync)(destRoot)) {
|
|
1689
|
+
try {
|
|
1690
|
+
const folderPath = (0, import_path14.resolve)(destRoot, folder);
|
|
1691
|
+
if (!(0, import_fs14.lstatSync)(folderPath).isDirectory()) continue;
|
|
1692
|
+
const children = (0, import_fs14.readdirSync)(folderPath);
|
|
1693
|
+
if (children.some(matchesTitle)) return folder;
|
|
1694
|
+
for (const child of children) {
|
|
1695
|
+
if (!isSeasonDirName(child)) continue;
|
|
1696
|
+
try {
|
|
1697
|
+
const seasonPath = (0, import_path14.resolve)(folderPath, child);
|
|
1698
|
+
if (!(0, import_fs14.lstatSync)(seasonPath).isDirectory()) continue;
|
|
1699
|
+
if ((0, import_fs14.readdirSync)(seasonPath).some(matchesTitle)) return folder;
|
|
1700
|
+
} catch {
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
} catch {
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
return null;
|
|
1707
|
+
};
|
|
1537
1708
|
var findSeasonFolder = (showPath, season) => {
|
|
1538
|
-
if (!(0,
|
|
1539
|
-
const folders = (0,
|
|
1709
|
+
if (!(0, import_fs14.existsSync)(showPath)) return null;
|
|
1710
|
+
const folders = (0, import_fs14.readdirSync)(showPath).filter((f) => {
|
|
1540
1711
|
try {
|
|
1541
|
-
return (0,
|
|
1712
|
+
return (0, import_fs14.lstatSync)((0, import_path14.resolve)(showPath, f)).isDirectory();
|
|
1542
1713
|
} catch {
|
|
1543
1714
|
return false;
|
|
1544
1715
|
}
|
|
@@ -1557,7 +1728,15 @@ var classifyMovieConfidence = (entry) => {
|
|
|
1557
1728
|
if (/\.\d{4}\..*?(?:2160p|1080p|720p|BluRay|WEBRip|WEB-DL|HDTV)/i.test(entry)) return "auto";
|
|
1558
1729
|
return "ambiguous";
|
|
1559
1730
|
};
|
|
1560
|
-
var
|
|
1731
|
+
var typeColor = {
|
|
1732
|
+
movie: import_termkit14.Color.white.cyan,
|
|
1733
|
+
tv: import_termkit14.Color.white.green,
|
|
1734
|
+
book: import_termkit14.Color.white.yellow,
|
|
1735
|
+
ps3: import_termkit14.Color.white.magenta
|
|
1736
|
+
};
|
|
1737
|
+
var typeGlyph = (t) => typeColor[t].encoder("\u25CF");
|
|
1738
|
+
var typeTag = (t) => isVerbose() ? import_termkit14.Color.white.faint.encoder(` (${t})`) : "";
|
|
1739
|
+
var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
|
|
1561
1740
|
const config = getConfig();
|
|
1562
1741
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1563
1742
|
const language = config.language ?? "eng";
|
|
@@ -1596,73 +1775,81 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1596
1775
|
const edition = detectEdition(entry);
|
|
1597
1776
|
const folderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear, edition);
|
|
1598
1777
|
const destFolder = (0, import_path14.resolve)(destRoot, folderName);
|
|
1599
|
-
if ((0,
|
|
1778
|
+
if ((0, import_fs14.existsSync)(destFolder)) {
|
|
1600
1779
|
spinner_default.warn(`already exists: ${folderName}`);
|
|
1601
1780
|
return false;
|
|
1602
1781
|
}
|
|
1603
1782
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1604
1783
|
if (!videoFile) {
|
|
1605
|
-
if (
|
|
1784
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1606
1785
|
return false;
|
|
1607
1786
|
}
|
|
1608
1787
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
1609
1788
|
const destVideoName = `${folderName}.${videoExt}`;
|
|
1610
1789
|
const videoSourcePath = isDir ? (0, import_path14.resolve)(entryPath, videoFile) : entryPath;
|
|
1611
|
-
const dirFiles = isDir ? (0,
|
|
1790
|
+
const dirFiles = isDir ? (0, import_fs14.readdirSync)(entryPath) : [];
|
|
1612
1791
|
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1613
1792
|
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1614
1793
|
const subtitleSourcePath = subtitle ? (0, import_path14.resolve)(entryPath, subtitle) : null;
|
|
1615
1794
|
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
1616
1795
|
if (!dryRun) {
|
|
1617
1796
|
if (useHardlink) {
|
|
1618
|
-
(0,
|
|
1797
|
+
(0, import_fs14.mkdirSync)(destFolder, { recursive: true });
|
|
1619
1798
|
const destVideoPath = (0, import_path14.resolve)(destFolder, destVideoName);
|
|
1620
1799
|
let mode;
|
|
1621
1800
|
try {
|
|
1622
1801
|
if (!sameDev(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
|
|
1623
|
-
(0,
|
|
1802
|
+
(0, import_fs14.linkSync)(videoSourcePath, destVideoPath);
|
|
1624
1803
|
mode = "hardlink";
|
|
1625
1804
|
} catch {
|
|
1626
1805
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
|
|
1627
|
-
(0,
|
|
1806
|
+
(0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
|
|
1628
1807
|
mode = "copy";
|
|
1629
1808
|
}
|
|
1630
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
1809
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs14.cpSync)(subtitleSourcePath, (0, import_path14.resolve)(destFolder, destSubtitleName));
|
|
1631
1810
|
recordImport(sessionId, entryPath, destFolder, mode, tmdbId);
|
|
1632
1811
|
} else {
|
|
1633
1812
|
if (isDir) {
|
|
1634
1813
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
1635
|
-
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0,
|
|
1636
|
-
(0,
|
|
1637
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
1814
|
+
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs14.rmSync)((0, import_path14.resolve)(entryPath, f), { recursive: true, force: true });
|
|
1815
|
+
(0, import_fs14.renameSync)(videoSourcePath, (0, import_path14.resolve)(entryPath, destVideoName));
|
|
1816
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs14.renameSync)(subtitleSourcePath, (0, import_path14.resolve)(entryPath, destSubtitleName));
|
|
1638
1817
|
moveFolder(entryPath, destFolder);
|
|
1639
1818
|
} else {
|
|
1640
|
-
(0,
|
|
1819
|
+
(0, import_fs14.mkdirSync)(destFolder, { recursive: true });
|
|
1641
1820
|
const destVideoPath = (0, import_path14.resolve)(destFolder, destVideoName);
|
|
1642
1821
|
if (sameDev(videoSourcePath, destRoot)) {
|
|
1643
|
-
(0,
|
|
1822
|
+
(0, import_fs14.renameSync)(videoSourcePath, destVideoPath);
|
|
1644
1823
|
} else {
|
|
1645
|
-
(0,
|
|
1646
|
-
(0,
|
|
1824
|
+
(0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
|
|
1825
|
+
(0, import_fs14.rmSync)(videoSourcePath);
|
|
1647
1826
|
}
|
|
1648
1827
|
}
|
|
1649
1828
|
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1650
1829
|
}
|
|
1651
1830
|
}
|
|
1652
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${folderName}`);
|
|
1831
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("movie")} ${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
|
|
1653
1832
|
return true;
|
|
1654
1833
|
};
|
|
1655
1834
|
spinner_default.start();
|
|
1656
1835
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
1657
1836
|
let imported = 0, skipped = 0;
|
|
1658
1837
|
const pendingMovies = [];
|
|
1838
|
+
const pendingTv = [];
|
|
1839
|
+
const ignoreSet = new Set(config.ignore ?? []);
|
|
1840
|
+
const seenIgnored = /* @__PURE__ */ new Set();
|
|
1659
1841
|
for (const source of config.sources) {
|
|
1660
|
-
if (!(0,
|
|
1842
|
+
if (!(0, import_fs14.existsSync)(source)) {
|
|
1661
1843
|
spinner_default.warn(`source not found: ${import_termkit14.Color.white.encoder(source)}`);
|
|
1662
1844
|
continue;
|
|
1663
1845
|
}
|
|
1664
1846
|
spinner_default.text = `scanning ${import_termkit14.Color.white.encoder(source)}`;
|
|
1665
1847
|
for (const { entry, entryPath, isDir } of gatherEntries(source)) {
|
|
1848
|
+
if (ignoreSet.has(entry)) {
|
|
1849
|
+
seenIgnored.add(entry);
|
|
1850
|
+
if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
|
|
1851
|
+
continue;
|
|
1852
|
+
}
|
|
1666
1853
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
1667
1854
|
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1668
1855
|
const isBookDir = isDir && containsBook(entryPath);
|
|
@@ -1680,7 +1867,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1680
1867
|
}
|
|
1681
1868
|
const destRoot = config.dest[detectedType];
|
|
1682
1869
|
if (!destRoot) {
|
|
1683
|
-
if (
|
|
1870
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
1684
1871
|
skipped++;
|
|
1685
1872
|
continue;
|
|
1686
1873
|
}
|
|
@@ -1693,7 +1880,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1693
1880
|
}
|
|
1694
1881
|
const destName = `${nameMatch[0]} [${id}]`;
|
|
1695
1882
|
const destPath = (0, import_path14.resolve)(destRoot, destName);
|
|
1696
|
-
if ((0,
|
|
1883
|
+
if ((0, import_fs14.existsSync)(destPath)) {
|
|
1697
1884
|
spinner_default.warn(`already exists: ${destName}`);
|
|
1698
1885
|
skipped++;
|
|
1699
1886
|
continue;
|
|
@@ -1702,13 +1889,13 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1702
1889
|
moveFolder(entryPath, destPath);
|
|
1703
1890
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1704
1891
|
}
|
|
1705
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${destName}`);
|
|
1892
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("ps3")} ${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
|
|
1706
1893
|
imported++;
|
|
1707
1894
|
continue;
|
|
1708
1895
|
}
|
|
1709
1896
|
if (detectedType === "book") {
|
|
1710
1897
|
const destPath = (0, import_path14.resolve)(destRoot, entry);
|
|
1711
|
-
if ((0,
|
|
1898
|
+
if ((0, import_fs14.existsSync)(destPath)) {
|
|
1712
1899
|
spinner_default.warn(`already exists: ${entry}`);
|
|
1713
1900
|
skipped++;
|
|
1714
1901
|
continue;
|
|
@@ -1717,30 +1904,30 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1717
1904
|
if (isDir || isBookDir) {
|
|
1718
1905
|
moveFolder(entryPath, destPath);
|
|
1719
1906
|
} else {
|
|
1720
|
-
(0,
|
|
1907
|
+
(0, import_fs14.mkdirSync)(destRoot, { recursive: true });
|
|
1721
1908
|
if (sameDev(entryPath, destRoot)) {
|
|
1722
|
-
(0,
|
|
1909
|
+
(0, import_fs14.renameSync)(entryPath, destPath);
|
|
1723
1910
|
} else {
|
|
1724
|
-
(0,
|
|
1725
|
-
(0,
|
|
1911
|
+
(0, import_fs14.cpSync)(entryPath, destPath);
|
|
1912
|
+
(0, import_fs14.rmSync)(entryPath);
|
|
1726
1913
|
}
|
|
1727
1914
|
}
|
|
1728
1915
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1729
1916
|
}
|
|
1730
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${entry}`);
|
|
1917
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("book")} ${typeColor.book.encoder(entry)}${typeTag("book")}`);
|
|
1731
1918
|
imported++;
|
|
1732
1919
|
continue;
|
|
1733
1920
|
}
|
|
1734
1921
|
const parsed = parseDownloadName(entry);
|
|
1735
1922
|
if (!parsed) {
|
|
1736
|
-
if (
|
|
1923
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
1737
1924
|
skipped++;
|
|
1738
1925
|
continue;
|
|
1739
1926
|
}
|
|
1740
1927
|
if (detectedType === "movie") {
|
|
1741
1928
|
const confidence = classifyMovieConfidence(entry);
|
|
1742
1929
|
if (confidence === "skip") {
|
|
1743
|
-
if (
|
|
1930
|
+
if (isVerbose()) spinner_default.info(`skipped (uncertain): ${entry}`);
|
|
1744
1931
|
skipped++;
|
|
1745
1932
|
continue;
|
|
1746
1933
|
}
|
|
@@ -1784,7 +1971,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1784
1971
|
}
|
|
1785
1972
|
if (detectedType === "tv") {
|
|
1786
1973
|
if (parsed.season === void 0) {
|
|
1787
|
-
if (
|
|
1974
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
1788
1975
|
skipped++;
|
|
1789
1976
|
continue;
|
|
1790
1977
|
}
|
|
@@ -1794,20 +1981,25 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1794
1981
|
if (registeredShow) {
|
|
1795
1982
|
showPath = registeredShow.path;
|
|
1796
1983
|
showFolderName = showPath.split("/").pop() ?? registeredShow.path;
|
|
1797
|
-
} else if (auto) {
|
|
1798
|
-
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1799
|
-
showPath = (0, import_path14.resolve)(destRoot, showFolderName);
|
|
1800
|
-
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1801
1984
|
} else {
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1985
|
+
const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
|
|
1986
|
+
if (existingFolder) {
|
|
1987
|
+
showFolderName = existingFolder;
|
|
1988
|
+
showPath = (0, import_path14.resolve)(destRoot, existingFolder);
|
|
1989
|
+
} else if (auto) {
|
|
1990
|
+
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1991
|
+
showPath = (0, import_path14.resolve)(destRoot, showFolderName);
|
|
1992
|
+
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1993
|
+
} else {
|
|
1994
|
+
pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
|
|
1995
|
+
continue;
|
|
1996
|
+
}
|
|
1805
1997
|
}
|
|
1806
1998
|
const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1807
1999
|
const seasonPath = (0, import_path14.resolve)(showPath, seasonFolderName);
|
|
1808
2000
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1809
2001
|
if (!videoFile) {
|
|
1810
|
-
if (
|
|
2002
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1811
2003
|
skipped++;
|
|
1812
2004
|
continue;
|
|
1813
2005
|
}
|
|
@@ -1817,7 +2009,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1817
2009
|
const destVideoName = `${episodeName}.${videoExt}`;
|
|
1818
2010
|
const destVideoPath = (0, import_path14.resolve)(seasonPath, destVideoName);
|
|
1819
2011
|
const videoSourcePath = isDir ? (0, import_path14.resolve)(entryPath, videoFile) : entryPath;
|
|
1820
|
-
if ((0,
|
|
2012
|
+
if ((0, import_fs14.existsSync)(destVideoPath)) {
|
|
1821
2013
|
let shouldReplace = force;
|
|
1822
2014
|
if (!shouldReplace && interactive) {
|
|
1823
2015
|
spinner_default.stop();
|
|
@@ -1835,43 +2027,43 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1835
2027
|
continue;
|
|
1836
2028
|
}
|
|
1837
2029
|
if (!dryRun) {
|
|
1838
|
-
for (const f of (0,
|
|
1839
|
-
if (f.startsWith(`${episodeName}.`)) (0,
|
|
2030
|
+
for (const f of (0, import_fs14.readdirSync)(seasonPath)) {
|
|
2031
|
+
if (f.startsWith(`${episodeName}.`)) (0, import_fs14.rmSync)((0, import_path14.resolve)(seasonPath, f));
|
|
1840
2032
|
}
|
|
1841
2033
|
}
|
|
1842
2034
|
}
|
|
1843
|
-
const dirFiles = isDir ? (0,
|
|
2035
|
+
const dirFiles = isDir ? (0, import_fs14.readdirSync)(entryPath) : [];
|
|
1844
2036
|
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1845
2037
|
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1846
2038
|
const subtitleSourcePath = subtitle ? (0, import_path14.resolve)(entryPath, subtitle) : null;
|
|
1847
2039
|
const destSubtitleName = subtitle && subtitleExt ? `${episodeName}.${subtitleExt}` : null;
|
|
1848
2040
|
if (!dryRun) {
|
|
1849
|
-
(0,
|
|
2041
|
+
(0, import_fs14.mkdirSync)(seasonPath, { recursive: true });
|
|
1850
2042
|
let mode = "move";
|
|
1851
2043
|
if (useHardlink) {
|
|
1852
2044
|
try {
|
|
1853
2045
|
if (!sameDev(videoSourcePath, seasonPath)) throw new Error("cross-filesystem");
|
|
1854
|
-
(0,
|
|
2046
|
+
(0, import_fs14.linkSync)(videoSourcePath, destVideoPath);
|
|
1855
2047
|
mode = "hardlink";
|
|
1856
2048
|
} catch {
|
|
1857
2049
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
|
|
1858
|
-
(0,
|
|
2050
|
+
(0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
|
|
1859
2051
|
mode = "copy";
|
|
1860
2052
|
}
|
|
1861
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
2053
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs14.cpSync)(subtitleSourcePath, (0, import_path14.resolve)(seasonPath, destSubtitleName));
|
|
1862
2054
|
} else {
|
|
1863
2055
|
if (sameDev(videoSourcePath, seasonPath)) {
|
|
1864
|
-
(0,
|
|
2056
|
+
(0, import_fs14.renameSync)(videoSourcePath, destVideoPath);
|
|
1865
2057
|
} else {
|
|
1866
|
-
(0,
|
|
1867
|
-
(0,
|
|
2058
|
+
(0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
|
|
2059
|
+
(0, import_fs14.rmSync)(videoSourcePath);
|
|
1868
2060
|
}
|
|
1869
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
1870
|
-
if (isDir) (0,
|
|
2061
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs14.renameSync)(subtitleSourcePath, (0, import_path14.resolve)(seasonPath, destSubtitleName));
|
|
2062
|
+
if (isDir) (0, import_fs14.rmSync)(entryPath, { recursive: true, force: true });
|
|
1871
2063
|
}
|
|
1872
2064
|
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
1873
2065
|
}
|
|
1874
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${showFolderName} / ${seasonFolderName} / ${episodeName}`);
|
|
2066
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("tv")} ${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
|
|
1875
2067
|
imported++;
|
|
1876
2068
|
continue;
|
|
1877
2069
|
}
|
|
@@ -1884,7 +2076,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1884
2076
|
}
|
|
1885
2077
|
if (pendingMovies.length > 0) {
|
|
1886
2078
|
spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
|
|
1887
|
-
for (const p of pendingMovies) spinner_default.info(`
|
|
2079
|
+
for (const p of pendingMovies) spinner_default.info(` ${typeGlyph("movie")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
|
|
1888
2080
|
let toProcess = [];
|
|
1889
2081
|
if (interactive) {
|
|
1890
2082
|
spinner_default.stop();
|
|
@@ -1911,6 +2103,20 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1911
2103
|
}
|
|
1912
2104
|
}
|
|
1913
2105
|
}
|
|
2106
|
+
if (pendingTv.length > 0) {
|
|
2107
|
+
spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
|
|
2108
|
+
for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
2109
|
+
skipped += pendingTv.length;
|
|
2110
|
+
}
|
|
2111
|
+
if (ignoreSet.size > 0) {
|
|
2112
|
+
const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
|
|
2113
|
+
if (stale.length > 0 && !dryRun) {
|
|
2114
|
+
const updated = config.ignore.filter((name) => !stale.includes(name));
|
|
2115
|
+
config.ignore = updated;
|
|
2116
|
+
saveConfig(config);
|
|
2117
|
+
for (const name of stale) spinner_default.info(`removed from ignore list (not found): ${import_termkit14.Color.white.encoder(name)}`);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
1914
2120
|
spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
|
|
1915
2121
|
if (skipped) spinner_default.info(`skipped ${skipped} items`);
|
|
1916
2122
|
spinner_default.stop();
|
|
@@ -1918,7 +2124,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1918
2124
|
var scan_default = scan;
|
|
1919
2125
|
|
|
1920
2126
|
// src/actions/shows.ts
|
|
1921
|
-
var
|
|
2127
|
+
var import_fs15 = require("fs");
|
|
1922
2128
|
var import_termkit15 = require("termkit");
|
|
1923
2129
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
1924
2130
|
var shows = async () => {
|
|
@@ -1935,7 +2141,7 @@ ${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termki
|
|
|
1935
2141
|
new import_termkit15.Table(
|
|
1936
2142
|
allShows.map((show) => ({
|
|
1937
2143
|
name: show.path.split("/").pop() ?? show.path,
|
|
1938
|
-
size: (0,
|
|
2144
|
+
size: (0, import_fs15.existsSync)(show.path) ? formatSize(dirSize(show.path)) : "\u2014",
|
|
1939
2145
|
tmdbId: show.tmdbId,
|
|
1940
2146
|
ended: show.ended
|
|
1941
2147
|
})),
|
|
@@ -1968,13 +2174,13 @@ ${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termki
|
|
|
1968
2174
|
var shows_default = shows;
|
|
1969
2175
|
|
|
1970
2176
|
// src/actions/stats.ts
|
|
1971
|
-
var
|
|
2177
|
+
var import_fs16 = require("fs");
|
|
1972
2178
|
var import_path15 = require("path");
|
|
1973
2179
|
var import_termkit16 = require("termkit");
|
|
1974
2180
|
var countVideos = (dir) => {
|
|
1975
2181
|
let count = 0;
|
|
1976
2182
|
try {
|
|
1977
|
-
for (const entry of (0,
|
|
2183
|
+
for (const entry of (0, import_fs16.readdirSync)(dir, { withFileTypes: true })) {
|
|
1978
2184
|
if (entry.isDirectory()) {
|
|
1979
2185
|
count += countVideos((0, import_path15.resolve)(dir, entry.name));
|
|
1980
2186
|
} else {
|
|
@@ -1988,9 +2194,9 @@ var countVideos = (dir) => {
|
|
|
1988
2194
|
};
|
|
1989
2195
|
var countDirs = (dir) => {
|
|
1990
2196
|
try {
|
|
1991
|
-
return (0,
|
|
2197
|
+
return (0, import_fs16.readdirSync)(dir).filter((f) => {
|
|
1992
2198
|
try {
|
|
1993
|
-
return (0,
|
|
2199
|
+
return (0, import_fs16.lstatSync)((0, import_path15.resolve)(dir, f)).isDirectory();
|
|
1994
2200
|
} catch {
|
|
1995
2201
|
return false;
|
|
1996
2202
|
}
|
|
@@ -2004,16 +2210,16 @@ var stats = async () => {
|
|
|
2004
2210
|
const shows2 = getShows();
|
|
2005
2211
|
const rows = [];
|
|
2006
2212
|
const movieDest = config.dest.movie;
|
|
2007
|
-
if (movieDest && (0,
|
|
2213
|
+
if (movieDest && (0, import_fs16.existsSync)(movieDest)) {
|
|
2008
2214
|
rows.push({ category: "Movies", count: countDirs(movieDest), size: formatSize(dirSize(movieDest)) });
|
|
2009
2215
|
}
|
|
2010
2216
|
const tvDest = config.dest.tv;
|
|
2011
|
-
if (tvDest && (0,
|
|
2217
|
+
if (tvDest && (0, import_fs16.existsSync)(tvDest)) {
|
|
2012
2218
|
rows.push({ category: "Shows", count: shows2.length, size: formatSize(dirSize(tvDest)) });
|
|
2013
2219
|
rows.push({ category: "Episodes", count: countVideos(tvDest) });
|
|
2014
2220
|
}
|
|
2015
2221
|
const ps3Dest = config.dest.ps3;
|
|
2016
|
-
if (ps3Dest && (0,
|
|
2222
|
+
if (ps3Dest && (0, import_fs16.existsSync)(ps3Dest)) {
|
|
2017
2223
|
rows.push({ category: "PS3", count: countDirs(ps3Dest), size: formatSize(dirSize(ps3Dest)) });
|
|
2018
2224
|
}
|
|
2019
2225
|
if (rows.length === 0) return;
|
|
@@ -2032,7 +2238,7 @@ var stats = async () => {
|
|
|
2032
2238
|
var stats_default = stats;
|
|
2033
2239
|
|
|
2034
2240
|
// src/actions/undo.ts
|
|
2035
|
-
var
|
|
2241
|
+
var import_fs17 = require("fs");
|
|
2036
2242
|
var import_termkit17 = require("termkit");
|
|
2037
2243
|
var undo = async () => {
|
|
2038
2244
|
spinner_default.start();
|
|
@@ -2044,7 +2250,7 @@ var undo = async () => {
|
|
|
2044
2250
|
}
|
|
2045
2251
|
let undone = 0;
|
|
2046
2252
|
for (const record of records) {
|
|
2047
|
-
(0,
|
|
2253
|
+
(0, import_fs17.renameSync)(record.newPath, record.oldPath);
|
|
2048
2254
|
spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.newPath)} \u2192 ${import_termkit17.Color.white.encoder(record.oldPath)}`);
|
|
2049
2255
|
undone++;
|
|
2050
2256
|
}
|
|
@@ -2056,37 +2262,37 @@ var undo_default = undo;
|
|
|
2056
2262
|
|
|
2057
2263
|
// src/actions/watch.ts
|
|
2058
2264
|
var import_chokidar = __toESM(require("chokidar"));
|
|
2059
|
-
var
|
|
2265
|
+
var import_fs18 = require("fs");
|
|
2060
2266
|
var import_path16 = require("path");
|
|
2061
2267
|
var import_termkit18 = require("termkit");
|
|
2062
2268
|
var sameDev2 = (a, b) => {
|
|
2063
2269
|
try {
|
|
2064
2270
|
let bExisting = b;
|
|
2065
|
-
while (!(0,
|
|
2066
|
-
return (0,
|
|
2271
|
+
while (!(0, import_fs18.existsSync)(bExisting)) bExisting = (0, import_path16.dirname)(bExisting);
|
|
2272
|
+
return (0, import_fs18.statSync)(a).dev === (0, import_fs18.statSync)(bExisting).dev;
|
|
2067
2273
|
} catch {
|
|
2068
2274
|
return false;
|
|
2069
2275
|
}
|
|
2070
2276
|
};
|
|
2071
2277
|
var moveItem = (src, dest) => {
|
|
2072
2278
|
if (sameDev2(src, dest)) {
|
|
2073
|
-
(0,
|
|
2279
|
+
(0, import_fs18.renameSync)(src, dest);
|
|
2074
2280
|
} else {
|
|
2075
|
-
(0,
|
|
2076
|
-
(0,
|
|
2281
|
+
(0, import_fs18.cpSync)(src, dest, { recursive: true });
|
|
2282
|
+
(0, import_fs18.rmSync)(src, { recursive: true, force: true });
|
|
2077
2283
|
}
|
|
2078
2284
|
};
|
|
2079
|
-
var findVideo2 = (dir) => (0,
|
|
2285
|
+
var findVideo2 = (dir) => (0, import_fs18.readdirSync)(dir).find((f) => {
|
|
2080
2286
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
2081
2287
|
return ext && videoExtensions_default.includes(ext);
|
|
2082
2288
|
}) ?? null;
|
|
2083
|
-
var containsBook2 = (dir, depth = 2) => (0,
|
|
2289
|
+
var containsBook2 = (dir, depth = 2) => (0, import_fs18.readdirSync)(dir).some((f) => {
|
|
2084
2290
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
2085
2291
|
if (ext && bookExtensions_default.includes(ext)) return true;
|
|
2086
2292
|
if (depth > 1) {
|
|
2087
2293
|
try {
|
|
2088
2294
|
const sub = (0, import_path16.resolve)(dir, f);
|
|
2089
|
-
if ((0,
|
|
2295
|
+
if ((0, import_fs18.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
|
|
2090
2296
|
} catch {
|
|
2091
2297
|
}
|
|
2092
2298
|
}
|
|
@@ -2097,7 +2303,7 @@ var isSeasonDirName2 = (name) => !isTvEpisodeName2(name) && /(?:^|[.\s_-])(?:sea
|
|
|
2097
2303
|
var expandWatchPath = (p) => {
|
|
2098
2304
|
let isDir;
|
|
2099
2305
|
try {
|
|
2100
|
-
isDir = (0,
|
|
2306
|
+
isDir = (0, import_fs18.lstatSync)(p).isDirectory();
|
|
2101
2307
|
} catch {
|
|
2102
2308
|
return [p];
|
|
2103
2309
|
}
|
|
@@ -2106,7 +2312,7 @@ var expandWatchPath = (p) => {
|
|
|
2106
2312
|
if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
|
|
2107
2313
|
let children;
|
|
2108
2314
|
try {
|
|
2109
|
-
children = (0,
|
|
2315
|
+
children = (0, import_fs18.readdirSync)(p);
|
|
2110
2316
|
} catch {
|
|
2111
2317
|
return [p];
|
|
2112
2318
|
}
|
|
@@ -2116,7 +2322,7 @@ var expandWatchPath = (p) => {
|
|
|
2116
2322
|
const cp = (0, import_path16.resolve)(p, child);
|
|
2117
2323
|
let cd;
|
|
2118
2324
|
try {
|
|
2119
|
-
cd = (0,
|
|
2325
|
+
cd = (0, import_fs18.lstatSync)(cp).isDirectory();
|
|
2120
2326
|
} catch {
|
|
2121
2327
|
continue;
|
|
2122
2328
|
}
|
|
@@ -2128,7 +2334,7 @@ var expandWatchPath = (p) => {
|
|
|
2128
2334
|
}
|
|
2129
2335
|
const seasonDirs = children.filter((c) => {
|
|
2130
2336
|
try {
|
|
2131
|
-
return isSeasonDirName2(c) && (0,
|
|
2337
|
+
return isSeasonDirName2(c) && (0, import_fs18.lstatSync)((0, import_path16.resolve)(p, c)).isDirectory();
|
|
2132
2338
|
} catch {
|
|
2133
2339
|
return false;
|
|
2134
2340
|
}
|
|
@@ -2139,7 +2345,7 @@ var expandWatchPath = (p) => {
|
|
|
2139
2345
|
const sp = (0, import_path16.resolve)(p, sd);
|
|
2140
2346
|
let sc;
|
|
2141
2347
|
try {
|
|
2142
|
-
sc = (0,
|
|
2348
|
+
sc = (0, import_fs18.readdirSync)(sp);
|
|
2143
2349
|
} catch {
|
|
2144
2350
|
continue;
|
|
2145
2351
|
}
|
|
@@ -2147,7 +2353,7 @@ var expandWatchPath = (p) => {
|
|
|
2147
2353
|
const cp = (0, import_path16.resolve)(sp, child);
|
|
2148
2354
|
let cd;
|
|
2149
2355
|
try {
|
|
2150
|
-
cd = (0,
|
|
2356
|
+
cd = (0, import_fs18.lstatSync)(cp).isDirectory();
|
|
2151
2357
|
} catch {
|
|
2152
2358
|
continue;
|
|
2153
2359
|
}
|
|
@@ -2161,10 +2367,10 @@ var expandWatchPath = (p) => {
|
|
|
2161
2367
|
return [p];
|
|
2162
2368
|
};
|
|
2163
2369
|
var findSeasonFolder2 = (showPath, season) => {
|
|
2164
|
-
if (!(0,
|
|
2165
|
-
const folders = (0,
|
|
2370
|
+
if (!(0, import_fs18.existsSync)(showPath)) return null;
|
|
2371
|
+
const folders = (0, import_fs18.readdirSync)(showPath).filter((f) => {
|
|
2166
2372
|
try {
|
|
2167
|
-
return (0,
|
|
2373
|
+
return (0, import_fs18.lstatSync)((0, import_path16.resolve)(showPath, f)).isDirectory();
|
|
2168
2374
|
} catch {
|
|
2169
2375
|
return false;
|
|
2170
2376
|
}
|
|
@@ -2174,13 +2380,13 @@ var findSeasonFolder2 = (showPath, season) => {
|
|
|
2174
2380
|
return match && parseInt(match[1]) === season;
|
|
2175
2381
|
}) ?? null;
|
|
2176
2382
|
};
|
|
2177
|
-
var processItem = async (entryPath, useHardlink,
|
|
2383
|
+
var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
2178
2384
|
const config = getConfig();
|
|
2179
2385
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
2180
2386
|
const entry = (0, import_path16.basename)(entryPath);
|
|
2181
2387
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
2182
2388
|
const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
|
|
2183
|
-
const isDir = (0,
|
|
2389
|
+
const isDir = (0, import_fs18.lstatSync)(entryPath).isDirectory();
|
|
2184
2390
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
2185
2391
|
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
2186
2392
|
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
@@ -2198,7 +2404,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2198
2404
|
}
|
|
2199
2405
|
const destRoot = config.dest[detectedType];
|
|
2200
2406
|
if (!destRoot) {
|
|
2201
|
-
if (
|
|
2407
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
2202
2408
|
return;
|
|
2203
2409
|
}
|
|
2204
2410
|
if (detectedType === "ps3") {
|
|
@@ -2207,7 +2413,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2207
2413
|
if (!nameMatch || !id) return;
|
|
2208
2414
|
const destName = `${nameMatch[0]} [${id}]`;
|
|
2209
2415
|
const destPath = (0, import_path16.resolve)(destRoot, destName);
|
|
2210
|
-
if ((0,
|
|
2416
|
+
if ((0, import_fs18.existsSync)(destPath)) {
|
|
2211
2417
|
spinner_default.warn(`already exists: ${destName}`);
|
|
2212
2418
|
return;
|
|
2213
2419
|
}
|
|
@@ -2218,19 +2424,19 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2218
2424
|
}
|
|
2219
2425
|
if (detectedType === "book") {
|
|
2220
2426
|
const destPath = (0, import_path16.resolve)(destRoot, entry);
|
|
2221
|
-
if ((0,
|
|
2427
|
+
if ((0, import_fs18.existsSync)(destPath)) {
|
|
2222
2428
|
spinner_default.warn(`already exists: ${entry}`);
|
|
2223
2429
|
return;
|
|
2224
2430
|
}
|
|
2225
2431
|
if (isDir || isBookDir) {
|
|
2226
2432
|
moveItem(entryPath, destPath);
|
|
2227
2433
|
} else {
|
|
2228
|
-
(0,
|
|
2434
|
+
(0, import_fs18.mkdirSync)(destRoot, { recursive: true });
|
|
2229
2435
|
if (sameDev2(entryPath, destRoot)) {
|
|
2230
|
-
(0,
|
|
2436
|
+
(0, import_fs18.renameSync)(entryPath, destPath);
|
|
2231
2437
|
} else {
|
|
2232
|
-
(0,
|
|
2233
|
-
(0,
|
|
2438
|
+
(0, import_fs18.cpSync)(entryPath, destPath);
|
|
2439
|
+
(0, import_fs18.rmSync)(entryPath);
|
|
2234
2440
|
}
|
|
2235
2441
|
}
|
|
2236
2442
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
@@ -2239,12 +2445,12 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2239
2445
|
}
|
|
2240
2446
|
const parsed = parseDownloadName(entry);
|
|
2241
2447
|
if (!parsed) {
|
|
2242
|
-
if (
|
|
2448
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
2243
2449
|
return;
|
|
2244
2450
|
}
|
|
2245
2451
|
if (detectedType === "tv") {
|
|
2246
2452
|
if (parsed.season === void 0) {
|
|
2247
|
-
if (
|
|
2453
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
2248
2454
|
return;
|
|
2249
2455
|
}
|
|
2250
2456
|
const registeredShow = getShowByTitle(parsed.title);
|
|
@@ -2258,14 +2464,14 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2258
2464
|
showPath = (0, import_path16.resolve)(destRoot, showFolderName);
|
|
2259
2465
|
upsertShow(showPath, null, parsed.title);
|
|
2260
2466
|
} else {
|
|
2261
|
-
if (
|
|
2467
|
+
if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
|
|
2262
2468
|
return;
|
|
2263
2469
|
}
|
|
2264
2470
|
const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
2265
2471
|
const seasonPath = (0, import_path16.resolve)(showPath, seasonFolderName);
|
|
2266
2472
|
const videoFile2 = isDir ? findVideo2(entryPath) : entry;
|
|
2267
2473
|
if (!videoFile2) {
|
|
2268
|
-
if (
|
|
2474
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
2269
2475
|
return;
|
|
2270
2476
|
}
|
|
2271
2477
|
const videoExt2 = videoFile2.match(/([^.]+$)/)?.[0];
|
|
@@ -2274,37 +2480,37 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2274
2480
|
const destVideoName2 = `${episodeName}.${videoExt2}`;
|
|
2275
2481
|
const destVideoPath = (0, import_path16.resolve)(seasonPath, destVideoName2);
|
|
2276
2482
|
const videoSourcePath2 = isDir ? (0, import_path16.resolve)(entryPath, videoFile2) : entryPath;
|
|
2277
|
-
if ((0,
|
|
2483
|
+
if ((0, import_fs18.existsSync)(destVideoPath)) {
|
|
2278
2484
|
spinner_default.warn(`already exists: ${episodeName}`);
|
|
2279
2485
|
return;
|
|
2280
2486
|
}
|
|
2281
|
-
const dirFiles2 = isDir ? (0,
|
|
2487
|
+
const dirFiles2 = isDir ? (0, import_fs18.readdirSync)(entryPath) : [];
|
|
2282
2488
|
const subtitle2 = isDir ? findSubtitle(dirFiles2, language) : null;
|
|
2283
2489
|
const subtitleExt2 = subtitle2?.match(/([^.]+$)/)?.[0];
|
|
2284
2490
|
const subtitleSourcePath2 = subtitle2 ? (0, import_path16.resolve)(entryPath, subtitle2) : null;
|
|
2285
2491
|
const destSubtitleName2 = subtitle2 && subtitleExt2 ? `${episodeName}.${subtitleExt2}` : null;
|
|
2286
|
-
(0,
|
|
2492
|
+
(0, import_fs18.mkdirSync)(seasonPath, { recursive: true });
|
|
2287
2493
|
let mode = "move";
|
|
2288
2494
|
if (useHardlink) {
|
|
2289
2495
|
try {
|
|
2290
2496
|
if (!sameDev2(videoSourcePath2, seasonPath)) throw new Error("cross-filesystem");
|
|
2291
|
-
(0,
|
|
2497
|
+
(0, import_fs18.linkSync)(videoSourcePath2, destVideoPath);
|
|
2292
2498
|
mode = "hardlink";
|
|
2293
2499
|
} catch {
|
|
2294
2500
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
|
|
2295
|
-
(0,
|
|
2501
|
+
(0, import_fs18.cpSync)(videoSourcePath2, destVideoPath);
|
|
2296
2502
|
mode = "copy";
|
|
2297
2503
|
}
|
|
2298
|
-
if (subtitleSourcePath2 && destSubtitleName2) (0,
|
|
2504
|
+
if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs18.cpSync)(subtitleSourcePath2, (0, import_path16.resolve)(seasonPath, destSubtitleName2));
|
|
2299
2505
|
} else {
|
|
2300
2506
|
if (sameDev2(videoSourcePath2, seasonPath)) {
|
|
2301
|
-
(0,
|
|
2507
|
+
(0, import_fs18.renameSync)(videoSourcePath2, destVideoPath);
|
|
2302
2508
|
} else {
|
|
2303
|
-
(0,
|
|
2304
|
-
(0,
|
|
2509
|
+
(0, import_fs18.cpSync)(videoSourcePath2, destVideoPath);
|
|
2510
|
+
(0, import_fs18.rmSync)(videoSourcePath2);
|
|
2305
2511
|
}
|
|
2306
|
-
if (subtitleSourcePath2 && destSubtitleName2) (0,
|
|
2307
|
-
if (isDir) (0,
|
|
2512
|
+
if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs18.renameSync)(subtitleSourcePath2, (0, import_path16.resolve)(seasonPath, destSubtitleName2));
|
|
2513
|
+
if (isDir) (0, import_fs18.rmSync)(entryPath, { recursive: true, force: true });
|
|
2308
2514
|
}
|
|
2309
2515
|
recordImport(sessionId, entryPath, seasonPath, mode);
|
|
2310
2516
|
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
|
|
@@ -2313,60 +2519,60 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2313
2519
|
const edition = detectEdition(entry);
|
|
2314
2520
|
const folderName = formatMovieName(movieFormat, parsed.title, parsed.year, edition);
|
|
2315
2521
|
const destFolder = (0, import_path16.resolve)(destRoot, folderName);
|
|
2316
|
-
if ((0,
|
|
2522
|
+
if ((0, import_fs18.existsSync)(destFolder)) {
|
|
2317
2523
|
spinner_default.warn(`already exists: ${folderName}`);
|
|
2318
2524
|
return;
|
|
2319
2525
|
}
|
|
2320
2526
|
const videoFile = isDir ? findVideo2(entryPath) : entry;
|
|
2321
2527
|
if (!videoFile) {
|
|
2322
|
-
if (
|
|
2528
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
2323
2529
|
return;
|
|
2324
2530
|
}
|
|
2325
2531
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
2326
2532
|
const destVideoName = `${folderName}.${videoExt}`;
|
|
2327
2533
|
const videoSourcePath = isDir ? (0, import_path16.resolve)(entryPath, videoFile) : entryPath;
|
|
2328
|
-
const dirFiles = isDir ? (0,
|
|
2534
|
+
const dirFiles = isDir ? (0, import_fs18.readdirSync)(entryPath) : [];
|
|
2329
2535
|
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
2330
2536
|
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
2331
2537
|
const subtitleSourcePath = subtitle ? (0, import_path16.resolve)(entryPath, subtitle) : null;
|
|
2332
2538
|
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
2333
2539
|
if (useHardlink) {
|
|
2334
|
-
(0,
|
|
2540
|
+
(0, import_fs18.mkdirSync)(destFolder, { recursive: true });
|
|
2335
2541
|
const destVideoPath = (0, import_path16.resolve)(destFolder, destVideoName);
|
|
2336
2542
|
let mode;
|
|
2337
2543
|
try {
|
|
2338
2544
|
if (!sameDev2(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
|
|
2339
|
-
(0,
|
|
2545
|
+
(0, import_fs18.linkSync)(videoSourcePath, destVideoPath);
|
|
2340
2546
|
mode = "hardlink";
|
|
2341
2547
|
} catch {
|
|
2342
2548
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
|
|
2343
|
-
(0,
|
|
2549
|
+
(0, import_fs18.cpSync)(videoSourcePath, destVideoPath);
|
|
2344
2550
|
mode = "copy";
|
|
2345
2551
|
}
|
|
2346
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
2552
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs18.cpSync)(subtitleSourcePath, (0, import_path16.resolve)(destFolder, destSubtitleName));
|
|
2347
2553
|
recordImport(sessionId, entryPath, destFolder, mode);
|
|
2348
2554
|
} else {
|
|
2349
2555
|
if (isDir) {
|
|
2350
2556
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
2351
|
-
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0,
|
|
2352
|
-
(0,
|
|
2353
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
2557
|
+
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs18.rmSync)((0, import_path16.resolve)(entryPath, f), { recursive: true, force: true });
|
|
2558
|
+
(0, import_fs18.renameSync)(videoSourcePath, (0, import_path16.resolve)(entryPath, destVideoName));
|
|
2559
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs18.renameSync)(subtitleSourcePath, (0, import_path16.resolve)(entryPath, destSubtitleName));
|
|
2354
2560
|
moveItem(entryPath, destFolder);
|
|
2355
2561
|
} else {
|
|
2356
|
-
(0,
|
|
2562
|
+
(0, import_fs18.mkdirSync)(destFolder, { recursive: true });
|
|
2357
2563
|
const destVideoPath = (0, import_path16.resolve)(destFolder, destVideoName);
|
|
2358
2564
|
if (sameDev2(videoSourcePath, destRoot)) {
|
|
2359
|
-
(0,
|
|
2565
|
+
(0, import_fs18.renameSync)(videoSourcePath, destVideoPath);
|
|
2360
2566
|
} else {
|
|
2361
|
-
(0,
|
|
2362
|
-
(0,
|
|
2567
|
+
(0, import_fs18.cpSync)(videoSourcePath, destVideoPath);
|
|
2568
|
+
(0, import_fs18.rmSync)(videoSourcePath);
|
|
2363
2569
|
}
|
|
2364
2570
|
}
|
|
2365
2571
|
recordImport(sessionId, entryPath, destFolder, "move");
|
|
2366
2572
|
}
|
|
2367
2573
|
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(folderName)}`);
|
|
2368
2574
|
};
|
|
2369
|
-
var watch = async ({ hardlink = false,
|
|
2575
|
+
var watch = async ({ hardlink = false, auto = false }) => {
|
|
2370
2576
|
const config = getConfig();
|
|
2371
2577
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
2372
2578
|
const language = config.language ?? "eng";
|
|
@@ -2380,7 +2586,7 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
2380
2586
|
pending.delete(path);
|
|
2381
2587
|
try {
|
|
2382
2588
|
for (const entry of expandWatchPath(path)) {
|
|
2383
|
-
await processItem(entry, hardlink,
|
|
2589
|
+
await processItem(entry, hardlink, language, auto);
|
|
2384
2590
|
}
|
|
2385
2591
|
} catch (err) {
|
|
2386
2592
|
spinner_default.fail(`error processing ${path}: ${err.message}`);
|
|
@@ -2404,19 +2610,20 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
2404
2610
|
var watch_default = watch;
|
|
2405
2611
|
|
|
2406
2612
|
// package.json
|
|
2407
|
-
var version = "0.2.
|
|
2613
|
+
var version = "0.2.4";
|
|
2408
2614
|
|
|
2409
2615
|
// src/program.ts
|
|
2410
2616
|
var toCamel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
2411
2617
|
var adapt = (fn) => (options) => {
|
|
2412
2618
|
const camel = Object.fromEntries(Object.entries(options).map(([k, v]) => [toCamel(k), v]));
|
|
2619
|
+
setVerbose(!!camel.verbose);
|
|
2413
2620
|
return fn(camel);
|
|
2414
2621
|
};
|
|
2415
2622
|
var { command, option } = import_termkit19.Program;
|
|
2416
2623
|
var program = import_termkit19.Program.command("reelsort").version(version).description("a cli to manage media").commands([
|
|
2417
2624
|
command("config").description("manage configuration").commands([
|
|
2418
|
-
command("source").description("manage source directories").commands([command("add", "<dir>").description("add a source directory").action(adapt(sourceAdd)), command("remove", "
|
|
2419
|
-
command("dest").description("manage destinations").commands([command("add", "<type> <dir>").description("set a destination (movie, tv, ps3, book)").action(adapt(destAdd)), command("remove", "
|
|
2625
|
+
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))]),
|
|
2626
|
+
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))]),
|
|
2420
2627
|
command("set", "<key> <subkey> [value]").description("set a value (e.g. set language eng)").action(adapt(configSet)),
|
|
2421
2628
|
command("show").description("show current configuration").action(adapt(configShow))
|
|
2422
2629
|
]),
|
|
@@ -2426,6 +2633,7 @@ var program = import_termkit19.Program.command("reelsort").version(version).desc
|
|
|
2426
2633
|
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)),
|
|
2427
2634
|
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)),
|
|
2428
2635
|
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)),
|
|
2636
|
+
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))]),
|
|
2429
2637
|
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)),
|
|
2430
2638
|
command("undo").description("undo the last rename session").action(adapt(undo_default)),
|
|
2431
2639
|
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)),
|
|
@@ -2440,6 +2648,12 @@ var program = import_termkit19.Program.command("reelsort").version(version).desc
|
|
|
2440
2648
|
var program_default = program;
|
|
2441
2649
|
|
|
2442
2650
|
// src/cli.ts
|
|
2651
|
+
if (!process.stdout.isTTY) {
|
|
2652
|
+
const { FORCE_COLOR, MSYSTEM, WT_SESSION, TERM } = process.env;
|
|
2653
|
+
if (FORCE_COLOR || MSYSTEM || WT_SESSION || TERM?.startsWith("xterm")) {
|
|
2654
|
+
Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true });
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2443
2657
|
var run = async (args) => {
|
|
2444
2658
|
try {
|
|
2445
2659
|
await program_default.parse(args);
|