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/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,47 @@ 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
|
+
};
|
|
1389
|
+
var findShowFolderByContent = (destRoot, title) => {
|
|
1390
|
+
if (!(0, import_fs10.existsSync)(destRoot)) return null;
|
|
1391
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1392
|
+
const target = normalize(title);
|
|
1393
|
+
const matchesTitle = (name) => {
|
|
1394
|
+
if (!isTvEpisodeName(name)) return false;
|
|
1395
|
+
const p = parseDownloadName(name);
|
|
1396
|
+
return !!p && normalize(p.title) === target;
|
|
1397
|
+
};
|
|
1398
|
+
for (const folder of (0, import_fs10.readdirSync)(destRoot)) {
|
|
1399
|
+
try {
|
|
1400
|
+
const folderPath = (0, import_path11.resolve)(destRoot, folder);
|
|
1401
|
+
if (!(0, import_fs10.lstatSync)(folderPath).isDirectory()) continue;
|
|
1402
|
+
const children = (0, import_fs10.readdirSync)(folderPath);
|
|
1403
|
+
if (children.some(matchesTitle)) return folder;
|
|
1404
|
+
for (const child of children) {
|
|
1405
|
+
if (!isSeasonDirName(child)) continue;
|
|
1406
|
+
try {
|
|
1407
|
+
const seasonPath = (0, import_path11.resolve)(folderPath, child);
|
|
1408
|
+
if (!(0, import_fs10.lstatSync)(seasonPath).isDirectory()) continue;
|
|
1409
|
+
if ((0, import_fs10.readdirSync)(seasonPath).some(matchesTitle)) return folder;
|
|
1410
|
+
} catch {
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
} catch {
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
return null;
|
|
1417
|
+
};
|
|
1340
1418
|
var findSeasonFolder = (showPath, season) => {
|
|
1341
1419
|
if (!(0, import_fs10.existsSync)(showPath)) return null;
|
|
1342
1420
|
const folders = (0, import_fs10.readdirSync)(showPath).filter((f) => {
|
|
@@ -1360,7 +1438,15 @@ var classifyMovieConfidence = (entry) => {
|
|
|
1360
1438
|
if (/\.\d{4}\..*?(?:2160p|1080p|720p|BluRay|WEBRip|WEB-DL|HDTV)/i.test(entry)) return "auto";
|
|
1361
1439
|
return "ambiguous";
|
|
1362
1440
|
};
|
|
1363
|
-
var
|
|
1441
|
+
var typeColor = {
|
|
1442
|
+
movie: import_termkit10.Color.white.cyan,
|
|
1443
|
+
tv: import_termkit10.Color.white.green,
|
|
1444
|
+
book: import_termkit10.Color.white.yellow,
|
|
1445
|
+
ps3: import_termkit10.Color.white.magenta
|
|
1446
|
+
};
|
|
1447
|
+
var typeGlyph = (t) => typeColor[t].encoder("\u25CF");
|
|
1448
|
+
var typeTag = (t) => isVerbose() ? import_termkit10.Color.white.faint.encoder(` (${t})`) : "";
|
|
1449
|
+
var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
|
|
1364
1450
|
const config = getConfig();
|
|
1365
1451
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1366
1452
|
const language = config.language ?? "eng";
|
|
@@ -1405,7 +1491,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1405
1491
|
}
|
|
1406
1492
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1407
1493
|
if (!videoFile) {
|
|
1408
|
-
if (
|
|
1494
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1409
1495
|
return false;
|
|
1410
1496
|
}
|
|
1411
1497
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
@@ -1452,13 +1538,16 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1452
1538
|
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1453
1539
|
}
|
|
1454
1540
|
}
|
|
1455
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${folderName}`);
|
|
1541
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("movie")} ${typeColor.movie.encoder(folderName)}${typeTag("movie")}`);
|
|
1456
1542
|
return true;
|
|
1457
1543
|
};
|
|
1458
1544
|
spinner_default.start();
|
|
1459
1545
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
1460
1546
|
let imported = 0, skipped = 0;
|
|
1461
1547
|
const pendingMovies = [];
|
|
1548
|
+
const pendingTv = [];
|
|
1549
|
+
const ignoreSet = new Set(config.ignore ?? []);
|
|
1550
|
+
const seenIgnored = /* @__PURE__ */ new Set();
|
|
1462
1551
|
for (const source of config.sources) {
|
|
1463
1552
|
if (!(0, import_fs10.existsSync)(source)) {
|
|
1464
1553
|
spinner_default.warn(`source not found: ${import_termkit10.Color.white.encoder(source)}`);
|
|
@@ -1466,6 +1555,11 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1466
1555
|
}
|
|
1467
1556
|
spinner_default.text = `scanning ${import_termkit10.Color.white.encoder(source)}`;
|
|
1468
1557
|
for (const { entry, entryPath, isDir } of gatherEntries(source)) {
|
|
1558
|
+
if (ignoreSet.has(entry)) {
|
|
1559
|
+
seenIgnored.add(entry);
|
|
1560
|
+
if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1469
1563
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
1470
1564
|
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1471
1565
|
const isBookDir = isDir && containsBook(entryPath);
|
|
@@ -1483,7 +1577,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1483
1577
|
}
|
|
1484
1578
|
const destRoot = config.dest[detectedType];
|
|
1485
1579
|
if (!destRoot) {
|
|
1486
|
-
if (
|
|
1580
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
1487
1581
|
skipped++;
|
|
1488
1582
|
continue;
|
|
1489
1583
|
}
|
|
@@ -1505,7 +1599,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1505
1599
|
moveFolder(entryPath, destPath);
|
|
1506
1600
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1507
1601
|
}
|
|
1508
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${destName}`);
|
|
1602
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("ps3")} ${typeColor.ps3.encoder(destName)}${typeTag("ps3")}`);
|
|
1509
1603
|
imported++;
|
|
1510
1604
|
continue;
|
|
1511
1605
|
}
|
|
@@ -1530,20 +1624,20 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1530
1624
|
}
|
|
1531
1625
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1532
1626
|
}
|
|
1533
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${entry}`);
|
|
1627
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("book")} ${typeColor.book.encoder(entry)}${typeTag("book")}`);
|
|
1534
1628
|
imported++;
|
|
1535
1629
|
continue;
|
|
1536
1630
|
}
|
|
1537
1631
|
const parsed = parseDownloadName(entry);
|
|
1538
1632
|
if (!parsed) {
|
|
1539
|
-
if (
|
|
1633
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
1540
1634
|
skipped++;
|
|
1541
1635
|
continue;
|
|
1542
1636
|
}
|
|
1543
1637
|
if (detectedType === "movie") {
|
|
1544
1638
|
const confidence = classifyMovieConfidence(entry);
|
|
1545
1639
|
if (confidence === "skip") {
|
|
1546
|
-
if (
|
|
1640
|
+
if (isVerbose()) spinner_default.info(`skipped (uncertain): ${entry}`);
|
|
1547
1641
|
skipped++;
|
|
1548
1642
|
continue;
|
|
1549
1643
|
}
|
|
@@ -1587,7 +1681,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1587
1681
|
}
|
|
1588
1682
|
if (detectedType === "tv") {
|
|
1589
1683
|
if (parsed.season === void 0) {
|
|
1590
|
-
if (
|
|
1684
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
1591
1685
|
skipped++;
|
|
1592
1686
|
continue;
|
|
1593
1687
|
}
|
|
@@ -1597,20 +1691,25 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1597
1691
|
if (registeredShow) {
|
|
1598
1692
|
showPath = registeredShow.path;
|
|
1599
1693
|
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
1694
|
} else {
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1695
|
+
const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
|
|
1696
|
+
if (existingFolder) {
|
|
1697
|
+
showFolderName = existingFolder;
|
|
1698
|
+
showPath = (0, import_path11.resolve)(destRoot, existingFolder);
|
|
1699
|
+
} else if (auto) {
|
|
1700
|
+
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
1701
|
+
showPath = (0, import_path11.resolve)(destRoot, showFolderName);
|
|
1702
|
+
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
1703
|
+
} else {
|
|
1704
|
+
pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
|
|
1705
|
+
continue;
|
|
1706
|
+
}
|
|
1608
1707
|
}
|
|
1609
1708
|
const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1610
1709
|
const seasonPath = (0, import_path11.resolve)(showPath, seasonFolderName);
|
|
1611
1710
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1612
1711
|
if (!videoFile) {
|
|
1613
|
-
if (
|
|
1712
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1614
1713
|
skipped++;
|
|
1615
1714
|
continue;
|
|
1616
1715
|
}
|
|
@@ -1674,7 +1773,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1674
1773
|
}
|
|
1675
1774
|
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
1676
1775
|
}
|
|
1677
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${showFolderName} / ${seasonFolderName} / ${episodeName}`);
|
|
1776
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("tv")} ${typeColor.tv.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
|
|
1678
1777
|
imported++;
|
|
1679
1778
|
continue;
|
|
1680
1779
|
}
|
|
@@ -1687,7 +1786,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1687
1786
|
}
|
|
1688
1787
|
if (pendingMovies.length > 0) {
|
|
1689
1788
|
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(`
|
|
1789
|
+
for (const p of pendingMovies) spinner_default.info(` ${typeGlyph("movie")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
|
|
1691
1790
|
let toProcess = [];
|
|
1692
1791
|
if (interactive) {
|
|
1693
1792
|
spinner_default.stop();
|
|
@@ -1714,6 +1813,20 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
|
|
|
1714
1813
|
}
|
|
1715
1814
|
}
|
|
1716
1815
|
}
|
|
1816
|
+
if (pendingTv.length > 0) {
|
|
1817
|
+
spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
|
|
1818
|
+
for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
1819
|
+
skipped += pendingTv.length;
|
|
1820
|
+
}
|
|
1821
|
+
if (ignoreSet.size > 0) {
|
|
1822
|
+
const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
|
|
1823
|
+
if (stale.length > 0 && !dryRun) {
|
|
1824
|
+
const updated = config.ignore.filter((name) => !stale.includes(name));
|
|
1825
|
+
config.ignore = updated;
|
|
1826
|
+
saveConfig(config);
|
|
1827
|
+
for (const name of stale) spinner_default.info(`removed from ignore list (not found): ${import_termkit10.Color.white.encoder(name)}`);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1717
1830
|
spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
|
|
1718
1831
|
if (skipped) spinner_default.info(`skipped ${skipped} items`);
|
|
1719
1832
|
spinner_default.stop();
|
|
@@ -1863,7 +1976,7 @@ var findSeasonFolder2 = (showPath, season) => {
|
|
|
1863
1976
|
return match && parseInt(match[1]) === season;
|
|
1864
1977
|
}) ?? null;
|
|
1865
1978
|
};
|
|
1866
|
-
var processItem = async (entryPath, useHardlink,
|
|
1979
|
+
var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
1867
1980
|
const config = getConfig();
|
|
1868
1981
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1869
1982
|
const entry = (0, import_path12.basename)(entryPath);
|
|
@@ -1887,7 +2000,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1887
2000
|
}
|
|
1888
2001
|
const destRoot = config.dest[detectedType];
|
|
1889
2002
|
if (!destRoot) {
|
|
1890
|
-
if (
|
|
2003
|
+
if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
|
|
1891
2004
|
return;
|
|
1892
2005
|
}
|
|
1893
2006
|
if (detectedType === "ps3") {
|
|
@@ -1928,12 +2041,12 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1928
2041
|
}
|
|
1929
2042
|
const parsed = parseDownloadName(entry);
|
|
1930
2043
|
if (!parsed) {
|
|
1931
|
-
if (
|
|
2044
|
+
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
1932
2045
|
return;
|
|
1933
2046
|
}
|
|
1934
2047
|
if (detectedType === "tv") {
|
|
1935
2048
|
if (parsed.season === void 0) {
|
|
1936
|
-
if (
|
|
2049
|
+
if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
|
|
1937
2050
|
return;
|
|
1938
2051
|
}
|
|
1939
2052
|
const registeredShow = getShowByTitle(parsed.title);
|
|
@@ -1947,14 +2060,14 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1947
2060
|
showPath = (0, import_path12.resolve)(destRoot, showFolderName);
|
|
1948
2061
|
upsertShow(showPath, null, parsed.title);
|
|
1949
2062
|
} else {
|
|
1950
|
-
if (
|
|
2063
|
+
if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
|
|
1951
2064
|
return;
|
|
1952
2065
|
}
|
|
1953
2066
|
const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1954
2067
|
const seasonPath = (0, import_path12.resolve)(showPath, seasonFolderName);
|
|
1955
2068
|
const videoFile2 = isDir ? findVideo2(entryPath) : entry;
|
|
1956
2069
|
if (!videoFile2) {
|
|
1957
|
-
if (
|
|
2070
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
1958
2071
|
return;
|
|
1959
2072
|
}
|
|
1960
2073
|
const videoExt2 = videoFile2.match(/([^.]+$)/)?.[0];
|
|
@@ -2008,7 +2121,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2008
2121
|
}
|
|
2009
2122
|
const videoFile = isDir ? findVideo2(entryPath) : entry;
|
|
2010
2123
|
if (!videoFile) {
|
|
2011
|
-
if (
|
|
2124
|
+
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
2012
2125
|
return;
|
|
2013
2126
|
}
|
|
2014
2127
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
@@ -2055,7 +2168,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2055
2168
|
}
|
|
2056
2169
|
spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(folderName)}`);
|
|
2057
2170
|
};
|
|
2058
|
-
var watch = async ({ hardlink = false,
|
|
2171
|
+
var watch = async ({ hardlink = false, auto = false }) => {
|
|
2059
2172
|
const config = getConfig();
|
|
2060
2173
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
2061
2174
|
const language = config.language ?? "eng";
|
|
@@ -2069,7 +2182,7 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
2069
2182
|
pending.delete(path);
|
|
2070
2183
|
try {
|
|
2071
2184
|
for (const entry of expandWatchPath(path)) {
|
|
2072
|
-
await processItem(entry, hardlink,
|
|
2185
|
+
await processItem(entry, hardlink, language, auto);
|
|
2073
2186
|
}
|
|
2074
2187
|
} catch (err) {
|
|
2075
2188
|
spinner_default.fail(`error processing ${path}: ${err.message}`);
|