reelsort 0.2.5 → 0.2.7
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 +266 -172
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +252 -158
- package/dist/index.mjs +157 -63
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1072,7 +1072,7 @@ var reset = async ({ dir: inputDir, double }) => {
|
|
|
1072
1072
|
var reset_default = reset;
|
|
1073
1073
|
|
|
1074
1074
|
// src/actions/scan.ts
|
|
1075
|
-
import { cpSync, existsSync as
|
|
1075
|
+
import { cpSync, existsSync as existsSync10, linkSync, lstatSync as lstatSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync7, renameSync as renameSync4, rmSync as rmSync3, statSync as statSync2 } from "fs";
|
|
1076
1076
|
import { dirname as dirname2, resolve as resolve8 } from "path";
|
|
1077
1077
|
import { Color as Color9, MultiSelect as MultiSelect2, Select as Select2 } from "termkit";
|
|
1078
1078
|
|
|
@@ -1097,10 +1097,49 @@ var detectEdition = (filename) => {
|
|
|
1097
1097
|
// src/helpers/hyperlink.ts
|
|
1098
1098
|
var hyperlink = (url, text) => `\x1B]8;;${url}\x1B\\${text}\x1B]8;;\x1B\\`;
|
|
1099
1099
|
|
|
1100
|
+
// src/helpers/trash.ts
|
|
1101
|
+
import { execSync } from "child_process";
|
|
1102
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, renameSync as renameSync3, rmSync as rmSync2 } from "fs";
|
|
1103
|
+
import { homedir as homedir3 } from "os";
|
|
1104
|
+
import { basename as basename3, join as join3 } from "path";
|
|
1105
|
+
var trashDir = () => {
|
|
1106
|
+
if (process.platform === "linux") return join3(homedir3(), ".local", "share", "Trash", "files");
|
|
1107
|
+
return join3(homedir3(), ".Trash");
|
|
1108
|
+
};
|
|
1109
|
+
var trashPath = (filePath, opts) => {
|
|
1110
|
+
if (process.platform === "darwin" || process.platform === "linux") {
|
|
1111
|
+
try {
|
|
1112
|
+
const dir = trashDir();
|
|
1113
|
+
if (!existsSync9(dir)) mkdirSync3(dir, { recursive: true });
|
|
1114
|
+
const name = basename3(filePath);
|
|
1115
|
+
let dest = join3(dir, name);
|
|
1116
|
+
let i = 1;
|
|
1117
|
+
while (existsSync9(dest)) dest = join3(dir, `${name} ${i++}`);
|
|
1118
|
+
renameSync3(filePath, dest);
|
|
1119
|
+
return;
|
|
1120
|
+
} catch {
|
|
1121
|
+
}
|
|
1122
|
+
} else if (process.platform === "win32") {
|
|
1123
|
+
try {
|
|
1124
|
+
const escaped = filePath.replace(/'/g, "''");
|
|
1125
|
+
const isDir = opts?.recursive ?? false;
|
|
1126
|
+
const method = isDir ? "DeleteDirectory" : "DeleteFile";
|
|
1127
|
+
execSync(
|
|
1128
|
+
`powershell -NoProfile -NonInteractive -Command "Add-Type -AssemblyName Microsoft.VisualBasic; [Microsoft.VisualBasic.FileIO.FileSystem]::${method}('${escaped}', 'OnlyErrorDialogs', 'SendToRecycleBin')"`,
|
|
1129
|
+
{ stdio: "ignore" }
|
|
1130
|
+
);
|
|
1131
|
+
return;
|
|
1132
|
+
} catch {
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
rmSync2(filePath, { recursive: opts?.recursive ?? false, force: true });
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1100
1138
|
// src/helpers/parseDownloadName.ts
|
|
1101
1139
|
var QUALITY_TOKENS = /* @__PURE__ */ new Set(["480p", "576p", "720p", "1080p", "2160p", "4k", "8k", "bluray", "bdrip", "bdremux", "brrip", "webrip", "web-dl", "webdl", "web", "hdtv", "dvdrip", "dvdscr", "cam", "ts", "scr", "x264", "x265", "hevc", "avc", "h264", "h265", "xvid", "divx", "dts", "ac3", "aac", "mp3", "truehd", "atmos", "dd5", "hdr", "hdr10", "hlg", "dv", "dolby", "remux", "proper", "repack", "extended", "theatrical", "unrated", "multi", "dubbed", "subbed", "internal"]);
|
|
1102
1140
|
var TV_PATTERN = /^(.*?)[.\s_-]*(?:S(\d{2,3})E(\d{2,3})|(\d{1,2})x(\d{2,3})|Season[\s.](\d+))/i;
|
|
1103
|
-
var parseDownloadName = (
|
|
1141
|
+
var parseDownloadName = (rawName) => {
|
|
1142
|
+
const name = rawName.replace(/^[\w.-]+\.\w{2,6}\s+-+\s+/i, "");
|
|
1104
1143
|
const base = name.replace(/\.[a-z0-9]{2,4}$/i, "");
|
|
1105
1144
|
const tvMatch = TV_PATTERN.exec(base);
|
|
1106
1145
|
if (tvMatch) {
|
|
@@ -1199,7 +1238,7 @@ var bookExtensions_default = ["epub", "mobi", "azw3", "azw"];
|
|
|
1199
1238
|
var sameDev = (a, b) => {
|
|
1200
1239
|
try {
|
|
1201
1240
|
let bExisting = b;
|
|
1202
|
-
while (!
|
|
1241
|
+
while (!existsSync10(bExisting)) bExisting = dirname2(bExisting);
|
|
1203
1242
|
return statSync2(a).dev === statSync2(bExisting).dev;
|
|
1204
1243
|
} catch {
|
|
1205
1244
|
return false;
|
|
@@ -1207,10 +1246,10 @@ var sameDev = (a, b) => {
|
|
|
1207
1246
|
};
|
|
1208
1247
|
var moveFolder = (src, dest) => {
|
|
1209
1248
|
if (sameDev(src, dest)) {
|
|
1210
|
-
|
|
1249
|
+
renameSync4(src, dest);
|
|
1211
1250
|
} else {
|
|
1212
1251
|
cpSync(src, dest, { recursive: true });
|
|
1213
|
-
|
|
1252
|
+
trashPath(src, { recursive: true });
|
|
1214
1253
|
}
|
|
1215
1254
|
};
|
|
1216
1255
|
var findVideo = (dir) => readdirSync7(dir).find((f) => {
|
|
@@ -1229,6 +1268,24 @@ var containsBook = (dir, depth = 2) => readdirSync7(dir).some((f) => {
|
|
|
1229
1268
|
}
|
|
1230
1269
|
return false;
|
|
1231
1270
|
});
|
|
1271
|
+
var containsPdf = (dir) => {
|
|
1272
|
+
try {
|
|
1273
|
+
return readdirSync7(dir).some((f) => /\.pdf$/i.test(f));
|
|
1274
|
+
} catch {
|
|
1275
|
+
return false;
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
var countVideos = (dir) => {
|
|
1279
|
+
try {
|
|
1280
|
+
return readdirSync7(dir).filter((f) => {
|
|
1281
|
+
if (/\bsample\b/i.test(f)) return false;
|
|
1282
|
+
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1283
|
+
return !!(ext && videoExtensions_default.includes(ext));
|
|
1284
|
+
}).length;
|
|
1285
|
+
} catch {
|
|
1286
|
+
return 0;
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1232
1289
|
var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
|
|
1233
1290
|
var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
|
|
1234
1291
|
var gatherEntries = (source) => {
|
|
@@ -1311,7 +1368,7 @@ var gatherEntries = (source) => {
|
|
|
1311
1368
|
return result;
|
|
1312
1369
|
};
|
|
1313
1370
|
var findShowFolder = (destRoot, title) => {
|
|
1314
|
-
if (!
|
|
1371
|
+
if (!existsSync10(destRoot)) return null;
|
|
1315
1372
|
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1316
1373
|
const target = normalize(title);
|
|
1317
1374
|
return readdirSync7(destRoot).filter((f) => {
|
|
@@ -1323,7 +1380,7 @@ var findShowFolder = (destRoot, title) => {
|
|
|
1323
1380
|
}).find((f) => normalize(f) === target) ?? null;
|
|
1324
1381
|
};
|
|
1325
1382
|
var findShowFolderByContent = (destRoot, title) => {
|
|
1326
|
-
if (!
|
|
1383
|
+
if (!existsSync10(destRoot)) return null;
|
|
1327
1384
|
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1328
1385
|
const target = normalize(title);
|
|
1329
1386
|
const matchesTitle = (name) => {
|
|
@@ -1351,8 +1408,8 @@ var findShowFolderByContent = (destRoot, title) => {
|
|
|
1351
1408
|
}
|
|
1352
1409
|
return null;
|
|
1353
1410
|
};
|
|
1354
|
-
var findSeasonFolder = (showPath, season) => {
|
|
1355
|
-
if (!
|
|
1411
|
+
var findSeasonFolder = (showPath, season, specialsFolder) => {
|
|
1412
|
+
if (!existsSync10(showPath)) return null;
|
|
1356
1413
|
const folders = readdirSync7(showPath).filter((f) => {
|
|
1357
1414
|
try {
|
|
1358
1415
|
return lstatSync4(resolve8(showPath, f)).isDirectory();
|
|
@@ -1360,6 +1417,10 @@ var findSeasonFolder = (showPath, season) => {
|
|
|
1360
1417
|
return false;
|
|
1361
1418
|
}
|
|
1362
1419
|
});
|
|
1420
|
+
if (season === 0 && specialsFolder) {
|
|
1421
|
+
const existing = folders.find((f) => f.toLowerCase() === specialsFolder.toLowerCase());
|
|
1422
|
+
if (existing) return existing;
|
|
1423
|
+
}
|
|
1363
1424
|
return folders.find((f) => {
|
|
1364
1425
|
const match = f.match(/(?:season|s)\s*0*(\d+)/i);
|
|
1365
1426
|
return match && parseInt(match[1]) === season;
|
|
@@ -1388,11 +1449,13 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1388
1449
|
const language = config.language ?? "eng";
|
|
1389
1450
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
1390
1451
|
const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
|
|
1452
|
+
const specialsFolder = config.specialsFolder ?? "Specials";
|
|
1391
1453
|
const lookupMovie = async (parsed) => {
|
|
1392
1454
|
let tmdbId;
|
|
1393
1455
|
let resolvedTitle = parsed.title;
|
|
1394
1456
|
let resolvedYear = parsed.year;
|
|
1395
1457
|
if (config.tmdbApiKey) {
|
|
1458
|
+
spinner_default.text = `TMDb: ${parsed.title}`;
|
|
1396
1459
|
const results = await searchMovie(parsed.title, parsed.year, config.tmdbApiKey);
|
|
1397
1460
|
if (results.length === 1) {
|
|
1398
1461
|
tmdbId = results[0].id;
|
|
@@ -1421,7 +1484,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1421
1484
|
const edition = detectEdition(entry);
|
|
1422
1485
|
const folderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear, edition);
|
|
1423
1486
|
const destFolder = resolve8(destRoot, folderName);
|
|
1424
|
-
if (
|
|
1487
|
+
if (existsSync10(destFolder)) {
|
|
1425
1488
|
spinner_default.warn(`already exists: ${folderName}`);
|
|
1426
1489
|
return false;
|
|
1427
1490
|
}
|
|
@@ -1440,7 +1503,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1440
1503
|
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
1441
1504
|
if (!dryRun) {
|
|
1442
1505
|
if (useHardlink) {
|
|
1443
|
-
|
|
1506
|
+
mkdirSync4(destFolder, { recursive: true });
|
|
1444
1507
|
const destVideoPath = resolve8(destFolder, destVideoName);
|
|
1445
1508
|
let mode;
|
|
1446
1509
|
try {
|
|
@@ -1457,18 +1520,18 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1457
1520
|
} else {
|
|
1458
1521
|
if (isDir) {
|
|
1459
1522
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
1460
|
-
for (const f of dirFiles.filter((f2) => !keep.has(f2)))
|
|
1461
|
-
|
|
1462
|
-
if (subtitleSourcePath && destSubtitleName)
|
|
1523
|
+
for (const f of dirFiles.filter((f2) => !keep.has(f2))) trashPath(resolve8(entryPath, f), { recursive: true });
|
|
1524
|
+
renameSync4(videoSourcePath, resolve8(entryPath, destVideoName));
|
|
1525
|
+
if (subtitleSourcePath && destSubtitleName) renameSync4(subtitleSourcePath, resolve8(entryPath, destSubtitleName));
|
|
1463
1526
|
moveFolder(entryPath, destFolder);
|
|
1464
1527
|
} else {
|
|
1465
|
-
|
|
1528
|
+
mkdirSync4(destFolder, { recursive: true });
|
|
1466
1529
|
const destVideoPath = resolve8(destFolder, destVideoName);
|
|
1467
1530
|
if (sameDev(videoSourcePath, destRoot)) {
|
|
1468
|
-
|
|
1531
|
+
renameSync4(videoSourcePath, destVideoPath);
|
|
1469
1532
|
} else {
|
|
1470
1533
|
cpSync(videoSourcePath, destVideoPath);
|
|
1471
|
-
|
|
1534
|
+
trashPath(videoSourcePath);
|
|
1472
1535
|
}
|
|
1473
1536
|
}
|
|
1474
1537
|
recordImport(sessionId, entryPath, destFolder, "move", tmdbId, "movie");
|
|
@@ -1482,15 +1545,18 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1482
1545
|
let imported = 0, skipped = 0;
|
|
1483
1546
|
const pendingMovies = [];
|
|
1484
1547
|
const pendingTv = [];
|
|
1548
|
+
const pendingBooks = [];
|
|
1549
|
+
const pendingAnime = [];
|
|
1485
1550
|
const ignoreSet = new Set(config.ignore ?? []);
|
|
1486
1551
|
const seenIgnored = /* @__PURE__ */ new Set();
|
|
1487
1552
|
for (const source of config.sources) {
|
|
1488
|
-
if (!
|
|
1553
|
+
if (!existsSync10(source)) {
|
|
1489
1554
|
spinner_default.warn(`source not found: ${Color9.white.encoder(source)}`);
|
|
1490
1555
|
continue;
|
|
1491
1556
|
}
|
|
1492
1557
|
spinner_default.text = `scanning ${Color9.white.encoder(source)}`;
|
|
1493
1558
|
for (const { entry, entryPath, isDir } of gatherEntries(source)) {
|
|
1559
|
+
spinner_default.text = `scanning: ${entry}`;
|
|
1494
1560
|
if (ignoreSet.has(entry)) {
|
|
1495
1561
|
seenIgnored.add(entry);
|
|
1496
1562
|
if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
|
|
@@ -1526,7 +1592,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1526
1592
|
}
|
|
1527
1593
|
const destName = `${nameMatch[0]} [${id}]`;
|
|
1528
1594
|
const destPath = resolve8(destRoot, destName);
|
|
1529
|
-
if (
|
|
1595
|
+
if (existsSync10(destPath)) {
|
|
1530
1596
|
spinner_default.warn(`already exists: ${destName}`);
|
|
1531
1597
|
skipped++;
|
|
1532
1598
|
continue;
|
|
@@ -1541,7 +1607,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1541
1607
|
}
|
|
1542
1608
|
if (detectedType === "book") {
|
|
1543
1609
|
const destPath = resolve8(destRoot, entry);
|
|
1544
|
-
if (
|
|
1610
|
+
if (existsSync10(destPath)) {
|
|
1545
1611
|
spinner_default.warn(`already exists: ${entry}`);
|
|
1546
1612
|
skipped++;
|
|
1547
1613
|
continue;
|
|
@@ -1550,12 +1616,12 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1550
1616
|
if (isDir || isBookDir) {
|
|
1551
1617
|
moveFolder(entryPath, destPath);
|
|
1552
1618
|
} else {
|
|
1553
|
-
|
|
1619
|
+
mkdirSync4(destRoot, { recursive: true });
|
|
1554
1620
|
if (sameDev(entryPath, destRoot)) {
|
|
1555
|
-
|
|
1621
|
+
renameSync4(entryPath, destPath);
|
|
1556
1622
|
} else {
|
|
1557
1623
|
cpSync(entryPath, destPath);
|
|
1558
|
-
|
|
1624
|
+
rmSync3(entryPath);
|
|
1559
1625
|
}
|
|
1560
1626
|
}
|
|
1561
1627
|
recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
|
|
@@ -1564,6 +1630,22 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1564
1630
|
imported++;
|
|
1565
1631
|
continue;
|
|
1566
1632
|
}
|
|
1633
|
+
if (detectedType === "movie" && isDir) {
|
|
1634
|
+
const videoCount = countVideos(entryPath);
|
|
1635
|
+
if (videoCount === 0) {
|
|
1636
|
+
if (containsPdf(entryPath)) {
|
|
1637
|
+
pendingBooks.push({ entry, entryPath });
|
|
1638
|
+
} else if (isVerbose()) {
|
|
1639
|
+
spinner_default.info(`no media found, skipped: ${entry}`);
|
|
1640
|
+
}
|
|
1641
|
+
skipped++;
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
if (videoCount >= 2) {
|
|
1645
|
+
pendingAnime.push({ entry, entryPath, videoCount });
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1567
1649
|
const parsed = parseDownloadName(entry);
|
|
1568
1650
|
if (!parsed) {
|
|
1569
1651
|
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
@@ -1587,6 +1669,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1587
1669
|
let resolvedYear = parsed.year;
|
|
1588
1670
|
if (config.tmdbApiKey) {
|
|
1589
1671
|
if (detectedType === "tv") {
|
|
1672
|
+
spinner_default.text = `TMDb: ${parsed.title}`;
|
|
1590
1673
|
const results = await searchTv(parsed.title, config.tmdbApiKey);
|
|
1591
1674
|
if (results.length === 1) {
|
|
1592
1675
|
tmdbId = results[0].id;
|
|
@@ -1641,7 +1724,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1641
1724
|
continue;
|
|
1642
1725
|
}
|
|
1643
1726
|
}
|
|
1644
|
-
const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1727
|
+
const seasonFolderName = parsed.season === 0 ? findSeasonFolder(showPath, 0, specialsFolder) ?? specialsFolder : findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1645
1728
|
const seasonPath = resolve8(showPath, seasonFolderName);
|
|
1646
1729
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1647
1730
|
if (!videoFile) {
|
|
@@ -1650,13 +1733,15 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1650
1733
|
continue;
|
|
1651
1734
|
}
|
|
1652
1735
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
1736
|
+
if (tmdbId && config.tmdbApiKey) spinner_default.text = `TMDb: episode name for ${resolvedTitle}`;
|
|
1653
1737
|
const tmdbEpisodeName = tmdbId && config.tmdbApiKey ? await getEpisodeName(tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
|
|
1654
1738
|
const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, resolvedTitle, tmdbEpisodeName ?? void 0);
|
|
1655
1739
|
const destVideoName = `${episodeName}.${videoExt}`;
|
|
1656
1740
|
const destVideoPath = resolve8(seasonPath, destVideoName);
|
|
1657
1741
|
const videoSourcePath = isDir ? resolve8(entryPath, videoFile) : entryPath;
|
|
1658
|
-
if (
|
|
1659
|
-
|
|
1742
|
+
if (existsSync10(destVideoPath)) {
|
|
1743
|
+
const isRepack = /\brepack\d*\b|\bproper\b/i.test(entry);
|
|
1744
|
+
let shouldReplace = force || isRepack;
|
|
1660
1745
|
if (!shouldReplace && interactive) {
|
|
1661
1746
|
spinner_default.stop();
|
|
1662
1747
|
const select = new Select2();
|
|
@@ -1674,7 +1759,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1674
1759
|
}
|
|
1675
1760
|
if (!dryRun) {
|
|
1676
1761
|
for (const f of readdirSync7(seasonPath)) {
|
|
1677
|
-
if (f.startsWith(`${episodeName}.`))
|
|
1762
|
+
if (f.startsWith(`${episodeName}.`)) trashPath(resolve8(seasonPath, f));
|
|
1678
1763
|
}
|
|
1679
1764
|
}
|
|
1680
1765
|
}
|
|
@@ -1684,7 +1769,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1684
1769
|
const subtitleSourcePath = subtitle ? resolve8(entryPath, subtitle) : null;
|
|
1685
1770
|
const destSubtitleName = subtitle && subtitleExt ? `${episodeName}.${subtitleExt}` : null;
|
|
1686
1771
|
if (!dryRun) {
|
|
1687
|
-
|
|
1772
|
+
mkdirSync4(seasonPath, { recursive: true });
|
|
1688
1773
|
let mode = "move";
|
|
1689
1774
|
if (useHardlink) {
|
|
1690
1775
|
try {
|
|
@@ -1699,13 +1784,13 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1699
1784
|
if (subtitleSourcePath && destSubtitleName) cpSync(subtitleSourcePath, resolve8(seasonPath, destSubtitleName));
|
|
1700
1785
|
} else {
|
|
1701
1786
|
if (sameDev(videoSourcePath, seasonPath)) {
|
|
1702
|
-
|
|
1787
|
+
renameSync4(videoSourcePath, destVideoPath);
|
|
1703
1788
|
} else {
|
|
1704
1789
|
cpSync(videoSourcePath, destVideoPath);
|
|
1705
|
-
|
|
1790
|
+
trashPath(videoSourcePath);
|
|
1706
1791
|
}
|
|
1707
|
-
if (subtitleSourcePath && destSubtitleName)
|
|
1708
|
-
if (isDir)
|
|
1792
|
+
if (subtitleSourcePath && destSubtitleName) renameSync4(subtitleSourcePath, resolve8(seasonPath, destSubtitleName));
|
|
1793
|
+
if (isDir) trashPath(entryPath, { recursive: true });
|
|
1709
1794
|
}
|
|
1710
1795
|
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId, "tv");
|
|
1711
1796
|
}
|
|
@@ -1754,6 +1839,15 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1754
1839
|
for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
1755
1840
|
skipped += pendingTv.length;
|
|
1756
1841
|
}
|
|
1842
|
+
if (pendingBooks.length > 0) {
|
|
1843
|
+
spinner_default.warn(`${pendingBooks.length} uncertain book${pendingBooks.length > 1 ? "s" : ""} skipped \u2014 contains PDFs, review manually`);
|
|
1844
|
+
for (const p of pendingBooks) spinner_default.info(` ${typeGlyph("book")} ${p.entry}${typeTag("book")}`);
|
|
1845
|
+
}
|
|
1846
|
+
if (pendingAnime.length > 0) {
|
|
1847
|
+
spinner_default.warn(`${pendingAnime.length} uncertain anime/TV director${pendingAnime.length > 1 ? "ies" : "y"} skipped \u2014 multiple videos with no episode naming`);
|
|
1848
|
+
for (const p of pendingAnime) spinner_default.info(` ${typeGlyph("tv")} ${p.entry} (${p.videoCount} video${p.videoCount > 1 ? "s" : ""})${typeTag("tv")}`);
|
|
1849
|
+
skipped += pendingAnime.length;
|
|
1850
|
+
}
|
|
1757
1851
|
if (ignoreSet.size > 0) {
|
|
1758
1852
|
const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
|
|
1759
1853
|
if (stale.length > 0 && !dryRun) {
|
|
@@ -1770,7 +1864,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1770
1864
|
var scan_default = scan;
|
|
1771
1865
|
|
|
1772
1866
|
// src/actions/undo.ts
|
|
1773
|
-
import { existsSync as
|
|
1867
|
+
import { existsSync as existsSync11, renameSync as renameSync5 } from "fs";
|
|
1774
1868
|
import { Color as Color10 } from "termkit";
|
|
1775
1869
|
var undo = async () => {
|
|
1776
1870
|
spinner_default.start();
|
|
@@ -1785,7 +1879,7 @@ var undo = async () => {
|
|
|
1785
1879
|
if (!useImports) {
|
|
1786
1880
|
let undone2 = 0;
|
|
1787
1881
|
for (const record of renameRecords) {
|
|
1788
|
-
|
|
1882
|
+
renameSync5(record.newPath, record.oldPath);
|
|
1789
1883
|
spinner_default.succeed(`${Color10.green.encoder(record.newPath)} \u2192 ${Color10.white.encoder(record.oldPath)}`);
|
|
1790
1884
|
undone2++;
|
|
1791
1885
|
}
|
|
@@ -1807,12 +1901,12 @@ var undo = async () => {
|
|
|
1807
1901
|
skipped++;
|
|
1808
1902
|
continue;
|
|
1809
1903
|
}
|
|
1810
|
-
if (!
|
|
1904
|
+
if (!existsSync11(record.destinationPath)) {
|
|
1811
1905
|
spinner_default.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
|
|
1812
1906
|
skipped++;
|
|
1813
1907
|
continue;
|
|
1814
1908
|
}
|
|
1815
|
-
|
|
1909
|
+
renameSync5(record.destinationPath, record.sourcePath);
|
|
1816
1910
|
spinner_default.succeed(`${Color10.green.encoder(record.destinationPath)} \u2192 ${Color10.white.encoder(record.sourcePath)}`);
|
|
1817
1911
|
undone++;
|
|
1818
1912
|
}
|
|
@@ -1825,13 +1919,13 @@ var undo_default = undo;
|
|
|
1825
1919
|
|
|
1826
1920
|
// src/actions/watch.ts
|
|
1827
1921
|
import chokidar from "chokidar";
|
|
1828
|
-
import { cpSync as cpSync2, existsSync as
|
|
1829
|
-
import { basename as
|
|
1922
|
+
import { cpSync as cpSync2, existsSync as existsSync12, linkSync as linkSync2, lstatSync as lstatSync5, mkdirSync as mkdirSync5, readdirSync as readdirSync8, renameSync as renameSync6, rmSync as rmSync4, statSync as statSync3 } from "fs";
|
|
1923
|
+
import { basename as basename4, dirname as dirname3, resolve as resolve9 } from "path";
|
|
1830
1924
|
import { Color as Color11 } from "termkit";
|
|
1831
1925
|
var sameDev2 = (a, b) => {
|
|
1832
1926
|
try {
|
|
1833
1927
|
let bExisting = b;
|
|
1834
|
-
while (!
|
|
1928
|
+
while (!existsSync12(bExisting)) bExisting = dirname3(bExisting);
|
|
1835
1929
|
return statSync3(a).dev === statSync3(bExisting).dev;
|
|
1836
1930
|
} catch {
|
|
1837
1931
|
return false;
|
|
@@ -1839,10 +1933,10 @@ var sameDev2 = (a, b) => {
|
|
|
1839
1933
|
};
|
|
1840
1934
|
var moveItem = (src, dest) => {
|
|
1841
1935
|
if (sameDev2(src, dest)) {
|
|
1842
|
-
|
|
1936
|
+
renameSync6(src, dest);
|
|
1843
1937
|
} else {
|
|
1844
1938
|
cpSync2(src, dest, { recursive: true });
|
|
1845
|
-
|
|
1939
|
+
rmSync4(src, { recursive: true, force: true });
|
|
1846
1940
|
}
|
|
1847
1941
|
};
|
|
1848
1942
|
var findVideo2 = (dir) => readdirSync8(dir).find((f) => {
|
|
@@ -1871,7 +1965,7 @@ var expandWatchPath = (p) => {
|
|
|
1871
1965
|
return [p];
|
|
1872
1966
|
}
|
|
1873
1967
|
if (!isDir) return [p];
|
|
1874
|
-
const name =
|
|
1968
|
+
const name = basename4(p);
|
|
1875
1969
|
if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
|
|
1876
1970
|
let children;
|
|
1877
1971
|
try {
|
|
@@ -1930,7 +2024,7 @@ var expandWatchPath = (p) => {
|
|
|
1930
2024
|
return [p];
|
|
1931
2025
|
};
|
|
1932
2026
|
var findSeasonFolder2 = (showPath, season) => {
|
|
1933
|
-
if (!
|
|
2027
|
+
if (!existsSync12(showPath)) return null;
|
|
1934
2028
|
const folders = readdirSync8(showPath).filter((f) => {
|
|
1935
2029
|
try {
|
|
1936
2030
|
return lstatSync5(resolve9(showPath, f)).isDirectory();
|
|
@@ -1946,7 +2040,7 @@ var findSeasonFolder2 = (showPath, season) => {
|
|
|
1946
2040
|
var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
1947
2041
|
const config = getConfig();
|
|
1948
2042
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1949
|
-
const entry =
|
|
2043
|
+
const entry = basename4(entryPath);
|
|
1950
2044
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
1951
2045
|
const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
|
|
1952
2046
|
const isDir = lstatSync5(entryPath).isDirectory();
|
|
@@ -1976,7 +2070,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
1976
2070
|
if (!nameMatch || !id) return;
|
|
1977
2071
|
const destName = `${nameMatch[0]} [${id}]`;
|
|
1978
2072
|
const destPath = resolve9(destRoot, destName);
|
|
1979
|
-
if (
|
|
2073
|
+
if (existsSync12(destPath)) {
|
|
1980
2074
|
spinner_default.warn(`already exists: ${destName}`);
|
|
1981
2075
|
return;
|
|
1982
2076
|
}
|
|
@@ -1987,19 +2081,19 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
1987
2081
|
}
|
|
1988
2082
|
if (detectedType === "book") {
|
|
1989
2083
|
const destPath = resolve9(destRoot, entry);
|
|
1990
|
-
if (
|
|
2084
|
+
if (existsSync12(destPath)) {
|
|
1991
2085
|
spinner_default.warn(`already exists: ${entry}`);
|
|
1992
2086
|
return;
|
|
1993
2087
|
}
|
|
1994
2088
|
if (isDir || isBookDir) {
|
|
1995
2089
|
moveItem(entryPath, destPath);
|
|
1996
2090
|
} else {
|
|
1997
|
-
|
|
2091
|
+
mkdirSync5(destRoot, { recursive: true });
|
|
1998
2092
|
if (sameDev2(entryPath, destRoot)) {
|
|
1999
|
-
|
|
2093
|
+
renameSync6(entryPath, destPath);
|
|
2000
2094
|
} else {
|
|
2001
2095
|
cpSync2(entryPath, destPath);
|
|
2002
|
-
|
|
2096
|
+
rmSync4(entryPath);
|
|
2003
2097
|
}
|
|
2004
2098
|
}
|
|
2005
2099
|
recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
|
|
@@ -2043,7 +2137,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2043
2137
|
const destVideoName2 = `${episodeName}.${videoExt2}`;
|
|
2044
2138
|
const destVideoPath = resolve9(seasonPath, destVideoName2);
|
|
2045
2139
|
const videoSourcePath2 = isDir ? resolve9(entryPath, videoFile2) : entryPath;
|
|
2046
|
-
if (
|
|
2140
|
+
if (existsSync12(destVideoPath)) {
|
|
2047
2141
|
spinner_default.warn(`already exists: ${episodeName}`);
|
|
2048
2142
|
return;
|
|
2049
2143
|
}
|
|
@@ -2052,7 +2146,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2052
2146
|
const subtitleExt2 = subtitle2?.match(/([^.]+$)/)?.[0];
|
|
2053
2147
|
const subtitleSourcePath2 = subtitle2 ? resolve9(entryPath, subtitle2) : null;
|
|
2054
2148
|
const destSubtitleName2 = subtitle2 && subtitleExt2 ? `${episodeName}.${subtitleExt2}` : null;
|
|
2055
|
-
|
|
2149
|
+
mkdirSync5(seasonPath, { recursive: true });
|
|
2056
2150
|
let mode = "move";
|
|
2057
2151
|
if (useHardlink) {
|
|
2058
2152
|
try {
|
|
@@ -2067,13 +2161,13 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2067
2161
|
if (subtitleSourcePath2 && destSubtitleName2) cpSync2(subtitleSourcePath2, resolve9(seasonPath, destSubtitleName2));
|
|
2068
2162
|
} else {
|
|
2069
2163
|
if (sameDev2(videoSourcePath2, seasonPath)) {
|
|
2070
|
-
|
|
2164
|
+
renameSync6(videoSourcePath2, destVideoPath);
|
|
2071
2165
|
} else {
|
|
2072
2166
|
cpSync2(videoSourcePath2, destVideoPath);
|
|
2073
|
-
|
|
2167
|
+
rmSync4(videoSourcePath2);
|
|
2074
2168
|
}
|
|
2075
|
-
if (subtitleSourcePath2 && destSubtitleName2)
|
|
2076
|
-
if (isDir)
|
|
2169
|
+
if (subtitleSourcePath2 && destSubtitleName2) renameSync6(subtitleSourcePath2, resolve9(seasonPath, destSubtitleName2));
|
|
2170
|
+
if (isDir) rmSync4(entryPath, { recursive: true, force: true });
|
|
2077
2171
|
}
|
|
2078
2172
|
recordImport(sessionId, entryPath, seasonPath, mode, void 0, "tv");
|
|
2079
2173
|
spinner_default.succeed(`imported ${Color11.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
|
|
@@ -2082,7 +2176,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2082
2176
|
const edition = detectEdition(entry);
|
|
2083
2177
|
const folderName = formatMovieName(movieFormat, parsed.title, parsed.year, edition);
|
|
2084
2178
|
const destFolder = resolve9(destRoot, folderName);
|
|
2085
|
-
if (
|
|
2179
|
+
if (existsSync12(destFolder)) {
|
|
2086
2180
|
spinner_default.warn(`already exists: ${folderName}`);
|
|
2087
2181
|
return;
|
|
2088
2182
|
}
|
|
@@ -2100,7 +2194,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2100
2194
|
const subtitleSourcePath = subtitle ? resolve9(entryPath, subtitle) : null;
|
|
2101
2195
|
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
2102
2196
|
if (useHardlink) {
|
|
2103
|
-
|
|
2197
|
+
mkdirSync5(destFolder, { recursive: true });
|
|
2104
2198
|
const destVideoPath = resolve9(destFolder, destVideoName);
|
|
2105
2199
|
let mode;
|
|
2106
2200
|
try {
|
|
@@ -2117,18 +2211,18 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2117
2211
|
} else {
|
|
2118
2212
|
if (isDir) {
|
|
2119
2213
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
2120
|
-
for (const f of dirFiles.filter((f2) => !keep.has(f2)))
|
|
2121
|
-
|
|
2122
|
-
if (subtitleSourcePath && destSubtitleName)
|
|
2214
|
+
for (const f of dirFiles.filter((f2) => !keep.has(f2))) rmSync4(resolve9(entryPath, f), { recursive: true, force: true });
|
|
2215
|
+
renameSync6(videoSourcePath, resolve9(entryPath, destVideoName));
|
|
2216
|
+
if (subtitleSourcePath && destSubtitleName) renameSync6(subtitleSourcePath, resolve9(entryPath, destSubtitleName));
|
|
2123
2217
|
moveItem(entryPath, destFolder);
|
|
2124
2218
|
} else {
|
|
2125
|
-
|
|
2219
|
+
mkdirSync5(destFolder, { recursive: true });
|
|
2126
2220
|
const destVideoPath = resolve9(destFolder, destVideoName);
|
|
2127
2221
|
if (sameDev2(videoSourcePath, destRoot)) {
|
|
2128
|
-
|
|
2222
|
+
renameSync6(videoSourcePath, destVideoPath);
|
|
2129
2223
|
} else {
|
|
2130
2224
|
cpSync2(videoSourcePath, destVideoPath);
|
|
2131
|
-
|
|
2225
|
+
rmSync4(videoSourcePath);
|
|
2132
2226
|
}
|
|
2133
2227
|
}
|
|
2134
2228
|
recordImport(sessionId, entryPath, destFolder, "move", void 0, "movie");
|
package/package.json
CHANGED