reelsort 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +380 -202
- package/dist/index.d.mts +7 -10
- package/dist/index.d.ts +7 -10
- package/dist/index.js +125 -42
- package/dist/index.mjs +131 -48
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -9,14 +9,14 @@ interface SourceAddOptions {
|
|
|
9
9
|
dir: string;
|
|
10
10
|
}
|
|
11
11
|
interface SourceRemoveOptions {
|
|
12
|
-
dir
|
|
12
|
+
dir?: string;
|
|
13
13
|
}
|
|
14
14
|
interface DestAddOptions {
|
|
15
15
|
type: string;
|
|
16
16
|
dir: string;
|
|
17
17
|
}
|
|
18
18
|
interface DestRemoveOptions {
|
|
19
|
-
type
|
|
19
|
+
type?: string;
|
|
20
20
|
}
|
|
21
21
|
interface ConfigSetOptions {
|
|
22
22
|
key: string;
|
|
@@ -59,16 +59,14 @@ declare const list: ({ type, missingSubs, codec: codecFilter, resolution: resFil
|
|
|
59
59
|
interface ProbeOptions {
|
|
60
60
|
type?: 'movie' | 'tv' | 'ps3';
|
|
61
61
|
force?: boolean;
|
|
62
|
-
verbose?: boolean;
|
|
63
62
|
}
|
|
64
|
-
declare const probe: ({ type, force
|
|
63
|
+
declare const probe: ({ type, force }: ProbeOptions) => Promise<void>;
|
|
65
64
|
|
|
66
65
|
interface RenameOptions {
|
|
67
66
|
dir: string;
|
|
68
67
|
type?: 'movie' | 'tv' | 'ps3';
|
|
69
|
-
verbose?: boolean;
|
|
70
68
|
}
|
|
71
|
-
declare const rename: ({ dir: inputDir, type
|
|
69
|
+
declare const rename: ({ dir: inputDir, type }: RenameOptions) => Promise<void>;
|
|
72
70
|
|
|
73
71
|
interface ResetOptions {
|
|
74
72
|
dir: string;
|
|
@@ -80,21 +78,19 @@ interface ScanOptions {
|
|
|
80
78
|
type?: 'movie' | 'tv' | 'ps3' | 'book';
|
|
81
79
|
hardlink?: boolean;
|
|
82
80
|
dryRun?: boolean;
|
|
83
|
-
verbose?: boolean;
|
|
84
81
|
auto?: boolean;
|
|
85
82
|
force?: boolean;
|
|
86
83
|
interactive?: boolean;
|
|
87
84
|
}
|
|
88
|
-
declare const scan: ({ type, hardlink: useHardlink, dryRun,
|
|
85
|
+
declare const scan: ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }: ScanOptions) => Promise<void>;
|
|
89
86
|
|
|
90
87
|
declare const undo: () => Promise<void>;
|
|
91
88
|
|
|
92
89
|
interface WatchOptions {
|
|
93
90
|
hardlink?: boolean;
|
|
94
|
-
verbose?: boolean;
|
|
95
91
|
auto?: boolean;
|
|
96
92
|
}
|
|
97
|
-
declare const watch: ({ hardlink,
|
|
93
|
+
declare const watch: ({ hardlink, auto }: WatchOptions) => Promise<void>;
|
|
98
94
|
|
|
99
95
|
interface ReelSortConfig {
|
|
100
96
|
sources: string[];
|
|
@@ -104,6 +100,7 @@ interface ReelSortConfig {
|
|
|
104
100
|
ps3?: string;
|
|
105
101
|
book?: string;
|
|
106
102
|
};
|
|
103
|
+
ignore?: string[];
|
|
107
104
|
language?: string;
|
|
108
105
|
tmdbApiKey?: string;
|
|
109
106
|
format?: {
|
package/dist/index.d.ts
CHANGED
|
@@ -9,14 +9,14 @@ interface SourceAddOptions {
|
|
|
9
9
|
dir: string;
|
|
10
10
|
}
|
|
11
11
|
interface SourceRemoveOptions {
|
|
12
|
-
dir
|
|
12
|
+
dir?: string;
|
|
13
13
|
}
|
|
14
14
|
interface DestAddOptions {
|
|
15
15
|
type: string;
|
|
16
16
|
dir: string;
|
|
17
17
|
}
|
|
18
18
|
interface DestRemoveOptions {
|
|
19
|
-
type
|
|
19
|
+
type?: string;
|
|
20
20
|
}
|
|
21
21
|
interface ConfigSetOptions {
|
|
22
22
|
key: string;
|
|
@@ -59,16 +59,14 @@ declare const list: ({ type, missingSubs, codec: codecFilter, resolution: resFil
|
|
|
59
59
|
interface ProbeOptions {
|
|
60
60
|
type?: 'movie' | 'tv' | 'ps3';
|
|
61
61
|
force?: boolean;
|
|
62
|
-
verbose?: boolean;
|
|
63
62
|
}
|
|
64
|
-
declare const probe: ({ type, force
|
|
63
|
+
declare const probe: ({ type, force }: ProbeOptions) => Promise<void>;
|
|
65
64
|
|
|
66
65
|
interface RenameOptions {
|
|
67
66
|
dir: string;
|
|
68
67
|
type?: 'movie' | 'tv' | 'ps3';
|
|
69
|
-
verbose?: boolean;
|
|
70
68
|
}
|
|
71
|
-
declare const rename: ({ dir: inputDir, type
|
|
69
|
+
declare const rename: ({ dir: inputDir, type }: RenameOptions) => Promise<void>;
|
|
72
70
|
|
|
73
71
|
interface ResetOptions {
|
|
74
72
|
dir: string;
|
|
@@ -80,21 +78,19 @@ interface ScanOptions {
|
|
|
80
78
|
type?: 'movie' | 'tv' | 'ps3' | 'book';
|
|
81
79
|
hardlink?: boolean;
|
|
82
80
|
dryRun?: boolean;
|
|
83
|
-
verbose?: boolean;
|
|
84
81
|
auto?: boolean;
|
|
85
82
|
force?: boolean;
|
|
86
83
|
interactive?: boolean;
|
|
87
84
|
}
|
|
88
|
-
declare const scan: ({ type, hardlink: useHardlink, dryRun,
|
|
85
|
+
declare const scan: ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }: ScanOptions) => Promise<void>;
|
|
89
86
|
|
|
90
87
|
declare const undo: () => Promise<void>;
|
|
91
88
|
|
|
92
89
|
interface WatchOptions {
|
|
93
90
|
hardlink?: boolean;
|
|
94
|
-
verbose?: boolean;
|
|
95
91
|
auto?: boolean;
|
|
96
92
|
}
|
|
97
|
-
declare const watch: ({ hardlink,
|
|
93
|
+
declare const watch: ({ hardlink, auto }: WatchOptions) => Promise<void>;
|
|
98
94
|
|
|
99
95
|
interface ReelSortConfig {
|
|
100
96
|
sources: string[];
|
|
@@ -104,6 +100,7 @@ interface ReelSortConfig {
|
|
|
104
100
|
ps3?: string;
|
|
105
101
|
book?: string;
|
|
106
102
|
};
|
|
103
|
+
ignore?: string[];
|
|
107
104
|
language?: string;
|
|
108
105
|
tmdbApiKey?: string;
|
|
109
106
|
format?: {
|
package/dist/index.js
CHANGED
|
@@ -366,8 +366,20 @@ var sourceAdd = async ({ dir }) => {
|
|
|
366
366
|
spinner_default.stop();
|
|
367
367
|
};
|
|
368
368
|
var sourceRemove = async ({ dir }) => {
|
|
369
|
-
const resolved = (0, import_path3.resolve)(dir);
|
|
370
369
|
const config = getConfig();
|
|
370
|
+
if (!dir) {
|
|
371
|
+
if (config.sources.length === 0) {
|
|
372
|
+
spinner_default.start();
|
|
373
|
+
spinner_default.warn("no sources configured");
|
|
374
|
+
spinner_default.stop();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const select = new import_termkit3.Select();
|
|
378
|
+
const picked = await select.ask("Which source do you want to remove?", config.sources.map((s) => ({ label: s, value: s })));
|
|
379
|
+
if (!picked) return;
|
|
380
|
+
dir = picked.value;
|
|
381
|
+
}
|
|
382
|
+
const resolved = (0, import_path3.resolve)(dir);
|
|
371
383
|
const index = config.sources.indexOf(resolved);
|
|
372
384
|
if (index === -1) {
|
|
373
385
|
spinner_default.start();
|
|
@@ -394,10 +406,23 @@ var destAdd = async ({ type, dir }) => {
|
|
|
394
406
|
spinner_default.stop();
|
|
395
407
|
};
|
|
396
408
|
var destRemove = async ({ type }) => {
|
|
409
|
+
const config = getConfig();
|
|
410
|
+
if (!type) {
|
|
411
|
+
const configured = DEST_TYPES.filter((t) => config.dest[t]);
|
|
412
|
+
if (configured.length === 0) {
|
|
413
|
+
spinner_default.start();
|
|
414
|
+
spinner_default.warn("no destinations configured");
|
|
415
|
+
spinner_default.stop();
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const select = new import_termkit3.Select();
|
|
419
|
+
const picked = await select.ask("Which destination do you want to remove?", configured.map((t) => ({ label: `${t.padEnd(6)} ${config.dest[t]}`, value: t })));
|
|
420
|
+
if (!picked) return;
|
|
421
|
+
type = picked.value;
|
|
422
|
+
}
|
|
397
423
|
if (!DEST_TYPES.includes(type)) {
|
|
398
424
|
throw new Error(`unknown type '${type}', expected: ${DEST_TYPES.join(", ")}`);
|
|
399
425
|
}
|
|
400
|
-
const config = getConfig();
|
|
401
426
|
if (!config.dest[type]) {
|
|
402
427
|
spinner_default.start();
|
|
403
428
|
spinner_default.warn(`no ${type} destination configured`);
|
|
@@ -475,6 +500,12 @@ Subtitle language: ${import_termkit3.Color.green.encoder(config.language ?? "eng
|
|
|
475
500
|
console.log(`Movie format: ${import_termkit3.Color.green.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
|
|
476
501
|
console.log(`Episode format: ${import_termkit3.Color.green.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
|
|
477
502
|
console.log(`Season folder: ${import_termkit3.Color.green.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
|
|
503
|
+
console.log("\nIgnored files:");
|
|
504
|
+
if (!config.ignore || config.ignore.length === 0) {
|
|
505
|
+
console.log(" (none)");
|
|
506
|
+
} else {
|
|
507
|
+
for (const name of config.ignore) console.log(` ${import_termkit3.Color.white.encoder(name)}`);
|
|
508
|
+
}
|
|
478
509
|
console.log();
|
|
479
510
|
};
|
|
480
511
|
|
|
@@ -782,6 +813,12 @@ var import_child_process = require("child_process");
|
|
|
782
813
|
var import_fs7 = require("fs");
|
|
783
814
|
var import_path8 = require("path");
|
|
784
815
|
var import_termkit7 = require("termkit");
|
|
816
|
+
|
|
817
|
+
// src/refs/verbose.ts
|
|
818
|
+
var _verbose = false;
|
|
819
|
+
var isVerbose = () => _verbose;
|
|
820
|
+
|
|
821
|
+
// src/actions/probe.ts
|
|
785
822
|
var DEST_TYPES3 = ["movie", "tv", "ps3"];
|
|
786
823
|
var CODEC_MAP2 = {
|
|
787
824
|
hevc: "x265",
|
|
@@ -843,7 +880,7 @@ var walkVideoFiles = (dir, depth = 0, maxDepth = 3) => {
|
|
|
843
880
|
}
|
|
844
881
|
return results;
|
|
845
882
|
};
|
|
846
|
-
var probe = async ({ type, force
|
|
883
|
+
var probe = async ({ type, force }) => {
|
|
847
884
|
spinner_default.start();
|
|
848
885
|
if (!isFfprobeAvailable()) {
|
|
849
886
|
spinner_default.fail("ffprobe not found \u2014 install ffmpeg to use this command");
|
|
@@ -861,19 +898,19 @@ var probe = async ({ type, force, verbose }) => {
|
|
|
861
898
|
const files = walkVideoFiles(destRoot);
|
|
862
899
|
for (const filePath of files) {
|
|
863
900
|
if (!force && getMediaInfo(filePath)) {
|
|
864
|
-
if (
|
|
901
|
+
if (isVerbose()) spinner_default.info(`already probed: ${filePath}`);
|
|
865
902
|
skipped++;
|
|
866
903
|
continue;
|
|
867
904
|
}
|
|
868
905
|
spinner_default.text = `probing ${import_termkit7.Color.white.encoder(filePath)}`;
|
|
869
906
|
const result = runFfprobe(filePath);
|
|
870
907
|
if (!result) {
|
|
871
|
-
if (
|
|
908
|
+
if (isVerbose()) spinner_default.warn(`ffprobe failed: ${filePath}`);
|
|
872
909
|
failed++;
|
|
873
910
|
continue;
|
|
874
911
|
}
|
|
875
912
|
upsertMediaInfo(filePath, result.codec, result.resolution, result.width, result.height, result.duration);
|
|
876
|
-
if (
|
|
913
|
+
if (isVerbose()) spinner_default.succeed(`${result.resolution ?? "?"} ${result.codec ?? "?"} ${filePath}`);
|
|
877
914
|
probed++;
|
|
878
915
|
}
|
|
879
916
|
}
|
|
@@ -943,7 +980,7 @@ var titleCase_default = (s) => {
|
|
|
943
980
|
};
|
|
944
981
|
|
|
945
982
|
// src/actions/rename.ts
|
|
946
|
-
var rename = async ({ dir: inputDir, type
|
|
983
|
+
var rename = async ({ dir: inputDir, type }) => {
|
|
947
984
|
const dir = (0, import_path9.resolve)(inputDir);
|
|
948
985
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
949
986
|
const config = getConfig();
|
|
@@ -957,7 +994,7 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
957
994
|
for (const [index, entry] of list2.entries()) {
|
|
958
995
|
spinner_default.text = `renaming in ${import_termkit8.Color.white.encoder(dir)} ${index + 1}/${list2.length}`;
|
|
959
996
|
if (!(0, import_fs8.lstatSync)((0, import_path9.resolve)(dir, entry)).isDirectory()) {
|
|
960
|
-
if (
|
|
997
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
961
998
|
skipped++;
|
|
962
999
|
continue;
|
|
963
1000
|
}
|
|
@@ -967,7 +1004,7 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
967
1004
|
const nameMatch = entry.match(/(?<=\[).+?(?=\])/);
|
|
968
1005
|
const id = entry.split("-")[0];
|
|
969
1006
|
if (!nameMatch || !id) {
|
|
970
|
-
if (
|
|
1007
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
971
1008
|
skipped++;
|
|
972
1009
|
continue;
|
|
973
1010
|
}
|
|
@@ -981,13 +1018,13 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
981
1018
|
}
|
|
982
1019
|
const yearMatch = entry.match(/\([^\d]*(\d+)[^\d]*\)/);
|
|
983
1020
|
if (!yearMatch) {
|
|
984
|
-
if (
|
|
1021
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
985
1022
|
skipped++;
|
|
986
1023
|
continue;
|
|
987
1024
|
}
|
|
988
1025
|
const year = yearMatch[0];
|
|
989
1026
|
if (year.length !== 6) {
|
|
990
|
-
if (
|
|
1027
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
991
1028
|
skipped++;
|
|
992
1029
|
continue;
|
|
993
1030
|
}
|
|
@@ -998,20 +1035,20 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
998
1035
|
return videoExtensions_default.includes(ext2) && title.split(" ").reduce((a, w) => f.toLowerCase().includes(w.toLowerCase()) ? a : false, true);
|
|
999
1036
|
});
|
|
1000
1037
|
if (!video) {
|
|
1001
|
-
if (
|
|
1038
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1002
1039
|
skipped++;
|
|
1003
1040
|
continue;
|
|
1004
1041
|
}
|
|
1005
1042
|
const ext = video.match(/([^.]+$)/)?.[0];
|
|
1006
1043
|
if (!ext) {
|
|
1007
|
-
if (
|
|
1044
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1008
1045
|
skipped++;
|
|
1009
1046
|
continue;
|
|
1010
1047
|
}
|
|
1011
1048
|
const yearNum = parseInt(year.replace(/\D/g, ""));
|
|
1012
1049
|
const formatted = formatMovieName(movieFormat, title, yearNum);
|
|
1013
1050
|
if (entry === formatted && video === `${formatted}.${ext}`) {
|
|
1014
|
-
if (
|
|
1051
|
+
if (isVerbose()) spinner_default.info(`skipped ${entry}`);
|
|
1015
1052
|
skipped++;
|
|
1016
1053
|
continue;
|
|
1017
1054
|
}
|
|
@@ -1337,6 +1374,18 @@ var gatherEntries = (source) => {
|
|
|
1337
1374
|
}
|
|
1338
1375
|
return result;
|
|
1339
1376
|
};
|
|
1377
|
+
var findShowFolder = (destRoot, title) => {
|
|
1378
|
+
if (!(0, import_fs10.existsSync)(destRoot)) return null;
|
|
1379
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1380
|
+
const target = normalize(title);
|
|
1381
|
+
return (0, import_fs10.readdirSync)(destRoot).filter((f) => {
|
|
1382
|
+
try {
|
|
1383
|
+
return (0, import_fs10.lstatSync)((0, import_path11.resolve)(destRoot, f)).isDirectory();
|
|
1384
|
+
} catch {
|
|
1385
|
+
return false;
|
|
1386
|
+
}
|
|
1387
|
+
}).find((f) => normalize(f) === target) ?? null;
|
|
1388
|
+
};
|
|
1340
1389
|
var findSeasonFolder = (showPath, season) => {
|
|
1341
1390
|
if (!(0, import_fs10.existsSync)(showPath)) return null;
|
|
1342
1391
|
const folders = (0, import_fs10.readdirSync)(showPath).filter((f) => {
|
|
@@ -1360,7 +1409,14 @@ var classifyMovieConfidence = (entry) => {
|
|
|
1360
1409
|
if (/\.\d{4}\..*?(?:2160p|1080p|720p|BluRay|WEBRip|WEB-DL|HDTV)/i.test(entry)) return "auto";
|
|
1361
1410
|
return "ambiguous";
|
|
1362
1411
|
};
|
|
1363
|
-
var
|
|
1412
|
+
var typeColor = {
|
|
1413
|
+
movie: import_termkit10.Color.white.cyan,
|
|
1414
|
+
tv: import_termkit10.Color.white.green,
|
|
1415
|
+
book: import_termkit10.Color.white.yellow,
|
|
1416
|
+
ps3: import_termkit10.Color.white.magenta
|
|
1417
|
+
};
|
|
1418
|
+
var typeTag = (t) => isVerbose() ? import_termkit10.Color.white.faint.encoder(` (${t})`) : "";
|
|
1419
|
+
var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
|
|
1364
1420
|
const config = getConfig();
|
|
1365
1421
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1366
1422
|
const language = config.language ?? "eng";
|
|
@@ -1405,7 +1461,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1405
1461
|
}
|
|
1406
1462
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1407
1463
|
if (!videoFile) {
|
|
1408
|
-
if (
|
|
1464
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1409
1465
|
return false;
|
|
1410
1466
|
}
|
|
1411
1467
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
@@ -1452,13 +1508,16 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1452
1508
|
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1453
1509
|
}
|
|
1454
1510
|
}
|
|
1455
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${folderName}`);
|
|
1511
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
|
|
1456
1512
|
return true;
|
|
1457
1513
|
};
|
|
1458
1514
|
spinner_default.start();
|
|
1459
1515
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
1460
1516
|
let imported = 0, skipped = 0;
|
|
1461
1517
|
const pendingMovies = [];
|
|
1518
|
+
const pendingTv = [];
|
|
1519
|
+
const ignoreSet = new Set(config.ignore ?? []);
|
|
1520
|
+
const seenIgnored = /* @__PURE__ */ new Set();
|
|
1462
1521
|
for (const source of config.sources) {
|
|
1463
1522
|
if (!(0, import_fs10.existsSync)(source)) {
|
|
1464
1523
|
spinner_default.warn(`source not found: ${import_termkit10.Color.white.encoder(source)}`);
|
|
@@ -1466,6 +1525,11 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1466
1525
|
}
|
|
1467
1526
|
spinner_default.text = `scanning ${import_termkit10.Color.white.encoder(source)}`;
|
|
1468
1527
|
for (const { entry, entryPath, isDir } of gatherEntries(source)) {
|
|
1528
|
+
if (ignoreSet.has(entry)) {
|
|
1529
|
+
seenIgnored.add(entry);
|
|
1530
|
+
if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1469
1533
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
1470
1534
|
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1471
1535
|
const isBookDir = isDir && containsBook(entryPath);
|
|
@@ -1483,7 +1547,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1483
1547
|
}
|
|
1484
1548
|
const destRoot = config.dest[detectedType];
|
|
1485
1549
|
if (!destRoot) {
|
|
1486
|
-
if (
|
|
1550
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
1487
1551
|
skipped++;
|
|
1488
1552
|
continue;
|
|
1489
1553
|
}
|
|
@@ -1505,7 +1569,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1505
1569
|
moveFolder(entryPath, destPath);
|
|
1506
1570
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1507
1571
|
}
|
|
1508
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${destName}`);
|
|
1572
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
|
|
1509
1573
|
imported++;
|
|
1510
1574
|
continue;
|
|
1511
1575
|
}
|
|
@@ -1530,20 +1594,20 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1530
1594
|
}
|
|
1531
1595
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1532
1596
|
}
|
|
1533
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${entry}`);
|
|
1597
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.book.encoder(entry)}${typeTag("book")}`);
|
|
1534
1598
|
imported++;
|
|
1535
1599
|
continue;
|
|
1536
1600
|
}
|
|
1537
1601
|
const parsed = parseDownloadName(entry);
|
|
1538
1602
|
if (!parsed) {
|
|
1539
|
-
if (
|
|
1603
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
1540
1604
|
skipped++;
|
|
1541
1605
|
continue;
|
|
1542
1606
|
}
|
|
1543
1607
|
if (detectedType === "movie") {
|
|
1544
1608
|
const confidence = classifyMovieConfidence(entry);
|
|
1545
1609
|
if (confidence === "skip") {
|
|
1546
|
-
if (
|
|
1610
|
+
if (isVerbose()) spinner_default.info(`skipped (uncertain): ${entry}`);
|
|
1547
1611
|
skipped++;
|
|
1548
1612
|
continue;
|
|
1549
1613
|
}
|
|
@@ -1587,7 +1651,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1587
1651
|
}
|
|
1588
1652
|
if (detectedType === "tv") {
|
|
1589
1653
|
if (parsed.season === void 0) {
|
|
1590
|
-
if (
|
|
1654
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
1591
1655
|
skipped++;
|
|
1592
1656
|
continue;
|
|
1593
1657
|
}
|
|
@@ -1597,20 +1661,25 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1597
1661
|
if (registeredShow) {
|
|
1598
1662
|
showPath = registeredShow.path;
|
|
1599
1663
|
showFolderName = showPath.split("/").pop() ?? registeredShow.path;
|
|
1600
|
-
} else if (auto) {
|
|
1601
|
-
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1602
|
-
showPath = (0, import_path11.resolve)(destRoot, showFolderName);
|
|
1603
|
-
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1604
1664
|
} else {
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1665
|
+
const existingFolder = findShowFolder(destRoot, resolvedTitle);
|
|
1666
|
+
if (existingFolder) {
|
|
1667
|
+
showFolderName = existingFolder;
|
|
1668
|
+
showPath = (0, import_path11.resolve)(destRoot, existingFolder);
|
|
1669
|
+
} else if (auto) {
|
|
1670
|
+
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1671
|
+
showPath = (0, import_path11.resolve)(destRoot, showFolderName);
|
|
1672
|
+
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1673
|
+
} else {
|
|
1674
|
+
pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1608
1677
|
}
|
|
1609
1678
|
const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1610
1679
|
const seasonPath = (0, import_path11.resolve)(showPath, seasonFolderName);
|
|
1611
1680
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1612
1681
|
if (!videoFile) {
|
|
1613
|
-
if (
|
|
1682
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1614
1683
|
skipped++;
|
|
1615
1684
|
continue;
|
|
1616
1685
|
}
|
|
@@ -1674,7 +1743,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1674
1743
|
}
|
|
1675
1744
|
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
1676
1745
|
}
|
|
1677
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${showFolderName} / ${seasonFolderName} / ${episodeName}`);
|
|
1746
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
|
|
1678
1747
|
imported++;
|
|
1679
1748
|
continue;
|
|
1680
1749
|
}
|
|
@@ -1687,7 +1756,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1687
1756
|
}
|
|
1688
1757
|
if (pendingMovies.length > 0) {
|
|
1689
1758
|
spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
|
|
1690
|
-
for (const p of pendingMovies) spinner_default.info(` ? ${p.entry.replace(/\/$/, "")}`);
|
|
1759
|
+
for (const p of pendingMovies) spinner_default.info(` ${typeColor.movie.encoder("?")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
|
|
1691
1760
|
let toProcess = [];
|
|
1692
1761
|
if (interactive) {
|
|
1693
1762
|
spinner_default.stop();
|
|
@@ -1714,6 +1783,20 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1714
1783
|
}
|
|
1715
1784
|
}
|
|
1716
1785
|
}
|
|
1786
|
+
if (pendingTv.length > 0) {
|
|
1787
|
+
spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
|
|
1788
|
+
for (const p of pendingTv) spinner_default.info(` ${typeColor.tv.encoder("?")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
1789
|
+
skipped += pendingTv.length;
|
|
1790
|
+
}
|
|
1791
|
+
if (ignoreSet.size > 0) {
|
|
1792
|
+
const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
|
|
1793
|
+
if (stale.length > 0 && !dryRun) {
|
|
1794
|
+
const updated = config.ignore.filter((name) => !stale.includes(name));
|
|
1795
|
+
config.ignore = updated;
|
|
1796
|
+
saveConfig(config);
|
|
1797
|
+
for (const name of stale) spinner_default.info(`removed from ignore list (not found): ${import_termkit10.Color.white.encoder(name)}`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1717
1800
|
spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
|
|
1718
1801
|
if (skipped) spinner_default.info(`skipped ${skipped} items`);
|
|
1719
1802
|
spinner_default.stop();
|
|
@@ -1863,7 +1946,7 @@ var findSeasonFolder2 = (showPath, season) => {
|
|
|
1863
1946
|
return match && parseInt(match[1]) === season;
|
|
1864
1947
|
}) ?? null;
|
|
1865
1948
|
};
|
|
1866
|
-
var processItem = async (entryPath, useHardlink,
|
|
1949
|
+
var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
1867
1950
|
const config = getConfig();
|
|
1868
1951
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1869
1952
|
const entry = (0, import_path12.basename)(entryPath);
|
|
@@ -1887,7 +1970,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1887
1970
|
}
|
|
1888
1971
|
const destRoot = config.dest[detectedType];
|
|
1889
1972
|
if (!destRoot) {
|
|
1890
|
-
if (
|
|
1973
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
1891
1974
|
return;
|
|
1892
1975
|
}
|
|
1893
1976
|
if (detectedType === "ps3") {
|
|
@@ -1928,12 +2011,12 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1928
2011
|
}
|
|
1929
2012
|
const parsed = parseDownloadName(entry);
|
|
1930
2013
|
if (!parsed) {
|
|
1931
|
-
if (
|
|
2014
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
1932
2015
|
return;
|
|
1933
2016
|
}
|
|
1934
2017
|
if (detectedType === "tv") {
|
|
1935
2018
|
if (parsed.season === void 0) {
|
|
1936
|
-
if (
|
|
2019
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
1937
2020
|
return;
|
|
1938
2021
|
}
|
|
1939
2022
|
const registeredShow = getShowByTitle(parsed.title);
|
|
@@ -1947,14 +2030,14 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1947
2030
|
showPath = (0, import_path12.resolve)(destRoot, showFolderName);
|
|
1948
2031
|
upsertShow(showPath, null, parsed.title);
|
|
1949
2032
|
} else {
|
|
1950
|
-
if (
|
|
2033
|
+
if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
|
|
1951
2034
|
return;
|
|
1952
2035
|
}
|
|
1953
2036
|
const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1954
2037
|
const seasonPath = (0, import_path12.resolve)(showPath, seasonFolderName);
|
|
1955
2038
|
const videoFile2 = isDir ? findVideo2(entryPath) : entry;
|
|
1956
2039
|
if (!videoFile2) {
|
|
1957
|
-
if (
|
|
2040
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1958
2041
|
return;
|
|
1959
2042
|
}
|
|
1960
2043
|
const videoExt2 = videoFile2.match(/([^.]+$)/)?.[0];
|
|
@@ -2008,7 +2091,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2008
2091
|
}
|
|
2009
2092
|
const videoFile = isDir ? findVideo2(entryPath) : entry;
|
|
2010
2093
|
if (!videoFile) {
|
|
2011
|
-
if (
|
|
2094
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
2012
2095
|
return;
|
|
2013
2096
|
}
|
|
2014
2097
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
@@ -2055,7 +2138,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2055
2138
|
}
|
|
2056
2139
|
spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(folderName)}`);
|
|
2057
2140
|
};
|
|
2058
|
-
var watch = async ({ hardlink = false,
|
|
2141
|
+
var watch = async ({ hardlink = false, auto = false }) => {
|
|
2059
2142
|
const config = getConfig();
|
|
2060
2143
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
2061
2144
|
const language = config.language ?? "eng";
|
|
@@ -2069,7 +2152,7 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
2069
2152
|
pending.delete(path);
|
|
2070
2153
|
try {
|
|
2071
2154
|
for (const entry of expandWatchPath(path)) {
|
|
2072
|
-
await processItem(entry, hardlink,
|
|
2155
|
+
await processItem(entry, hardlink, language, auto);
|
|
2073
2156
|
}
|
|
2074
2157
|
} catch (err) {
|
|
2075
2158
|
spinner_default.fail(`error processing ${path}: ${err.message}`);
|