reelsort 0.2.0 → 0.2.2
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 +491 -163
- package/dist/index.d.mts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +473 -149
- package/dist/index.mjs +474 -150
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -213,18 +213,17 @@ var searchMovie = async (title, year, apiKey) => {
|
|
|
213
213
|
if (year) url.searchParams.set("year", String(year));
|
|
214
214
|
try {
|
|
215
215
|
const res = await fetch(url.toString());
|
|
216
|
-
if (!res.ok) return
|
|
216
|
+
if (!res.ok) return [];
|
|
217
217
|
const data = await res.json();
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
};
|
|
218
|
+
return data.results.slice(0, 5).map((r) => ({
|
|
219
|
+
id: r.id,
|
|
220
|
+
title: r.title,
|
|
221
|
+
year: r.release_date ? parseInt(r.release_date.slice(0, 4)) : void 0,
|
|
222
|
+
overview: r.overview || void 0,
|
|
223
|
+
url: `${TMDB_WEB}/movie/${r.id}`
|
|
224
|
+
}));
|
|
226
225
|
} catch {
|
|
227
|
-
return
|
|
226
|
+
return [];
|
|
228
227
|
}
|
|
229
228
|
};
|
|
230
229
|
var getEpisodeName = async (seriesId, season, episode, apiKey) => {
|
|
@@ -343,7 +342,7 @@ var add = async ({ name }) => {
|
|
|
343
342
|
(0, import_fs3.mkdirSync)(showPath, { recursive: true });
|
|
344
343
|
upsertShow(showPath, picked.id, picked.title);
|
|
345
344
|
spinner_default.start();
|
|
346
|
-
spinner_default.succeed(`added ${import_termkit2.Color.
|
|
345
|
+
spinner_default.succeed(`added ${import_termkit2.Color.green.encoder(folderName)}`);
|
|
347
346
|
spinner_default.stop();
|
|
348
347
|
};
|
|
349
348
|
var add_default = add;
|
|
@@ -383,17 +382,17 @@ var clean = async ({ dryRun, olderThan }) => {
|
|
|
383
382
|
continue;
|
|
384
383
|
}
|
|
385
384
|
if (dryRun) {
|
|
386
|
-
spinner_default.succeed(`[dry] would remove ${import_termkit3.Color.
|
|
385
|
+
spinner_default.succeed(`[dry] would remove ${import_termkit3.Color.white.encoder(imp.sourcePath)}`);
|
|
387
386
|
cleaned++;
|
|
388
387
|
continue;
|
|
389
388
|
}
|
|
390
389
|
try {
|
|
391
390
|
(0, import_fs4.rmSync)(imp.sourcePath, { recursive: true, force: true });
|
|
392
391
|
deleteImport(imp.id);
|
|
393
|
-
spinner_default.succeed(`removed ${import_termkit3.Color.
|
|
392
|
+
spinner_default.succeed(`removed ${import_termkit3.Color.white.encoder(imp.sourcePath)}`);
|
|
394
393
|
cleaned++;
|
|
395
394
|
} catch {
|
|
396
|
-
spinner_default.warn(`locked or inaccessible, skipped: ${import_termkit3.Color.
|
|
395
|
+
spinner_default.warn(`locked or inaccessible, skipped: ${import_termkit3.Color.white.encoder(imp.sourcePath)}`);
|
|
397
396
|
skipped++;
|
|
398
397
|
}
|
|
399
398
|
}
|
|
@@ -424,20 +423,20 @@ var formatEpisode = (season, episode, format = DEFAULT_EPISODE_FORMAT, double =
|
|
|
424
423
|
};
|
|
425
424
|
|
|
426
425
|
// src/actions/config.ts
|
|
427
|
-
var DEST_TYPES = ["movie", "tv", "ps3"];
|
|
426
|
+
var DEST_TYPES = ["movie", "tv", "ps3", "book"];
|
|
428
427
|
var sourceAdd = async ({ dir }) => {
|
|
429
428
|
const resolved = (0, import_path4.resolve)(dir);
|
|
430
429
|
const config = getConfig();
|
|
431
430
|
if (config.sources.includes(resolved)) {
|
|
432
431
|
spinner_default.start();
|
|
433
|
-
spinner_default.info(`source already configured: ${import_termkit4.Color.
|
|
432
|
+
spinner_default.info(`source already configured: ${import_termkit4.Color.white.encoder(resolved)}`);
|
|
434
433
|
spinner_default.stop();
|
|
435
434
|
return;
|
|
436
435
|
}
|
|
437
436
|
config.sources.push(resolved);
|
|
438
437
|
saveConfig(config);
|
|
439
438
|
spinner_default.start();
|
|
440
|
-
spinner_default.succeed(`added source: ${import_termkit4.Color.
|
|
439
|
+
spinner_default.succeed(`added source: ${import_termkit4.Color.white.encoder(resolved)}`);
|
|
441
440
|
spinner_default.stop();
|
|
442
441
|
};
|
|
443
442
|
var sourceRemove = async ({ dir }) => {
|
|
@@ -446,14 +445,14 @@ var sourceRemove = async ({ dir }) => {
|
|
|
446
445
|
const index = config.sources.indexOf(resolved);
|
|
447
446
|
if (index === -1) {
|
|
448
447
|
spinner_default.start();
|
|
449
|
-
spinner_default.warn(`source not found: ${import_termkit4.Color.
|
|
448
|
+
spinner_default.warn(`source not found: ${import_termkit4.Color.white.encoder(resolved)}`);
|
|
450
449
|
spinner_default.stop();
|
|
451
450
|
return;
|
|
452
451
|
}
|
|
453
452
|
config.sources.splice(index, 1);
|
|
454
453
|
saveConfig(config);
|
|
455
454
|
spinner_default.start();
|
|
456
|
-
spinner_default.succeed(`removed source: ${import_termkit4.Color.
|
|
455
|
+
spinner_default.succeed(`removed source: ${import_termkit4.Color.white.encoder(resolved)}`);
|
|
457
456
|
spinner_default.stop();
|
|
458
457
|
};
|
|
459
458
|
var destAdd = async ({ type, dir }) => {
|
|
@@ -465,7 +464,7 @@ var destAdd = async ({ type, dir }) => {
|
|
|
465
464
|
config.dest[type] = resolved;
|
|
466
465
|
saveConfig(config);
|
|
467
466
|
spinner_default.start();
|
|
468
|
-
spinner_default.succeed(`set ${type} destination: ${import_termkit4.Color.
|
|
467
|
+
spinner_default.succeed(`set ${type} destination: ${import_termkit4.Color.green.encoder(resolved)}`);
|
|
469
468
|
spinner_default.stop();
|
|
470
469
|
};
|
|
471
470
|
var destRemove = async ({ type }) => {
|
|
@@ -491,7 +490,7 @@ var configSet = async ({ key, subkey, value }) => {
|
|
|
491
490
|
config.language = subkey;
|
|
492
491
|
saveConfig(config);
|
|
493
492
|
spinner_default.start();
|
|
494
|
-
spinner_default.succeed(`set subtitle language: ${import_termkit4.Color.
|
|
493
|
+
spinner_default.succeed(`set subtitle language: ${import_termkit4.Color.green.encoder(subkey)}`);
|
|
495
494
|
spinner_default.stop();
|
|
496
495
|
return;
|
|
497
496
|
}
|
|
@@ -521,7 +520,7 @@ var configSet = async ({ key, subkey, value }) => {
|
|
|
521
520
|
}
|
|
522
521
|
saveConfig(config);
|
|
523
522
|
spinner_default.start();
|
|
524
|
-
spinner_default.succeed(`set ${subkey} format: ${import_termkit4.Color.
|
|
523
|
+
spinner_default.succeed(`set ${subkey} format: ${import_termkit4.Color.green.encoder(value ?? subkey)}`);
|
|
525
524
|
spinner_default.stop();
|
|
526
525
|
return;
|
|
527
526
|
}
|
|
@@ -533,7 +532,7 @@ var configShow = async () => {
|
|
|
533
532
|
if (config.sources.length === 0) {
|
|
534
533
|
console.log(" (none)");
|
|
535
534
|
} else {
|
|
536
|
-
for (const s of config.sources) console.log(` ${import_termkit4.Color.
|
|
535
|
+
for (const s of config.sources) console.log(` ${import_termkit4.Color.white.encoder(s)}`);
|
|
537
536
|
}
|
|
538
537
|
console.log("\nDestinations:");
|
|
539
538
|
const entries = DEST_TYPES.map((t) => ({ type: t, path: config.dest[t] })).filter((e) => e.path);
|
|
@@ -541,15 +540,15 @@ var configShow = async () => {
|
|
|
541
540
|
console.log(" (none)");
|
|
542
541
|
} else {
|
|
543
542
|
for (const { type, path } of entries) {
|
|
544
|
-
console.log(` ${type.padEnd(6)} ${import_termkit4.Color.
|
|
543
|
+
console.log(` ${type.padEnd(6)} ${import_termkit4.Color.green.encoder(path)}`);
|
|
545
544
|
}
|
|
546
545
|
}
|
|
547
546
|
console.log(`
|
|
548
|
-
Subtitle language: ${import_termkit4.Color.
|
|
547
|
+
Subtitle language: ${import_termkit4.Color.green.encoder(config.language ?? "eng (default)")}`);
|
|
549
548
|
console.log(`TMDb API key: ${config.tmdbApiKey ? import_termkit4.Color.green.encoder("configured") : import_termkit4.Color.red.encoder("not set")}`);
|
|
550
|
-
console.log(`Movie format: ${import_termkit4.Color.
|
|
551
|
-
console.log(`Episode format: ${import_termkit4.Color.
|
|
552
|
-
console.log(`Season folder: ${import_termkit4.Color.
|
|
549
|
+
console.log(`Movie format: ${import_termkit4.Color.green.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
|
|
550
|
+
console.log(`Episode format: ${import_termkit4.Color.green.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
|
|
551
|
+
console.log(`Season folder: ${import_termkit4.Color.green.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
|
|
553
552
|
console.log();
|
|
554
553
|
};
|
|
555
554
|
|
|
@@ -560,7 +559,7 @@ var import_termkit5 = require("termkit");
|
|
|
560
559
|
var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore }) => {
|
|
561
560
|
let dir1 = rawDir1;
|
|
562
561
|
let dir2 = rawDir2;
|
|
563
|
-
spinner_default.text = `checking differences between ${import_termkit5.Color.
|
|
562
|
+
spinner_default.text = `checking differences between ${import_termkit5.Color.white.encoder(dir1)} and ${import_termkit5.Color.white.encoder(dir2)}`;
|
|
564
563
|
spinner_default.start();
|
|
565
564
|
dir1 = (0, import_path5.resolve)(dir1);
|
|
566
565
|
dir2 = (0, import_path5.resolve)(dir2);
|
|
@@ -596,7 +595,7 @@ var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore }) => {
|
|
|
596
595
|
removed.push(l);
|
|
597
596
|
}
|
|
598
597
|
}
|
|
599
|
-
spinner_default.succeed(`checked differences between ${import_termkit5.Color.
|
|
598
|
+
spinner_default.succeed(`checked differences between ${import_termkit5.Color.white.encoder(dir1)} and ${import_termkit5.Color.white.encoder(dir2)}`);
|
|
600
599
|
spinner_default.succeed(`found ${added.length} added files`);
|
|
601
600
|
spinner_default.succeed(`found ${removed.length} removed files`);
|
|
602
601
|
spinner_default.stop();
|
|
@@ -627,9 +626,9 @@ var ended = async ({ remove }) => {
|
|
|
627
626
|
setShowEnded(picked.path, !remove);
|
|
628
627
|
spinner_default.start();
|
|
629
628
|
if (remove) {
|
|
630
|
-
spinner_default.succeed(`marked as active: ${import_termkit6.Color.
|
|
629
|
+
spinner_default.succeed(`marked as active: ${import_termkit6.Color.green.encoder(picked.label)}`);
|
|
631
630
|
} else {
|
|
632
|
-
spinner_default.succeed(`marked as ended: ${import_termkit6.Color.
|
|
631
|
+
spinner_default.succeed(`marked as ended: ${import_termkit6.Color.green.encoder(picked.label)}`);
|
|
633
632
|
}
|
|
634
633
|
spinner_default.stop();
|
|
635
634
|
};
|
|
@@ -652,8 +651,8 @@ var history = async ({ limit, imports }) => {
|
|
|
652
651
|
${import_termkit7.Color.yellow.encoder(label)} (${session.records.length} item${session.records.length !== 1 ? "s" : ""})`);
|
|
653
652
|
for (const r of session.records) {
|
|
654
653
|
const src = (0, import_path6.basename)(r.sourcePath);
|
|
655
|
-
const dest = import_termkit7.Color.
|
|
656
|
-
const mode = r.mode !== "move" ? ` ${import_termkit7.Color.
|
|
654
|
+
const dest = import_termkit7.Color.green.encoder(r.destinationPath);
|
|
655
|
+
const mode = r.mode !== "move" ? ` ${import_termkit7.Color.white.encoder(`[${r.mode}]`)}` : "";
|
|
657
656
|
console.log(` ${src} \u2192 ${dest}${mode}`);
|
|
658
657
|
}
|
|
659
658
|
}
|
|
@@ -672,7 +671,7 @@ ${import_termkit7.Color.yellow.encoder(label)} (${folders.length} item${folders
|
|
|
672
671
|
for (const r of folders) {
|
|
673
672
|
const oldName = (0, import_path6.basename)(r.oldPath);
|
|
674
673
|
const newName = (0, import_path6.basename)(r.newPath);
|
|
675
|
-
console.log(` ${import_termkit7.Color.
|
|
674
|
+
console.log(` ${import_termkit7.Color.white.encoder(oldName)} \u2192 ${import_termkit7.Color.green.encoder(newName)}`);
|
|
676
675
|
}
|
|
677
676
|
}
|
|
678
677
|
}
|
|
@@ -710,7 +709,7 @@ var link = async ({ force }) => {
|
|
|
710
709
|
skipped++;
|
|
711
710
|
continue;
|
|
712
711
|
}
|
|
713
|
-
spinner_default.start(`linking ${import_termkit8.Color.
|
|
712
|
+
spinner_default.start(`linking ${import_termkit8.Color.white.encoder(show)}`);
|
|
714
713
|
const title = parseShowTitle(show);
|
|
715
714
|
const results = await searchTv(title, config.tmdbApiKey);
|
|
716
715
|
if (results.length === 0) {
|
|
@@ -720,7 +719,7 @@ var link = async ({ force }) => {
|
|
|
720
719
|
}
|
|
721
720
|
if (results.length === 1) {
|
|
722
721
|
upsertShow(showPath, results[0].id, results[0].title);
|
|
723
|
-
spinner_default.succeed(`${show} \u2192 ${import_termkit8.Color.
|
|
722
|
+
spinner_default.succeed(`${show} \u2192 ${import_termkit8.Color.green.encoder(results[0].title)} (${results[0].year})`);
|
|
724
723
|
linked++;
|
|
725
724
|
continue;
|
|
726
725
|
}
|
|
@@ -735,7 +734,7 @@ var link = async ({ force }) => {
|
|
|
735
734
|
spinner_default.start();
|
|
736
735
|
if (picked) {
|
|
737
736
|
upsertShow(showPath, picked.id, picked.title);
|
|
738
|
-
spinner_default.succeed(`${show} \u2192 ${import_termkit8.Color.
|
|
737
|
+
spinner_default.succeed(`${show} \u2192 ${import_termkit8.Color.green.encoder(picked.title)} (${picked.year})`);
|
|
739
738
|
linked++;
|
|
740
739
|
} else {
|
|
741
740
|
spinner_default.info(`skipped: ${show}`);
|
|
@@ -892,7 +891,7 @@ var list = async ({ type, missingSubs, codec: codecFilter, resolution: resFilter
|
|
|
892
891
|
const destRoot = config.dest[t];
|
|
893
892
|
if (!(0, import_fs8.existsSync)(destRoot)) {
|
|
894
893
|
console.log(`
|
|
895
|
-
${t.toUpperCase()} ${import_termkit9.Color.
|
|
894
|
+
${t.toUpperCase()} ${import_termkit9.Color.white.encoder(destRoot)} (not found)`);
|
|
896
895
|
continue;
|
|
897
896
|
}
|
|
898
897
|
const folders = (0, import_fs8.readdirSync)(destRoot).filter((f) => {
|
|
@@ -923,7 +922,7 @@ ${t.toUpperCase()} ${import_termkit9.Color.blue.encoder(destRoot)} (not found)
|
|
|
923
922
|
return yearDiff !== 0 ? yearDiff : a.title.localeCompare(b.title);
|
|
924
923
|
});
|
|
925
924
|
console.log(`
|
|
926
|
-
${import_termkit9.Color.yellow.encoder(t.toUpperCase())} ${import_termkit9.Color.
|
|
925
|
+
${import_termkit9.Color.yellow.encoder(t.toUpperCase())} ${import_termkit9.Color.white.encoder(destRoot)}`);
|
|
927
926
|
new import_termkit9.Table(
|
|
928
927
|
filtered.map((e) => ({
|
|
929
928
|
title: e.title,
|
|
@@ -1113,7 +1112,7 @@ var probe = async ({ type, force, verbose }) => {
|
|
|
1113
1112
|
for (const t of types) {
|
|
1114
1113
|
const destRoot = config.dest[t];
|
|
1115
1114
|
if (!(0, import_fs10.existsSync)(destRoot)) continue;
|
|
1116
|
-
spinner_default.text = `scanning ${import_termkit11.Color.
|
|
1115
|
+
spinner_default.text = `scanning ${import_termkit11.Color.white.encoder(destRoot)}`;
|
|
1117
1116
|
const files = walkVideoFiles(destRoot);
|
|
1118
1117
|
for (const filePath of files) {
|
|
1119
1118
|
if (!force && getMediaInfo(filePath)) {
|
|
@@ -1121,7 +1120,7 @@ var probe = async ({ type, force, verbose }) => {
|
|
|
1121
1120
|
skipped++;
|
|
1122
1121
|
continue;
|
|
1123
1122
|
}
|
|
1124
|
-
spinner_default.text = `probing ${import_termkit11.Color.
|
|
1123
|
+
spinner_default.text = `probing ${import_termkit11.Color.white.encoder(filePath)}`;
|
|
1125
1124
|
const result = runFfprobe(filePath);
|
|
1126
1125
|
if (!result) {
|
|
1127
1126
|
if (verbose) spinner_default.warn(`ffprobe failed: ${filePath}`);
|
|
@@ -1205,13 +1204,13 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
1205
1204
|
const config = getConfig();
|
|
1206
1205
|
const language = config.language ?? "eng";
|
|
1207
1206
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
1208
|
-
spinner_default.text = `renaming in ${import_termkit12.Color.
|
|
1207
|
+
spinner_default.text = `renaming in ${import_termkit12.Color.white.encoder(dir)}`;
|
|
1209
1208
|
spinner_default.start();
|
|
1210
1209
|
if (!(0, import_fs11.existsSync)(dir)) throw new Error(`dir ${dir} does not exist`);
|
|
1211
1210
|
const list2 = (0, import_fs11.readdirSync)(dir);
|
|
1212
1211
|
let renamed = 0, removed = 0, skipped = 0;
|
|
1213
1212
|
for (const [index, entry] of list2.entries()) {
|
|
1214
|
-
spinner_default.text = `renaming in ${import_termkit12.Color.
|
|
1213
|
+
spinner_default.text = `renaming in ${import_termkit12.Color.white.encoder(dir)} ${index + 1}/${list2.length}`;
|
|
1215
1214
|
if (!(0, import_fs11.lstatSync)((0, import_path12.resolve)(dir, entry)).isDirectory()) {
|
|
1216
1215
|
if (verbose) spinner_default.info(`skipped ${entry}`);
|
|
1217
1216
|
skipped++;
|
|
@@ -1296,7 +1295,7 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
1296
1295
|
spinner_default.succeed(`renamed ${renamed} files`);
|
|
1297
1296
|
if (removed) spinner_default.info(`removed ${removed} files`);
|
|
1298
1297
|
spinner_default.info(`skipped ${skipped} files`);
|
|
1299
|
-
spinner_default.succeed(`done in ${import_termkit12.Color.
|
|
1298
|
+
spinner_default.succeed(`done in ${import_termkit12.Color.green.encoder(dir)}`);
|
|
1300
1299
|
spinner_default.stop();
|
|
1301
1300
|
};
|
|
1302
1301
|
var rename_default = rename;
|
|
@@ -1307,7 +1306,7 @@ var import_path13 = require("path");
|
|
|
1307
1306
|
var import_termkit13 = require("termkit");
|
|
1308
1307
|
var reset = async ({ dir: inputDir, double }) => {
|
|
1309
1308
|
let dir = inputDir;
|
|
1310
|
-
spinner_default.text = `resetting episodes in ${import_termkit13.Color.
|
|
1309
|
+
spinner_default.text = `resetting episodes in ${import_termkit13.Color.white.encoder(dir)}`;
|
|
1311
1310
|
spinner_default.start();
|
|
1312
1311
|
dir = (0, import_path13.resolve)(dir);
|
|
1313
1312
|
if (!(0, import_fs12.existsSync)(dir)) throw new Error(`dir ${dir} does not exist`);
|
|
@@ -1336,7 +1335,7 @@ var reset = async ({ dir: inputDir, double }) => {
|
|
|
1336
1335
|
const episodeFormat = getConfig().format?.episode;
|
|
1337
1336
|
let renamed = 0, skipped = other.length;
|
|
1338
1337
|
for (const [index, i] of sublist.entries()) {
|
|
1339
|
-
spinner_default.text = `resetting episodes in ${import_termkit13.Color.
|
|
1338
|
+
spinner_default.text = `resetting episodes in ${import_termkit13.Color.white.encoder(dir)} ${index}/${list2.length}`;
|
|
1340
1339
|
const ext = i.match(/([^.]+$)/)?.[0];
|
|
1341
1340
|
const episode = double ? index * 2 + 1 : index + 1;
|
|
1342
1341
|
const name = `${formatEpisode(seasonNum, episode, episodeFormat, double, showTitle)}.${ext}`;
|
|
@@ -1349,7 +1348,7 @@ var reset = async ({ dir: inputDir, double }) => {
|
|
|
1349
1348
|
}
|
|
1350
1349
|
spinner_default.succeed(`renamed ${renamed} files`);
|
|
1351
1350
|
spinner_default.info(`skipped ${skipped} files`);
|
|
1352
|
-
spinner_default.succeed(`done in ${import_termkit13.Color.
|
|
1351
|
+
spinner_default.succeed(`done in ${import_termkit13.Color.green.encoder(dir)}`);
|
|
1353
1352
|
spinner_default.stop();
|
|
1354
1353
|
};
|
|
1355
1354
|
var reset_default = reset;
|
|
@@ -1417,6 +1416,9 @@ var parseDownloadName = (name) => {
|
|
|
1417
1416
|
return { title: titleCase_default(titleTokens.join(" ")), year, type: "movie" };
|
|
1418
1417
|
};
|
|
1419
1418
|
|
|
1419
|
+
// src/refs/bookExtensions.json
|
|
1420
|
+
var bookExtensions_default = ["epub", "mobi", "azw3", "azw"];
|
|
1421
|
+
|
|
1420
1422
|
// src/actions/scan.ts
|
|
1421
1423
|
var sameDev = (a, b) => {
|
|
1422
1424
|
try {
|
|
@@ -1439,6 +1441,99 @@ var findVideo = (dir) => (0, import_fs13.readdirSync)(dir).find((f) => {
|
|
|
1439
1441
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1440
1442
|
return ext && videoExtensions_default.includes(ext);
|
|
1441
1443
|
}) ?? null;
|
|
1444
|
+
var containsBook = (dir, depth = 2) => (0, import_fs13.readdirSync)(dir).some((f) => {
|
|
1445
|
+
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1446
|
+
if (ext && bookExtensions_default.includes(ext)) return true;
|
|
1447
|
+
if (depth > 1) {
|
|
1448
|
+
try {
|
|
1449
|
+
const sub = (0, import_path14.resolve)(dir, f);
|
|
1450
|
+
if ((0, import_fs13.lstatSync)(sub).isDirectory()) return containsBook(sub, depth - 1);
|
|
1451
|
+
} catch {
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
return false;
|
|
1455
|
+
});
|
|
1456
|
+
var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
|
|
1457
|
+
var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
|
|
1458
|
+
var gatherEntries = (source) => {
|
|
1459
|
+
const result = [];
|
|
1460
|
+
for (const name of (0, import_fs13.readdirSync)(source)) {
|
|
1461
|
+
const fullPath = (0, import_path14.resolve)(source, name);
|
|
1462
|
+
let isDir;
|
|
1463
|
+
try {
|
|
1464
|
+
isDir = (0, import_fs13.lstatSync)(fullPath).isDirectory();
|
|
1465
|
+
} catch {
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
const ext = name.match(/([^.]+$)/)?.[0];
|
|
1469
|
+
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
1470
|
+
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1471
|
+
if (!isDir && !isVideo && !isBook) continue;
|
|
1472
|
+
if (!isDir) {
|
|
1473
|
+
result.push({ entry: name, entryPath: fullPath, isDir: false });
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
if (/(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name) || isTvEpisodeName(name)) {
|
|
1477
|
+
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1478
|
+
continue;
|
|
1479
|
+
}
|
|
1480
|
+
let children;
|
|
1481
|
+
try {
|
|
1482
|
+
children = (0, import_fs13.readdirSync)(fullPath);
|
|
1483
|
+
} catch {
|
|
1484
|
+
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
if (children.some((c) => isTvEpisodeName(c))) {
|
|
1488
|
+
for (const child of children) {
|
|
1489
|
+
const childPath = (0, import_path14.resolve)(fullPath, child);
|
|
1490
|
+
let childIsDir;
|
|
1491
|
+
try {
|
|
1492
|
+
childIsDir = (0, import_fs13.lstatSync)(childPath).isDirectory();
|
|
1493
|
+
} catch {
|
|
1494
|
+
continue;
|
|
1495
|
+
}
|
|
1496
|
+
const childExt = child.match(/([^.]+$)/)?.[0];
|
|
1497
|
+
if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
|
|
1498
|
+
result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
|
|
1499
|
+
}
|
|
1500
|
+
continue;
|
|
1501
|
+
}
|
|
1502
|
+
const seasonDirs = children.filter((c) => {
|
|
1503
|
+
try {
|
|
1504
|
+
return isSeasonDirName(c) && (0, import_fs13.lstatSync)((0, import_path14.resolve)(fullPath, c)).isDirectory();
|
|
1505
|
+
} catch {
|
|
1506
|
+
return false;
|
|
1507
|
+
}
|
|
1508
|
+
});
|
|
1509
|
+
if (seasonDirs.length > 0) {
|
|
1510
|
+
for (const seasonDir of seasonDirs) {
|
|
1511
|
+
const seasonPath = (0, import_path14.resolve)(fullPath, seasonDir);
|
|
1512
|
+
let seasonChildren;
|
|
1513
|
+
try {
|
|
1514
|
+
seasonChildren = (0, import_fs13.readdirSync)(seasonPath);
|
|
1515
|
+
} catch {
|
|
1516
|
+
continue;
|
|
1517
|
+
}
|
|
1518
|
+
for (const child of seasonChildren) {
|
|
1519
|
+
const childPath = (0, import_path14.resolve)(seasonPath, child);
|
|
1520
|
+
let childIsDir;
|
|
1521
|
+
try {
|
|
1522
|
+
childIsDir = (0, import_fs13.lstatSync)(childPath).isDirectory();
|
|
1523
|
+
} catch {
|
|
1524
|
+
continue;
|
|
1525
|
+
}
|
|
1526
|
+
const childExt = child.match(/([^.]+$)/)?.[0];
|
|
1527
|
+
if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
|
|
1528
|
+
result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1533
|
+
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1534
|
+
}
|
|
1535
|
+
return result;
|
|
1536
|
+
};
|
|
1442
1537
|
var findSeasonFolder = (showPath, season) => {
|
|
1443
1538
|
if (!(0, import_fs13.existsSync)(showPath)) return null;
|
|
1444
1539
|
const folders = (0, import_fs13.readdirSync)(showPath).filter((f) => {
|
|
@@ -1453,35 +1548,130 @@ var findSeasonFolder = (showPath, season) => {
|
|
|
1453
1548
|
return match && parseInt(match[1]) === season;
|
|
1454
1549
|
}) ?? null;
|
|
1455
1550
|
};
|
|
1456
|
-
var
|
|
1551
|
+
var classifyMovieConfidence = (entry) => {
|
|
1552
|
+
if (/^\[(?:Team|Group)\s/i.test(entry)) return "skip";
|
|
1553
|
+
if (/\bepisodes?\s+\d+[-–]\d+/i.test(entry)) return "skip";
|
|
1554
|
+
if (/\b(?:patch|keygen|crack)\b|\bkeys?\s*\{/i.test(entry)) return "skip";
|
|
1555
|
+
if (/\[YTS[.\-]/i.test(entry)) return "auto";
|
|
1556
|
+
if (/\(\d{4}\)/.test(entry) && /\[(?:2160p|1080p|720p|480p|576p|BluRay|BDRip|BDRemux|WEBRip|WEB-DL|HDRip|DVDRip|HDTV)/i.test(entry)) return "auto";
|
|
1557
|
+
if (/\.\d{4}\..*?(?:2160p|1080p|720p|BluRay|WEBRip|WEB-DL|HDTV)/i.test(entry)) return "auto";
|
|
1558
|
+
return "ambiguous";
|
|
1559
|
+
};
|
|
1560
|
+
var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, interactive }) => {
|
|
1457
1561
|
const config = getConfig();
|
|
1458
1562
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1459
1563
|
const language = config.language ?? "eng";
|
|
1460
1564
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
1461
1565
|
const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
|
|
1566
|
+
const lookupMovie = async (parsed) => {
|
|
1567
|
+
let tmdbId;
|
|
1568
|
+
let resolvedTitle = parsed.title;
|
|
1569
|
+
let resolvedYear = parsed.year;
|
|
1570
|
+
if (config.tmdbApiKey) {
|
|
1571
|
+
const results = await searchMovie(parsed.title, parsed.year, config.tmdbApiKey);
|
|
1572
|
+
if (results.length === 1) {
|
|
1573
|
+
tmdbId = results[0].id;
|
|
1574
|
+
resolvedTitle = results[0].title;
|
|
1575
|
+
resolvedYear = results[0].year ?? parsed.year;
|
|
1576
|
+
} else if (results.length > 1) {
|
|
1577
|
+
spinner_default.stop();
|
|
1578
|
+
const select = new import_termkit14.Select();
|
|
1579
|
+
const items = results.map((r) => ({
|
|
1580
|
+
label: r.year ? `${r.title} (${r.year})` : r.title,
|
|
1581
|
+
description: [r.overview?.slice(0, 60), hyperlink(r.url, "themoviedb.org")].filter(Boolean).join(" \xB7 "),
|
|
1582
|
+
...r
|
|
1583
|
+
}));
|
|
1584
|
+
const picked = await select.ask(`Multiple movies found for "${parsed.title}":`, items);
|
|
1585
|
+
spinner_default.start();
|
|
1586
|
+
if (picked) {
|
|
1587
|
+
tmdbId = picked.id;
|
|
1588
|
+
resolvedTitle = picked.title;
|
|
1589
|
+
resolvedYear = picked.year ?? parsed.year;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
return { tmdbId, resolvedTitle, resolvedYear };
|
|
1594
|
+
};
|
|
1595
|
+
const importMovie = async (entry, entryPath, isDir, resolvedTitle, resolvedYear, tmdbId, destRoot) => {
|
|
1596
|
+
const edition = detectEdition(entry);
|
|
1597
|
+
const folderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear, edition);
|
|
1598
|
+
const destFolder = (0, import_path14.resolve)(destRoot, folderName);
|
|
1599
|
+
if ((0, import_fs13.existsSync)(destFolder)) {
|
|
1600
|
+
spinner_default.warn(`already exists: ${folderName}`);
|
|
1601
|
+
return false;
|
|
1602
|
+
}
|
|
1603
|
+
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1604
|
+
if (!videoFile) {
|
|
1605
|
+
if (verbose) spinner_default.info(`no video found in: ${entry}`);
|
|
1606
|
+
return false;
|
|
1607
|
+
}
|
|
1608
|
+
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
1609
|
+
const destVideoName = `${folderName}.${videoExt}`;
|
|
1610
|
+
const videoSourcePath = isDir ? (0, import_path14.resolve)(entryPath, videoFile) : entryPath;
|
|
1611
|
+
const dirFiles = isDir ? (0, import_fs13.readdirSync)(entryPath) : [];
|
|
1612
|
+
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1613
|
+
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1614
|
+
const subtitleSourcePath = subtitle ? (0, import_path14.resolve)(entryPath, subtitle) : null;
|
|
1615
|
+
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
1616
|
+
if (!dryRun) {
|
|
1617
|
+
if (useHardlink) {
|
|
1618
|
+
(0, import_fs13.mkdirSync)(destFolder, { recursive: true });
|
|
1619
|
+
const destVideoPath = (0, import_path14.resolve)(destFolder, destVideoName);
|
|
1620
|
+
let mode;
|
|
1621
|
+
try {
|
|
1622
|
+
if (!sameDev(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
|
|
1623
|
+
(0, import_fs13.linkSync)(videoSourcePath, destVideoPath);
|
|
1624
|
+
mode = "hardlink";
|
|
1625
|
+
} catch {
|
|
1626
|
+
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
|
|
1627
|
+
(0, import_fs13.cpSync)(videoSourcePath, destVideoPath);
|
|
1628
|
+
mode = "copy";
|
|
1629
|
+
}
|
|
1630
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs13.cpSync)(subtitleSourcePath, (0, import_path14.resolve)(destFolder, destSubtitleName));
|
|
1631
|
+
recordImport(sessionId, entryPath, destFolder, mode, tmdbId);
|
|
1632
|
+
} else {
|
|
1633
|
+
if (isDir) {
|
|
1634
|
+
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
1635
|
+
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs13.rmSync)((0, import_path14.resolve)(entryPath, f), { recursive: true, force: true });
|
|
1636
|
+
(0, import_fs13.renameSync)(videoSourcePath, (0, import_path14.resolve)(entryPath, destVideoName));
|
|
1637
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs13.renameSync)(subtitleSourcePath, (0, import_path14.resolve)(entryPath, destSubtitleName));
|
|
1638
|
+
moveFolder(entryPath, destFolder);
|
|
1639
|
+
} else {
|
|
1640
|
+
(0, import_fs13.mkdirSync)(destFolder, { recursive: true });
|
|
1641
|
+
const destVideoPath = (0, import_path14.resolve)(destFolder, destVideoName);
|
|
1642
|
+
if (sameDev(videoSourcePath, destRoot)) {
|
|
1643
|
+
(0, import_fs13.renameSync)(videoSourcePath, destVideoPath);
|
|
1644
|
+
} else {
|
|
1645
|
+
(0, import_fs13.cpSync)(videoSourcePath, destVideoPath);
|
|
1646
|
+
(0, import_fs13.rmSync)(videoSourcePath);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${folderName}`);
|
|
1653
|
+
return true;
|
|
1654
|
+
};
|
|
1462
1655
|
spinner_default.start();
|
|
1463
1656
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
1464
1657
|
let imported = 0, skipped = 0;
|
|
1658
|
+
const pendingMovies = [];
|
|
1465
1659
|
for (const source of config.sources) {
|
|
1466
1660
|
if (!(0, import_fs13.existsSync)(source)) {
|
|
1467
|
-
spinner_default.warn(`source not found: ${import_termkit14.Color.
|
|
1661
|
+
spinner_default.warn(`source not found: ${import_termkit14.Color.white.encoder(source)}`);
|
|
1468
1662
|
continue;
|
|
1469
1663
|
}
|
|
1470
|
-
spinner_default.text = `scanning ${import_termkit14.Color.
|
|
1471
|
-
for (const entry of (
|
|
1472
|
-
const entryPath = (0, import_path14.resolve)(source, entry);
|
|
1473
|
-
const isDir = (0, import_fs13.lstatSync)(entryPath).isDirectory();
|
|
1664
|
+
spinner_default.text = `scanning ${import_termkit14.Color.white.encoder(source)}`;
|
|
1665
|
+
for (const { entry, entryPath, isDir } of gatherEntries(source)) {
|
|
1474
1666
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
1475
|
-
const
|
|
1476
|
-
|
|
1477
|
-
if (verbose) spinner_default.info(`skipped ${entry}`);
|
|
1478
|
-
skipped++;
|
|
1479
|
-
continue;
|
|
1480
|
-
}
|
|
1667
|
+
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1668
|
+
const isBookDir = isDir && containsBook(entryPath);
|
|
1481
1669
|
let detectedType;
|
|
1482
1670
|
if (type) {
|
|
1483
1671
|
detectedType = type;
|
|
1484
|
-
} else if (
|
|
1672
|
+
} else if (isBook || isBookDir) {
|
|
1673
|
+
detectedType = "book";
|
|
1674
|
+
} else if (isDir && /^[A-Z]{4}\d{5}/i.test(entry)) {
|
|
1485
1675
|
detectedType = "ps3";
|
|
1486
1676
|
} else if (/S\d{2,3}E\d{2,3}/i.test(entry) || /\d+x\d{2,3}/i.test(entry)) {
|
|
1487
1677
|
detectedType = "tv";
|
|
@@ -1516,12 +1706,49 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto }) => {
|
|
|
1516
1706
|
imported++;
|
|
1517
1707
|
continue;
|
|
1518
1708
|
}
|
|
1709
|
+
if (detectedType === "book") {
|
|
1710
|
+
const destPath = (0, import_path14.resolve)(destRoot, entry);
|
|
1711
|
+
if ((0, import_fs13.existsSync)(destPath)) {
|
|
1712
|
+
spinner_default.warn(`already exists: ${entry}`);
|
|
1713
|
+
skipped++;
|
|
1714
|
+
continue;
|
|
1715
|
+
}
|
|
1716
|
+
if (!dryRun) {
|
|
1717
|
+
if (isDir || isBookDir) {
|
|
1718
|
+
moveFolder(entryPath, destPath);
|
|
1719
|
+
} else {
|
|
1720
|
+
(0, import_fs13.mkdirSync)(destRoot, { recursive: true });
|
|
1721
|
+
if (sameDev(entryPath, destRoot)) {
|
|
1722
|
+
(0, import_fs13.renameSync)(entryPath, destPath);
|
|
1723
|
+
} else {
|
|
1724
|
+
(0, import_fs13.cpSync)(entryPath, destPath);
|
|
1725
|
+
(0, import_fs13.rmSync)(entryPath);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
recordImport(sessionId, entryPath, destPath, "move");
|
|
1729
|
+
}
|
|
1730
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${entry}`);
|
|
1731
|
+
imported++;
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1519
1734
|
const parsed = parseDownloadName(entry);
|
|
1520
1735
|
if (!parsed) {
|
|
1521
1736
|
if (verbose) spinner_default.info(`could not parse: ${entry}`);
|
|
1522
1737
|
skipped++;
|
|
1523
1738
|
continue;
|
|
1524
1739
|
}
|
|
1740
|
+
if (detectedType === "movie") {
|
|
1741
|
+
const confidence = classifyMovieConfidence(entry);
|
|
1742
|
+
if (confidence === "skip") {
|
|
1743
|
+
if (verbose) spinner_default.info(`skipped (uncertain): ${entry}`);
|
|
1744
|
+
skipped++;
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
if (confidence === "ambiguous") {
|
|
1748
|
+
pendingMovies.push({ entry, entryPath, isDir, parsed, destRoot });
|
|
1749
|
+
continue;
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1525
1752
|
let tmdbId;
|
|
1526
1753
|
let resolvedTitle = parsed.title;
|
|
1527
1754
|
let resolvedYear = parsed.year;
|
|
@@ -1549,12 +1776,10 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto }) => {
|
|
|
1549
1776
|
}
|
|
1550
1777
|
}
|
|
1551
1778
|
} else {
|
|
1552
|
-
const
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
resolvedYear = tmdb.year ?? parsed.year;
|
|
1557
|
-
}
|
|
1779
|
+
const result = await lookupMovie(parsed);
|
|
1780
|
+
tmdbId = result.tmdbId;
|
|
1781
|
+
resolvedTitle = result.resolvedTitle;
|
|
1782
|
+
resolvedYear = result.resolvedYear;
|
|
1558
1783
|
}
|
|
1559
1784
|
}
|
|
1560
1785
|
if (detectedType === "tv") {
|
|
@@ -1580,50 +1805,68 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto }) => {
|
|
|
1580
1805
|
}
|
|
1581
1806
|
const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1582
1807
|
const seasonPath = (0, import_path14.resolve)(showPath, seasonFolderName);
|
|
1583
|
-
const
|
|
1584
|
-
if (!
|
|
1808
|
+
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1809
|
+
if (!videoFile) {
|
|
1585
1810
|
if (verbose) spinner_default.info(`no video found in: ${entry}`);
|
|
1586
1811
|
skipped++;
|
|
1587
1812
|
continue;
|
|
1588
1813
|
}
|
|
1589
|
-
const
|
|
1814
|
+
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
1590
1815
|
const tmdbEpisodeName = tmdbId && config.tmdbApiKey ? await getEpisodeName(tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
|
|
1591
1816
|
const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, resolvedTitle, tmdbEpisodeName ?? void 0);
|
|
1592
|
-
const
|
|
1593
|
-
const destVideoPath = (0, import_path14.resolve)(seasonPath,
|
|
1594
|
-
const
|
|
1817
|
+
const destVideoName = `${episodeName}.${videoExt}`;
|
|
1818
|
+
const destVideoPath = (0, import_path14.resolve)(seasonPath, destVideoName);
|
|
1819
|
+
const videoSourcePath = isDir ? (0, import_path14.resolve)(entryPath, videoFile) : entryPath;
|
|
1595
1820
|
if ((0, import_fs13.existsSync)(destVideoPath)) {
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1821
|
+
let shouldReplace = force;
|
|
1822
|
+
if (!shouldReplace && interactive) {
|
|
1823
|
+
spinner_default.stop();
|
|
1824
|
+
const select = new import_termkit14.Select();
|
|
1825
|
+
const picked = await select.ask(`Already exists \u2014 replace?`, [
|
|
1826
|
+
{ label: `${showFolderName} / ${seasonFolderName} / ${episodeName}`, value: "replace" },
|
|
1827
|
+
{ label: "Skip", value: "skip" }
|
|
1828
|
+
]);
|
|
1829
|
+
spinner_default.start();
|
|
1830
|
+
shouldReplace = picked?.value === "replace";
|
|
1831
|
+
}
|
|
1832
|
+
if (!shouldReplace) {
|
|
1833
|
+
spinner_default.warn(`already exists: ${episodeName}`);
|
|
1834
|
+
skipped++;
|
|
1835
|
+
continue;
|
|
1836
|
+
}
|
|
1837
|
+
if (!dryRun) {
|
|
1838
|
+
for (const f of (0, import_fs13.readdirSync)(seasonPath)) {
|
|
1839
|
+
if (f.startsWith(`${episodeName}.`)) (0, import_fs13.rmSync)((0, import_path14.resolve)(seasonPath, f));
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1599
1842
|
}
|
|
1600
|
-
const
|
|
1601
|
-
const
|
|
1602
|
-
const
|
|
1603
|
-
const
|
|
1604
|
-
const
|
|
1843
|
+
const dirFiles = isDir ? (0, import_fs13.readdirSync)(entryPath) : [];
|
|
1844
|
+
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1845
|
+
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1846
|
+
const subtitleSourcePath = subtitle ? (0, import_path14.resolve)(entryPath, subtitle) : null;
|
|
1847
|
+
const destSubtitleName = subtitle && subtitleExt ? `${episodeName}.${subtitleExt}` : null;
|
|
1605
1848
|
if (!dryRun) {
|
|
1606
1849
|
(0, import_fs13.mkdirSync)(seasonPath, { recursive: true });
|
|
1607
1850
|
let mode = "move";
|
|
1608
1851
|
if (useHardlink) {
|
|
1609
1852
|
try {
|
|
1610
|
-
if (!sameDev(
|
|
1611
|
-
(0, import_fs13.linkSync)(
|
|
1853
|
+
if (!sameDev(videoSourcePath, seasonPath)) throw new Error("cross-filesystem");
|
|
1854
|
+
(0, import_fs13.linkSync)(videoSourcePath, destVideoPath);
|
|
1612
1855
|
mode = "hardlink";
|
|
1613
1856
|
} catch {
|
|
1614
1857
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
|
|
1615
|
-
(0, import_fs13.cpSync)(
|
|
1858
|
+
(0, import_fs13.cpSync)(videoSourcePath, destVideoPath);
|
|
1616
1859
|
mode = "copy";
|
|
1617
1860
|
}
|
|
1618
|
-
if (
|
|
1861
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs13.cpSync)(subtitleSourcePath, (0, import_path14.resolve)(seasonPath, destSubtitleName));
|
|
1619
1862
|
} else {
|
|
1620
|
-
if (sameDev(
|
|
1621
|
-
(0, import_fs13.renameSync)(
|
|
1863
|
+
if (sameDev(videoSourcePath, seasonPath)) {
|
|
1864
|
+
(0, import_fs13.renameSync)(videoSourcePath, destVideoPath);
|
|
1622
1865
|
} else {
|
|
1623
|
-
(0, import_fs13.cpSync)(
|
|
1624
|
-
(0, import_fs13.rmSync)(
|
|
1866
|
+
(0, import_fs13.cpSync)(videoSourcePath, destVideoPath);
|
|
1867
|
+
(0, import_fs13.rmSync)(videoSourcePath);
|
|
1625
1868
|
}
|
|
1626
|
-
if (
|
|
1869
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs13.renameSync)(subtitleSourcePath, (0, import_path14.resolve)(seasonPath, destSubtitleName));
|
|
1627
1870
|
if (isDir) (0, import_fs13.rmSync)(entryPath, { recursive: true, force: true });
|
|
1628
1871
|
}
|
|
1629
1872
|
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
@@ -1632,69 +1875,43 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto }) => {
|
|
|
1632
1875
|
imported++;
|
|
1633
1876
|
continue;
|
|
1634
1877
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
if ((0, import_fs13.existsSync)(destFolder)) {
|
|
1639
|
-
spinner_default.warn(`already exists: ${folderName}`);
|
|
1878
|
+
if (await importMovie(entry, entryPath, isDir, resolvedTitle, resolvedYear, tmdbId, destRoot)) {
|
|
1879
|
+
imported++;
|
|
1880
|
+
} else {
|
|
1640
1881
|
skipped++;
|
|
1641
|
-
continue;
|
|
1642
1882
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
if (pendingMovies.length > 0) {
|
|
1886
|
+
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(` ? ${p.entry.replace(/\/$/, "")}`);
|
|
1888
|
+
let toProcess = [];
|
|
1889
|
+
if (interactive) {
|
|
1890
|
+
spinner_default.stop();
|
|
1891
|
+
const ms = new import_termkit14.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
|
|
1892
|
+
const items = pendingMovies.map((p) => ({
|
|
1893
|
+
label: p.entry.replace(/\/$/, ""),
|
|
1894
|
+
description: p.parsed.year ? `parsed: ${p.parsed.title} \xB7 ${p.parsed.year}` : `parsed: ${p.parsed.title}`,
|
|
1895
|
+
...p
|
|
1896
|
+
}));
|
|
1897
|
+
toProcess = await ms.ask("Select entries to import as movies:", items) ?? [];
|
|
1898
|
+
spinner_default.start();
|
|
1899
|
+
skipped += pendingMovies.length - toProcess.length;
|
|
1900
|
+
} else if (force) {
|
|
1901
|
+
toProcess = pendingMovies;
|
|
1902
|
+
} else {
|
|
1903
|
+
skipped += pendingMovies.length;
|
|
1904
|
+
}
|
|
1905
|
+
for (const p of toProcess) {
|
|
1906
|
+
const { tmdbId, resolvedTitle, resolvedYear } = await lookupMovie(p.parsed);
|
|
1907
|
+
if (await importMovie(p.entry, p.entryPath, p.isDir, resolvedTitle, resolvedYear, tmdbId, p.destRoot)) {
|
|
1908
|
+
imported++;
|
|
1909
|
+
} else {
|
|
1646
1910
|
skipped++;
|
|
1647
|
-
continue;
|
|
1648
|
-
}
|
|
1649
|
-
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
1650
|
-
const destVideoName = `${folderName}.${videoExt}`;
|
|
1651
|
-
const videoSourcePath = isDir ? (0, import_path14.resolve)(entryPath, videoFile) : entryPath;
|
|
1652
|
-
const dirFiles = isDir ? (0, import_fs13.readdirSync)(entryPath) : [];
|
|
1653
|
-
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1654
|
-
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1655
|
-
const subtitleSourcePath = subtitle ? (0, import_path14.resolve)(entryPath, subtitle) : null;
|
|
1656
|
-
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
1657
|
-
if (!dryRun) {
|
|
1658
|
-
if (useHardlink) {
|
|
1659
|
-
(0, import_fs13.mkdirSync)(destFolder, { recursive: true });
|
|
1660
|
-
const destVideoPath = (0, import_path14.resolve)(destFolder, destVideoName);
|
|
1661
|
-
let mode;
|
|
1662
|
-
try {
|
|
1663
|
-
if (!sameDev(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
|
|
1664
|
-
(0, import_fs13.linkSync)(videoSourcePath, destVideoPath);
|
|
1665
|
-
mode = "hardlink";
|
|
1666
|
-
} catch {
|
|
1667
|
-
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
|
|
1668
|
-
(0, import_fs13.cpSync)(videoSourcePath, destVideoPath);
|
|
1669
|
-
mode = "copy";
|
|
1670
|
-
}
|
|
1671
|
-
if (subtitleSourcePath && destSubtitleName) (0, import_fs13.cpSync)(subtitleSourcePath, (0, import_path14.resolve)(destFolder, destSubtitleName));
|
|
1672
|
-
recordImport(sessionId, entryPath, destFolder, mode, tmdbId);
|
|
1673
|
-
} else {
|
|
1674
|
-
if (isDir) {
|
|
1675
|
-
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
1676
|
-
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs13.rmSync)((0, import_path14.resolve)(entryPath, f), { recursive: true, force: true });
|
|
1677
|
-
(0, import_fs13.renameSync)(videoSourcePath, (0, import_path14.resolve)(entryPath, destVideoName));
|
|
1678
|
-
if (subtitleSourcePath && destSubtitleName) (0, import_fs13.renameSync)(subtitleSourcePath, (0, import_path14.resolve)(entryPath, destSubtitleName));
|
|
1679
|
-
moveFolder(entryPath, destFolder);
|
|
1680
|
-
} else {
|
|
1681
|
-
(0, import_fs13.mkdirSync)(destFolder, { recursive: true });
|
|
1682
|
-
const destVideoPath = (0, import_path14.resolve)(destFolder, destVideoName);
|
|
1683
|
-
if (sameDev(videoSourcePath, destRoot)) {
|
|
1684
|
-
(0, import_fs13.renameSync)(videoSourcePath, destVideoPath);
|
|
1685
|
-
} else {
|
|
1686
|
-
(0, import_fs13.cpSync)(videoSourcePath, destVideoPath);
|
|
1687
|
-
(0, import_fs13.rmSync)(videoSourcePath);
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1691
|
-
}
|
|
1692
1911
|
}
|
|
1693
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${folderName}`);
|
|
1694
|
-
imported++;
|
|
1695
1912
|
}
|
|
1696
1913
|
}
|
|
1697
|
-
spinner_default.succeed(
|
|
1914
|
+
spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
|
|
1698
1915
|
if (skipped) spinner_default.info(`skipped ${skipped} items`);
|
|
1699
1916
|
spinner_default.stop();
|
|
1700
1917
|
};
|
|
@@ -1714,7 +1931,7 @@ var shows = async () => {
|
|
|
1714
1931
|
return;
|
|
1715
1932
|
}
|
|
1716
1933
|
console.log(`
|
|
1717
|
-
${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termkit15.Color.
|
|
1934
|
+
${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termkit15.Color.white.encoder(destRoot)}` : ""} (${allShows.length} registered)`);
|
|
1718
1935
|
new import_termkit15.Table(
|
|
1719
1936
|
allShows.map((show) => ({
|
|
1720
1937
|
name: show.path.split("/").pop() ?? show.path,
|
|
@@ -1828,7 +2045,7 @@ var undo = async () => {
|
|
|
1828
2045
|
let undone = 0;
|
|
1829
2046
|
for (const record of records) {
|
|
1830
2047
|
(0, import_fs16.renameSync)(record.newPath, record.oldPath);
|
|
1831
|
-
spinner_default.succeed(`${import_termkit17.Color.
|
|
2048
|
+
spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.newPath)} \u2192 ${import_termkit17.Color.white.encoder(record.oldPath)}`);
|
|
1832
2049
|
undone++;
|
|
1833
2050
|
}
|
|
1834
2051
|
deleteSession(records[0].sessionId);
|
|
@@ -1863,6 +2080,86 @@ var findVideo2 = (dir) => (0, import_fs17.readdirSync)(dir).find((f) => {
|
|
|
1863
2080
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1864
2081
|
return ext && videoExtensions_default.includes(ext);
|
|
1865
2082
|
}) ?? null;
|
|
2083
|
+
var containsBook2 = (dir, depth = 2) => (0, import_fs17.readdirSync)(dir).some((f) => {
|
|
2084
|
+
const ext = f.match(/([^.]+$)/)?.[0];
|
|
2085
|
+
if (ext && bookExtensions_default.includes(ext)) return true;
|
|
2086
|
+
if (depth > 1) {
|
|
2087
|
+
try {
|
|
2088
|
+
const sub = (0, import_path16.resolve)(dir, f);
|
|
2089
|
+
if ((0, import_fs17.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
|
|
2090
|
+
} catch {
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
return false;
|
|
2094
|
+
});
|
|
2095
|
+
var isTvEpisodeName2 = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
|
|
2096
|
+
var isSeasonDirName2 = (name) => !isTvEpisodeName2(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
|
|
2097
|
+
var expandWatchPath = (p) => {
|
|
2098
|
+
let isDir;
|
|
2099
|
+
try {
|
|
2100
|
+
isDir = (0, import_fs17.lstatSync)(p).isDirectory();
|
|
2101
|
+
} catch {
|
|
2102
|
+
return [p];
|
|
2103
|
+
}
|
|
2104
|
+
if (!isDir) return [p];
|
|
2105
|
+
const name = (0, import_path16.basename)(p);
|
|
2106
|
+
if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
|
|
2107
|
+
let children;
|
|
2108
|
+
try {
|
|
2109
|
+
children = (0, import_fs17.readdirSync)(p);
|
|
2110
|
+
} catch {
|
|
2111
|
+
return [p];
|
|
2112
|
+
}
|
|
2113
|
+
if (children.some((c) => isTvEpisodeName2(c))) {
|
|
2114
|
+
const entries = [];
|
|
2115
|
+
for (const child of children) {
|
|
2116
|
+
const cp = (0, import_path16.resolve)(p, child);
|
|
2117
|
+
let cd;
|
|
2118
|
+
try {
|
|
2119
|
+
cd = (0, import_fs17.lstatSync)(cp).isDirectory();
|
|
2120
|
+
} catch {
|
|
2121
|
+
continue;
|
|
2122
|
+
}
|
|
2123
|
+
const ext = child.match(/([^.]+$)/)?.[0];
|
|
2124
|
+
if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
|
|
2125
|
+
entries.push(cp);
|
|
2126
|
+
}
|
|
2127
|
+
return entries.length > 0 ? entries : [p];
|
|
2128
|
+
}
|
|
2129
|
+
const seasonDirs = children.filter((c) => {
|
|
2130
|
+
try {
|
|
2131
|
+
return isSeasonDirName2(c) && (0, import_fs17.lstatSync)((0, import_path16.resolve)(p, c)).isDirectory();
|
|
2132
|
+
} catch {
|
|
2133
|
+
return false;
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
if (seasonDirs.length > 0) {
|
|
2137
|
+
const entries = [];
|
|
2138
|
+
for (const sd of seasonDirs) {
|
|
2139
|
+
const sp = (0, import_path16.resolve)(p, sd);
|
|
2140
|
+
let sc;
|
|
2141
|
+
try {
|
|
2142
|
+
sc = (0, import_fs17.readdirSync)(sp);
|
|
2143
|
+
} catch {
|
|
2144
|
+
continue;
|
|
2145
|
+
}
|
|
2146
|
+
for (const child of sc) {
|
|
2147
|
+
const cp = (0, import_path16.resolve)(sp, child);
|
|
2148
|
+
let cd;
|
|
2149
|
+
try {
|
|
2150
|
+
cd = (0, import_fs17.lstatSync)(cp).isDirectory();
|
|
2151
|
+
} catch {
|
|
2152
|
+
continue;
|
|
2153
|
+
}
|
|
2154
|
+
const ext = child.match(/([^.]+$)/)?.[0];
|
|
2155
|
+
if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
|
|
2156
|
+
entries.push(cp);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
return entries.length > 0 ? entries : [p];
|
|
2160
|
+
}
|
|
2161
|
+
return [p];
|
|
2162
|
+
};
|
|
1866
2163
|
var findSeasonFolder2 = (showPath, season) => {
|
|
1867
2164
|
if (!(0, import_fs17.existsSync)(showPath)) return null;
|
|
1868
2165
|
const folders = (0, import_fs17.readdirSync)(showPath).filter((f) => {
|
|
@@ -1886,9 +2183,13 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1886
2183
|
const isDir = (0, import_fs17.lstatSync)(entryPath).isDirectory();
|
|
1887
2184
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
1888
2185
|
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
1889
|
-
|
|
2186
|
+
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
2187
|
+
const isBookDir = isDir && containsBook2(entryPath);
|
|
2188
|
+
if (!isDir && !isVideo && !isBook) return;
|
|
1890
2189
|
let detectedType;
|
|
1891
|
-
if (
|
|
2190
|
+
if (isBook || isBookDir) {
|
|
2191
|
+
detectedType = "book";
|
|
2192
|
+
} else if (isDir && /(?<=\[).+?(?=\])/.test(entry)) {
|
|
1892
2193
|
detectedType = "ps3";
|
|
1893
2194
|
} else if (/S\d{2,3}E\d{2,3}/i.test(entry) || /\d+x\d{2,3}/i.test(entry)) {
|
|
1894
2195
|
detectedType = "tv";
|
|
@@ -1912,7 +2213,28 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1912
2213
|
}
|
|
1913
2214
|
moveItem(entryPath, destPath);
|
|
1914
2215
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1915
|
-
spinner_default.succeed(`imported ${import_termkit18.Color.
|
|
2216
|
+
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(destName)}`);
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
if (detectedType === "book") {
|
|
2220
|
+
const destPath = (0, import_path16.resolve)(destRoot, entry);
|
|
2221
|
+
if ((0, import_fs17.existsSync)(destPath)) {
|
|
2222
|
+
spinner_default.warn(`already exists: ${entry}`);
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
if (isDir || isBookDir) {
|
|
2226
|
+
moveItem(entryPath, destPath);
|
|
2227
|
+
} else {
|
|
2228
|
+
(0, import_fs17.mkdirSync)(destRoot, { recursive: true });
|
|
2229
|
+
if (sameDev2(entryPath, destRoot)) {
|
|
2230
|
+
(0, import_fs17.renameSync)(entryPath, destPath);
|
|
2231
|
+
} else {
|
|
2232
|
+
(0, import_fs17.cpSync)(entryPath, destPath);
|
|
2233
|
+
(0, import_fs17.rmSync)(entryPath);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
recordImport(sessionId, entryPath, destPath, "move");
|
|
2237
|
+
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(entry)}`);
|
|
1916
2238
|
return;
|
|
1917
2239
|
}
|
|
1918
2240
|
const parsed = parseDownloadName(entry);
|
|
@@ -1985,7 +2307,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1985
2307
|
if (isDir) (0, import_fs17.rmSync)(entryPath, { recursive: true, force: true });
|
|
1986
2308
|
}
|
|
1987
2309
|
recordImport(sessionId, entryPath, seasonPath, mode);
|
|
1988
|
-
spinner_default.succeed(`imported ${import_termkit18.Color.
|
|
2310
|
+
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
|
|
1989
2311
|
return;
|
|
1990
2312
|
}
|
|
1991
2313
|
const edition = detectEdition(entry);
|
|
@@ -2042,7 +2364,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
2042
2364
|
}
|
|
2043
2365
|
recordImport(sessionId, entryPath, destFolder, "move");
|
|
2044
2366
|
}
|
|
2045
|
-
spinner_default.succeed(`imported ${import_termkit18.Color.
|
|
2367
|
+
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(folderName)}`);
|
|
2046
2368
|
};
|
|
2047
2369
|
var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
2048
2370
|
const config = getConfig();
|
|
@@ -2057,7 +2379,9 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
2057
2379
|
setTimeout(async () => {
|
|
2058
2380
|
pending.delete(path);
|
|
2059
2381
|
try {
|
|
2060
|
-
|
|
2382
|
+
for (const entry of expandWatchPath(path)) {
|
|
2383
|
+
await processItem(entry, hardlink, verbose, language, auto);
|
|
2384
|
+
}
|
|
2061
2385
|
} catch (err) {
|
|
2062
2386
|
spinner_default.fail(`error processing ${path}: ${err.message}`);
|
|
2063
2387
|
}
|
|
@@ -2073,31 +2397,35 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
2073
2397
|
watcher.on("add", handle);
|
|
2074
2398
|
spinner_default.start();
|
|
2075
2399
|
spinner_default.succeed(`watching ${config.sources.length} source${config.sources.length !== 1 ? "s" : ""}`);
|
|
2076
|
-
for (const s of config.sources) spinner_default.info(` ${import_termkit18.Color.
|
|
2400
|
+
for (const s of config.sources) spinner_default.info(` ${import_termkit18.Color.white.encoder(s)}`);
|
|
2077
2401
|
spinner_default.stop();
|
|
2078
2402
|
process.stdin.resume();
|
|
2079
2403
|
};
|
|
2080
2404
|
var watch_default = watch;
|
|
2081
2405
|
|
|
2082
2406
|
// package.json
|
|
2083
|
-
var version = "0.2.
|
|
2407
|
+
var version = "0.2.2";
|
|
2084
2408
|
|
|
2085
2409
|
// src/program.ts
|
|
2086
|
-
var
|
|
2410
|
+
var toCamel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
2411
|
+
var adapt = (fn) => (options) => {
|
|
2412
|
+
const camel = Object.fromEntries(Object.entries(options).map(([k, v]) => [toCamel(k), v]));
|
|
2413
|
+
return fn(camel);
|
|
2414
|
+
};
|
|
2087
2415
|
var { command, option } = import_termkit19.Program;
|
|
2088
2416
|
var program = import_termkit19.Program.command("reelsort").version(version).description("a cli to manage media").commands([
|
|
2089
2417
|
command("config").description("manage configuration").commands([
|
|
2090
2418
|
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))]),
|
|
2091
|
-
command("dest").description("manage destinations").commands([command("add", "<type> <dir>").description("set a destination (movie, tv, ps3)").action(adapt(destAdd)), command("remove", "<type>").description("remove a destination (movie, tv, ps3)").action(adapt(destRemove))]),
|
|
2419
|
+
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))]),
|
|
2092
2420
|
command("set", "<key> <subkey> [value]").description("set a value (e.g. set language eng)").action(adapt(configSet)),
|
|
2093
2421
|
command("show").description("show current configuration").action(adapt(configShow))
|
|
2094
2422
|
]),
|
|
2095
|
-
command("rename", "<dir>").description("rename media files in directory").options([option("t", "type", "<type>", "media type: movie, tv, ps3"), option("v", "verbose", null, "additional output")]).action(adapt(rename_default)),
|
|
2423
|
+
command("rename", "<dir>").description("rename media files in directory").options([option("t", "type", "<type>", "media type: movie, tv, ps3, book"), option("v", "verbose", null, "additional output")]).action(adapt(rename_default)),
|
|
2096
2424
|
command("reset", "<dir>").description("reset episode names (sxee)").options([option("d", "double", null, "episodes are doubles")]).action(adapt(reset_default)),
|
|
2097
|
-
command("probe").description("index library metadata using ffprobe").options([option("t", "type", "<type>", "media type: movie, tv, ps3"), option("f", "force", null, "re-probe files already indexed"), option("v", "verbose", null, "show each file as it is probed")]).action(adapt(probe_default)),
|
|
2098
|
-
command("list").description("list library contents").options([option("t", "type", "<type>", "media type: movie, tv, ps3"), 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)),
|
|
2425
|
+
command("probe").description("index library metadata using ffprobe").options([option("t", "type", "<type>", "media type: movie, tv, ps3, book"), option("f", "force", null, "re-probe files already indexed"), option("v", "verbose", null, "show each file as it is probed")]).action(adapt(probe_default)),
|
|
2426
|
+
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)),
|
|
2099
2427
|
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)),
|
|
2100
|
-
command("scan").description("import media from configured sources to destinations").options([option("t", "type", "<type>", "only process this media type: movie, tv, ps3"), 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")]).action(adapt(scan_default)),
|
|
2428
|
+
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)),
|
|
2101
2429
|
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)),
|
|
2102
2430
|
command("undo").description("undo the last rename session").action(adapt(undo_default)),
|
|
2103
2431
|
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)),
|