reelsort 0.2.3 → 0.2.5
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 +112 -33
- package/dist/index.d.mts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +109 -32
- package/dist/index.mjs +115 -40
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -98,6 +98,10 @@ var db = () => {
|
|
|
98
98
|
_db.exec("ALTER TABLE shows ADD COLUMN title TEXT");
|
|
99
99
|
} catch {
|
|
100
100
|
}
|
|
101
|
+
try {
|
|
102
|
+
_db.exec("ALTER TABLE imports ADD COLUMN type TEXT");
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
101
105
|
return _db;
|
|
102
106
|
};
|
|
103
107
|
var recordRename = (sessionId, oldPath, newPath) => {
|
|
@@ -111,8 +115,16 @@ var getLastSession = () => {
|
|
|
111
115
|
var deleteSession = (sessionId) => {
|
|
112
116
|
db().prepare("DELETE FROM renameHistory WHERE sessionId = ?").run(sessionId);
|
|
113
117
|
};
|
|
114
|
-
var
|
|
115
|
-
db().prepare("
|
|
118
|
+
var getLastImportSession = () => {
|
|
119
|
+
const last = db().prepare("SELECT sessionId FROM imports ORDER BY id DESC LIMIT 1").get();
|
|
120
|
+
if (!last) return [];
|
|
121
|
+
return db().prepare("SELECT * FROM imports WHERE sessionId = ? ORDER BY id DESC").all(last.sessionId);
|
|
122
|
+
};
|
|
123
|
+
var deleteImportSession = (sessionId) => {
|
|
124
|
+
db().prepare("DELETE FROM imports WHERE sessionId = ?").run(sessionId);
|
|
125
|
+
};
|
|
126
|
+
var recordImport = (sessionId, sourcePath, destPath, mode, tmdbId, type) => {
|
|
127
|
+
db().prepare("INSERT INTO imports (sessionId, sourcePath, destinationPath, mode, tmdbId, type) VALUES (?, ?, ?, ?, ?, ?)").run(sessionId, sourcePath, destPath, mode, tmdbId ?? null, type ?? null);
|
|
116
128
|
};
|
|
117
129
|
var getMediaInfo = (filePath) => {
|
|
118
130
|
return db().prepare("SELECT * FROM mediaInfo WHERE filePath = ?").get(filePath);
|
|
@@ -408,10 +420,10 @@ var import_path4 = require("path");
|
|
|
408
420
|
var import_termkit4 = require("termkit");
|
|
409
421
|
|
|
410
422
|
// src/helpers/formatEpisode.ts
|
|
411
|
-
var DEFAULT_EPISODE_FORMAT = "{s}x{ee}";
|
|
423
|
+
var DEFAULT_EPISODE_FORMAT = "{s}x{ee} - {title}";
|
|
412
424
|
var DEFAULT_SEASON_FORMAT = "Season {s}";
|
|
413
425
|
var renderEpisode = (format, season, episode, title, name) => {
|
|
414
|
-
return format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).replace(/\{eee\}/g, episode.toString().padStart(3, "0")).replace(/\{ee\}/g, episode.toString().padStart(2, "0")).replace(/\{e\}/g, episode.toString()).replace(/\{
|
|
426
|
+
return format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).replace(/\{eee\}/g, episode.toString().padStart(3, "0")).replace(/\{ee\}/g, episode.toString().padStart(2, "0")).replace(/\{e\}/g, episode.toString()).replace(/\{show\}/g, title ?? "").replace(/\{title\}/g, name ?? "").replace(/(\s*-\s*)+$/, "").replace(/^(\s*-\s*)+/, "").replace(/\s+/g, " ").trim();
|
|
415
427
|
};
|
|
416
428
|
var formatSeasonFolder = (format, season) => format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).trim();
|
|
417
429
|
var formatEpisode = (season, episode, format = DEFAULT_EPISODE_FORMAT, double = false, title, name) => {
|
|
@@ -1676,6 +1688,35 @@ var findShowFolder = (destRoot, title) => {
|
|
|
1676
1688
|
}
|
|
1677
1689
|
}).find((f) => normalize(f) === target) ?? null;
|
|
1678
1690
|
};
|
|
1691
|
+
var findShowFolderByContent = (destRoot, title) => {
|
|
1692
|
+
if (!(0, import_fs14.existsSync)(destRoot)) return null;
|
|
1693
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1694
|
+
const target = normalize(title);
|
|
1695
|
+
const matchesTitle = (name) => {
|
|
1696
|
+
if (!isTvEpisodeName(name)) return false;
|
|
1697
|
+
const p = parseDownloadName(name);
|
|
1698
|
+
return !!p && normalize(p.title) === target;
|
|
1699
|
+
};
|
|
1700
|
+
for (const folder of (0, import_fs14.readdirSync)(destRoot)) {
|
|
1701
|
+
try {
|
|
1702
|
+
const folderPath = (0, import_path14.resolve)(destRoot, folder);
|
|
1703
|
+
if (!(0, import_fs14.lstatSync)(folderPath).isDirectory()) continue;
|
|
1704
|
+
const children = (0, import_fs14.readdirSync)(folderPath);
|
|
1705
|
+
if (children.some(matchesTitle)) return folder;
|
|
1706
|
+
for (const child of children) {
|
|
1707
|
+
if (!isSeasonDirName(child)) continue;
|
|
1708
|
+
try {
|
|
1709
|
+
const seasonPath = (0, import_path14.resolve)(folderPath, child);
|
|
1710
|
+
if (!(0, import_fs14.lstatSync)(seasonPath).isDirectory()) continue;
|
|
1711
|
+
if ((0, import_fs14.readdirSync)(seasonPath).some(matchesTitle)) return folder;
|
|
1712
|
+
} catch {
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
} catch {
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return null;
|
|
1719
|
+
};
|
|
1679
1720
|
var findSeasonFolder = (showPath, season) => {
|
|
1680
1721
|
if (!(0, import_fs14.existsSync)(showPath)) return null;
|
|
1681
1722
|
const folders = (0, import_fs14.readdirSync)(showPath).filter((f) => {
|
|
@@ -1700,11 +1741,12 @@ var classifyMovieConfidence = (entry) => {
|
|
|
1700
1741
|
return "ambiguous";
|
|
1701
1742
|
};
|
|
1702
1743
|
var typeColor = {
|
|
1703
|
-
movie: import_termkit14.Color.
|
|
1704
|
-
tv: import_termkit14.Color.
|
|
1705
|
-
book: import_termkit14.Color.
|
|
1706
|
-
ps3: import_termkit14.Color.
|
|
1744
|
+
movie: (s) => import_termkit14.Color.cyan.encoder(s),
|
|
1745
|
+
tv: (s) => import_termkit14.Color.green.encoder(s),
|
|
1746
|
+
book: (s) => import_termkit14.Color.yellow.encoder(s),
|
|
1747
|
+
ps3: (s) => import_termkit14.Color.magenta.encoder(s)
|
|
1707
1748
|
};
|
|
1749
|
+
var typeGlyph = (t) => typeColor[t]("\u25CF");
|
|
1708
1750
|
var typeTag = (t) => isVerbose() ? import_termkit14.Color.white.faint.encoder(` (${t})`) : "";
|
|
1709
1751
|
var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
|
|
1710
1752
|
const config = getConfig();
|
|
@@ -1777,7 +1819,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1777
1819
|
mode = "copy";
|
|
1778
1820
|
}
|
|
1779
1821
|
if (subtitleSourcePath && destSubtitleName) (0, import_fs14.cpSync)(subtitleSourcePath, (0, import_path14.resolve)(destFolder, destSubtitleName));
|
|
1780
|
-
recordImport(sessionId, entryPath, destFolder, mode, tmdbId);
|
|
1822
|
+
recordImport(sessionId, entryPath, destFolder, mode, tmdbId, "movie");
|
|
1781
1823
|
} else {
|
|
1782
1824
|
if (isDir) {
|
|
1783
1825
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
@@ -1795,10 +1837,10 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1795
1837
|
(0, import_fs14.rmSync)(videoSourcePath);
|
|
1796
1838
|
}
|
|
1797
1839
|
}
|
|
1798
|
-
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1840
|
+
recordImport(sessionId, entryPath, destFolder, "move", tmdbId, "movie");
|
|
1799
1841
|
}
|
|
1800
1842
|
}
|
|
1801
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.movie
|
|
1843
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("movie")} ${typeColor.movie(folderName)}${typeTag("movie")}`);
|
|
1802
1844
|
return true;
|
|
1803
1845
|
};
|
|
1804
1846
|
spinner_default.start();
|
|
@@ -1857,9 +1899,9 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1857
1899
|
}
|
|
1858
1900
|
if (!dryRun) {
|
|
1859
1901
|
moveFolder(entryPath, destPath);
|
|
1860
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
1902
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "ps3");
|
|
1861
1903
|
}
|
|
1862
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.ps3
|
|
1904
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("ps3")} ${typeColor.ps3(destName)}${typeTag("ps3")}`);
|
|
1863
1905
|
imported++;
|
|
1864
1906
|
continue;
|
|
1865
1907
|
}
|
|
@@ -1882,9 +1924,9 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1882
1924
|
(0, import_fs14.rmSync)(entryPath);
|
|
1883
1925
|
}
|
|
1884
1926
|
}
|
|
1885
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
1927
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
|
|
1886
1928
|
}
|
|
1887
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.book
|
|
1929
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("book")} ${typeColor.book(entry)}${typeTag("book")}`);
|
|
1888
1930
|
imported++;
|
|
1889
1931
|
continue;
|
|
1890
1932
|
}
|
|
@@ -1952,7 +1994,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1952
1994
|
showPath = registeredShow.path;
|
|
1953
1995
|
showFolderName = showPath.split("/").pop() ?? registeredShow.path;
|
|
1954
1996
|
} else {
|
|
1955
|
-
const existingFolder = findShowFolder(destRoot, resolvedTitle);
|
|
1997
|
+
const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
|
|
1956
1998
|
if (existingFolder) {
|
|
1957
1999
|
showFolderName = existingFolder;
|
|
1958
2000
|
showPath = (0, import_path14.resolve)(destRoot, existingFolder);
|
|
@@ -2031,9 +2073,9 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
2031
2073
|
if (subtitleSourcePath && destSubtitleName) (0, import_fs14.renameSync)(subtitleSourcePath, (0, import_path14.resolve)(seasonPath, destSubtitleName));
|
|
2032
2074
|
if (isDir) (0, import_fs14.rmSync)(entryPath, { recursive: true, force: true });
|
|
2033
2075
|
}
|
|
2034
|
-
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
2076
|
+
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId, "tv");
|
|
2035
2077
|
}
|
|
2036
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.tv
|
|
2078
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("tv")} ${typeColor.tv(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
|
|
2037
2079
|
imported++;
|
|
2038
2080
|
continue;
|
|
2039
2081
|
}
|
|
@@ -2046,7 +2088,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
2046
2088
|
}
|
|
2047
2089
|
if (pendingMovies.length > 0) {
|
|
2048
2090
|
spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
|
|
2049
|
-
for (const p of pendingMovies) spinner_default.info(` ${
|
|
2091
|
+
for (const p of pendingMovies) spinner_default.info(` ${typeGlyph("movie")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
|
|
2050
2092
|
let toProcess = [];
|
|
2051
2093
|
if (interactive) {
|
|
2052
2094
|
spinner_default.stop();
|
|
@@ -2075,7 +2117,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
2075
2117
|
}
|
|
2076
2118
|
if (pendingTv.length > 0) {
|
|
2077
2119
|
spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
|
|
2078
|
-
for (const p of pendingTv) spinner_default.info(` ${
|
|
2120
|
+
for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
2079
2121
|
skipped += pendingTv.length;
|
|
2080
2122
|
}
|
|
2081
2123
|
if (ignoreSet.size > 0) {
|
|
@@ -2212,20 +2254,51 @@ var import_fs17 = require("fs");
|
|
|
2212
2254
|
var import_termkit17 = require("termkit");
|
|
2213
2255
|
var undo = async () => {
|
|
2214
2256
|
spinner_default.start();
|
|
2215
|
-
const
|
|
2216
|
-
|
|
2257
|
+
const renameRecords = getLastSession();
|
|
2258
|
+
const importRecords = getLastImportSession();
|
|
2259
|
+
if (renameRecords.length === 0 && importRecords.length === 0) {
|
|
2217
2260
|
spinner_default.info("nothing to undo");
|
|
2218
2261
|
spinner_default.stop();
|
|
2219
2262
|
return;
|
|
2220
2263
|
}
|
|
2264
|
+
const useImports = importRecords.length > 0 && (renameRecords.length === 0 || importRecords[0].sessionId > renameRecords[0].sessionId);
|
|
2265
|
+
if (!useImports) {
|
|
2266
|
+
let undone2 = 0;
|
|
2267
|
+
for (const record of renameRecords) {
|
|
2268
|
+
(0, import_fs17.renameSync)(record.newPath, record.oldPath);
|
|
2269
|
+
spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.newPath)} \u2192 ${import_termkit17.Color.white.encoder(record.oldPath)}`);
|
|
2270
|
+
undone2++;
|
|
2271
|
+
}
|
|
2272
|
+
deleteSession(renameRecords[0].sessionId);
|
|
2273
|
+
spinner_default.succeed(`undid ${undone2} renames`);
|
|
2274
|
+
spinner_default.stop();
|
|
2275
|
+
return;
|
|
2276
|
+
}
|
|
2221
2277
|
let undone = 0;
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2278
|
+
let skipped = 0;
|
|
2279
|
+
for (const record of importRecords) {
|
|
2280
|
+
if (record.mode !== "move") {
|
|
2281
|
+
spinner_default.info(`skipped ${record.destinationPath} (${record.mode} \u2014 source file unchanged)`);
|
|
2282
|
+
skipped++;
|
|
2283
|
+
continue;
|
|
2284
|
+
}
|
|
2285
|
+
if (record.type === "tv") {
|
|
2286
|
+
spinner_default.info(`skipped TV import \u2014 season folder cannot be cleanly reversed: ${record.destinationPath}`);
|
|
2287
|
+
skipped++;
|
|
2288
|
+
continue;
|
|
2289
|
+
}
|
|
2290
|
+
if (!(0, import_fs17.existsSync)(record.destinationPath)) {
|
|
2291
|
+
spinner_default.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
|
|
2292
|
+
skipped++;
|
|
2293
|
+
continue;
|
|
2294
|
+
}
|
|
2295
|
+
(0, import_fs17.renameSync)(record.destinationPath, record.sourcePath);
|
|
2296
|
+
spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.destinationPath)} \u2192 ${import_termkit17.Color.white.encoder(record.sourcePath)}`);
|
|
2225
2297
|
undone++;
|
|
2226
2298
|
}
|
|
2227
|
-
|
|
2228
|
-
spinner_default.succeed(`undid ${undone}
|
|
2299
|
+
deleteImportSession(importRecords[0].sessionId);
|
|
2300
|
+
if (undone > 0) spinner_default.succeed(`undid ${undone} import${undone !== 1 ? "s" : ""}`);
|
|
2301
|
+
if (skipped > 0) spinner_default.info(`skipped ${skipped} item${skipped !== 1 ? "s" : ""} (TV or non-move mode)`);
|
|
2229
2302
|
spinner_default.stop();
|
|
2230
2303
|
};
|
|
2231
2304
|
var undo_default = undo;
|
|
@@ -2388,7 +2461,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2388
2461
|
return;
|
|
2389
2462
|
}
|
|
2390
2463
|
moveItem(entryPath, destPath);
|
|
2391
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
2464
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "ps3");
|
|
2392
2465
|
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(destName)}`);
|
|
2393
2466
|
return;
|
|
2394
2467
|
}
|
|
@@ -2409,7 +2482,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2409
2482
|
(0, import_fs18.rmSync)(entryPath);
|
|
2410
2483
|
}
|
|
2411
2484
|
}
|
|
2412
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
2485
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
|
|
2413
2486
|
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(entry)}`);
|
|
2414
2487
|
return;
|
|
2415
2488
|
}
|
|
@@ -2482,7 +2555,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2482
2555
|
if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs18.renameSync)(subtitleSourcePath2, (0, import_path16.resolve)(seasonPath, destSubtitleName2));
|
|
2483
2556
|
if (isDir) (0, import_fs18.rmSync)(entryPath, { recursive: true, force: true });
|
|
2484
2557
|
}
|
|
2485
|
-
recordImport(sessionId, entryPath, seasonPath, mode);
|
|
2558
|
+
recordImport(sessionId, entryPath, seasonPath, mode, void 0, "tv");
|
|
2486
2559
|
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
|
|
2487
2560
|
return;
|
|
2488
2561
|
}
|
|
@@ -2520,7 +2593,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2520
2593
|
mode = "copy";
|
|
2521
2594
|
}
|
|
2522
2595
|
if (subtitleSourcePath && destSubtitleName) (0, import_fs18.cpSync)(subtitleSourcePath, (0, import_path16.resolve)(destFolder, destSubtitleName));
|
|
2523
|
-
recordImport(sessionId, entryPath, destFolder, mode);
|
|
2596
|
+
recordImport(sessionId, entryPath, destFolder, mode, void 0, "movie");
|
|
2524
2597
|
} else {
|
|
2525
2598
|
if (isDir) {
|
|
2526
2599
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
@@ -2538,7 +2611,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2538
2611
|
(0, import_fs18.rmSync)(videoSourcePath);
|
|
2539
2612
|
}
|
|
2540
2613
|
}
|
|
2541
|
-
recordImport(sessionId, entryPath, destFolder, "move");
|
|
2614
|
+
recordImport(sessionId, entryPath, destFolder, "move", void 0, "movie");
|
|
2542
2615
|
}
|
|
2543
2616
|
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(folderName)}`);
|
|
2544
2617
|
};
|
|
@@ -2580,7 +2653,7 @@ var watch = async ({ hardlink = false, auto = false }) => {
|
|
|
2580
2653
|
var watch_default = watch;
|
|
2581
2654
|
|
|
2582
2655
|
// package.json
|
|
2583
|
-
var version = "0.2.
|
|
2656
|
+
var version = "0.2.5";
|
|
2584
2657
|
|
|
2585
2658
|
// src/program.ts
|
|
2586
2659
|
var toCamel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
@@ -2618,6 +2691,12 @@ var program = import_termkit19.Program.command("reelsort").version(version).desc
|
|
|
2618
2691
|
var program_default = program;
|
|
2619
2692
|
|
|
2620
2693
|
// src/cli.ts
|
|
2694
|
+
if (!process.stdout.isTTY) {
|
|
2695
|
+
const { FORCE_COLOR, MSYSTEM, WT_SESSION, TERM } = process.env;
|
|
2696
|
+
if (FORCE_COLOR || MSYSTEM || WT_SESSION || TERM?.startsWith("xterm")) {
|
|
2697
|
+
Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true });
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2621
2700
|
var run = async (args) => {
|
|
2622
2701
|
try {
|
|
2623
2702
|
await program_default.parse(args);
|
package/dist/index.d.mts
CHANGED
|
@@ -122,11 +122,14 @@ interface RenameRecord {
|
|
|
122
122
|
declare const recordRename: (sessionId: string, oldPath: string, newPath: string) => void;
|
|
123
123
|
declare const getLastSession: () => RenameRecord[];
|
|
124
124
|
declare const deleteSession: (sessionId: string) => void;
|
|
125
|
+
declare const getLastImportSession: () => ImportRecord[];
|
|
126
|
+
declare const deleteImportSession: (sessionId: string) => void;
|
|
125
127
|
interface RenameSession {
|
|
126
128
|
sessionId: string;
|
|
127
129
|
records: RenameRecord[];
|
|
128
130
|
}
|
|
129
131
|
type ImportMode = 'move' | 'hardlink' | 'copy';
|
|
132
|
+
type MediaType = 'movie' | 'tv' | 'ps3' | 'book';
|
|
130
133
|
interface ImportRecord {
|
|
131
134
|
id: number;
|
|
132
135
|
sessionId: string;
|
|
@@ -134,9 +137,10 @@ interface ImportRecord {
|
|
|
134
137
|
destinationPath: string;
|
|
135
138
|
mode: ImportMode;
|
|
136
139
|
tmdbId: number | null;
|
|
140
|
+
type: MediaType | null;
|
|
137
141
|
importedAt: string;
|
|
138
142
|
}
|
|
139
|
-
declare const recordImport: (sessionId: string, sourcePath: string, destPath: string, mode: ImportMode, tmdbId?: number) => void;
|
|
143
|
+
declare const recordImport: (sessionId: string, sourcePath: string, destPath: string, mode: ImportMode, tmdbId?: number, type?: MediaType) => void;
|
|
140
144
|
interface MediaInfoRecord {
|
|
141
145
|
id: number;
|
|
142
146
|
filePath: string;
|
|
@@ -156,7 +160,7 @@ declare const getHistory: (limit?: number) => RenameSession[];
|
|
|
156
160
|
|
|
157
161
|
declare const detectEdition: (filename: string) => string | null;
|
|
158
162
|
|
|
159
|
-
declare const DEFAULT_EPISODE_FORMAT = "{s}x{ee}";
|
|
163
|
+
declare const DEFAULT_EPISODE_FORMAT = "{s}x{ee} - {title}";
|
|
160
164
|
declare const DEFAULT_SEASON_FORMAT = "Season {s}";
|
|
161
165
|
declare const formatSeasonFolder: (format: string, season: number) => string;
|
|
162
166
|
declare const formatEpisode: (season: number, episode: number, format?: string, double?: boolean, title?: string, name?: string) => string;
|
|
@@ -219,4 +223,4 @@ var videoExtensions = [
|
|
|
219
223
|
"yuv"
|
|
220
224
|
];
|
|
221
225
|
|
|
222
|
-
export { type CleanOptions, type ConfigSetOptions, type ConfigShowOptions, DEFAULT_EPISODE_FORMAT, DEFAULT_MOVIE_FORMAT, DEFAULT_SEASON_FORMAT, type DestAddOptions, type DestRemoveOptions, type DifferencesOptions, type HistoryOptions, type ImportMode, type ImportRecord, type ListOptions, type MediaInfoRecord, type ParsedMediaName, type ProbeOptions, type Quality, type ReelSortConfig, type RenameOptions, type RenameRecord, type RenameSession, type ResetOptions, type ScanOptions, type SourceAddOptions, type SourceRemoveOptions, type WatchOptions, clean, configSet, configShow, deleteImport, deleteSession, destAdd, destRemove, detectEdition, differences, formatEpisode, formatMovieName, formatSeasonFolder, getCleanableImports, getConfig, getHistory, getImportByDest, getLastSession, getMediaInfo, history, list, normalizeCodec, normalizeResolution, parseDownloadName, parseQuality, probe, recordImport, recordRename, rename, reset, saveConfig, scan, sourceAdd, sourceRemove, _default as titleCase, undo, upsertMediaInfo, videoExtensions, watch };
|
|
226
|
+
export { type CleanOptions, type ConfigSetOptions, type ConfigShowOptions, DEFAULT_EPISODE_FORMAT, DEFAULT_MOVIE_FORMAT, DEFAULT_SEASON_FORMAT, type DestAddOptions, type DestRemoveOptions, type DifferencesOptions, type HistoryOptions, type ImportMode, type ImportRecord, type ListOptions, type MediaInfoRecord, type MediaType, type ParsedMediaName, type ProbeOptions, type Quality, type ReelSortConfig, type RenameOptions, type RenameRecord, type RenameSession, type ResetOptions, type ScanOptions, type SourceAddOptions, type SourceRemoveOptions, type WatchOptions, clean, configSet, configShow, deleteImport, deleteImportSession, deleteSession, destAdd, destRemove, detectEdition, differences, formatEpisode, formatMovieName, formatSeasonFolder, getCleanableImports, getConfig, getHistory, getImportByDest, getLastImportSession, getLastSession, getMediaInfo, history, list, normalizeCodec, normalizeResolution, parseDownloadName, parseQuality, probe, recordImport, recordRename, rename, reset, saveConfig, scan, sourceAdd, sourceRemove, _default as titleCase, undo, upsertMediaInfo, videoExtensions, watch };
|
package/dist/index.d.ts
CHANGED
|
@@ -122,11 +122,14 @@ interface RenameRecord {
|
|
|
122
122
|
declare const recordRename: (sessionId: string, oldPath: string, newPath: string) => void;
|
|
123
123
|
declare const getLastSession: () => RenameRecord[];
|
|
124
124
|
declare const deleteSession: (sessionId: string) => void;
|
|
125
|
+
declare const getLastImportSession: () => ImportRecord[];
|
|
126
|
+
declare const deleteImportSession: (sessionId: string) => void;
|
|
125
127
|
interface RenameSession {
|
|
126
128
|
sessionId: string;
|
|
127
129
|
records: RenameRecord[];
|
|
128
130
|
}
|
|
129
131
|
type ImportMode = 'move' | 'hardlink' | 'copy';
|
|
132
|
+
type MediaType = 'movie' | 'tv' | 'ps3' | 'book';
|
|
130
133
|
interface ImportRecord {
|
|
131
134
|
id: number;
|
|
132
135
|
sessionId: string;
|
|
@@ -134,9 +137,10 @@ interface ImportRecord {
|
|
|
134
137
|
destinationPath: string;
|
|
135
138
|
mode: ImportMode;
|
|
136
139
|
tmdbId: number | null;
|
|
140
|
+
type: MediaType | null;
|
|
137
141
|
importedAt: string;
|
|
138
142
|
}
|
|
139
|
-
declare const recordImport: (sessionId: string, sourcePath: string, destPath: string, mode: ImportMode, tmdbId?: number) => void;
|
|
143
|
+
declare const recordImport: (sessionId: string, sourcePath: string, destPath: string, mode: ImportMode, tmdbId?: number, type?: MediaType) => void;
|
|
140
144
|
interface MediaInfoRecord {
|
|
141
145
|
id: number;
|
|
142
146
|
filePath: string;
|
|
@@ -156,7 +160,7 @@ declare const getHistory: (limit?: number) => RenameSession[];
|
|
|
156
160
|
|
|
157
161
|
declare const detectEdition: (filename: string) => string | null;
|
|
158
162
|
|
|
159
|
-
declare const DEFAULT_EPISODE_FORMAT = "{s}x{ee}";
|
|
163
|
+
declare const DEFAULT_EPISODE_FORMAT = "{s}x{ee} - {title}";
|
|
160
164
|
declare const DEFAULT_SEASON_FORMAT = "Season {s}";
|
|
161
165
|
declare const formatSeasonFolder: (format: string, season: number) => string;
|
|
162
166
|
declare const formatEpisode: (season: number, episode: number, format?: string, double?: boolean, title?: string, name?: string) => string;
|
|
@@ -219,4 +223,4 @@ var videoExtensions = [
|
|
|
219
223
|
"yuv"
|
|
220
224
|
];
|
|
221
225
|
|
|
222
|
-
export { type CleanOptions, type ConfigSetOptions, type ConfigShowOptions, DEFAULT_EPISODE_FORMAT, DEFAULT_MOVIE_FORMAT, DEFAULT_SEASON_FORMAT, type DestAddOptions, type DestRemoveOptions, type DifferencesOptions, type HistoryOptions, type ImportMode, type ImportRecord, type ListOptions, type MediaInfoRecord, type ParsedMediaName, type ProbeOptions, type Quality, type ReelSortConfig, type RenameOptions, type RenameRecord, type RenameSession, type ResetOptions, type ScanOptions, type SourceAddOptions, type SourceRemoveOptions, type WatchOptions, clean, configSet, configShow, deleteImport, deleteSession, destAdd, destRemove, detectEdition, differences, formatEpisode, formatMovieName, formatSeasonFolder, getCleanableImports, getConfig, getHistory, getImportByDest, getLastSession, getMediaInfo, history, list, normalizeCodec, normalizeResolution, parseDownloadName, parseQuality, probe, recordImport, recordRename, rename, reset, saveConfig, scan, sourceAdd, sourceRemove, _default as titleCase, undo, upsertMediaInfo, videoExtensions, watch };
|
|
226
|
+
export { type CleanOptions, type ConfigSetOptions, type ConfigShowOptions, DEFAULT_EPISODE_FORMAT, DEFAULT_MOVIE_FORMAT, DEFAULT_SEASON_FORMAT, type DestAddOptions, type DestRemoveOptions, type DifferencesOptions, type HistoryOptions, type ImportMode, type ImportRecord, type ListOptions, type MediaInfoRecord, type MediaType, type ParsedMediaName, type ProbeOptions, type Quality, type ReelSortConfig, type RenameOptions, type RenameRecord, type RenameSession, type ResetOptions, type ScanOptions, type SourceAddOptions, type SourceRemoveOptions, type WatchOptions, clean, configSet, configShow, deleteImport, deleteImportSession, deleteSession, destAdd, destRemove, detectEdition, differences, formatEpisode, formatMovieName, formatSeasonFolder, getCleanableImports, getConfig, getHistory, getImportByDest, getLastImportSession, getLastSession, getMediaInfo, history, list, normalizeCodec, normalizeResolution, parseDownloadName, parseQuality, probe, recordImport, recordRename, rename, reset, saveConfig, scan, sourceAdd, sourceRemove, _default as titleCase, undo, upsertMediaInfo, videoExtensions, watch };
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
configSet: () => configSet,
|
|
38
38
|
configShow: () => configShow,
|
|
39
39
|
deleteImport: () => deleteImport,
|
|
40
|
+
deleteImportSession: () => deleteImportSession,
|
|
40
41
|
deleteSession: () => deleteSession,
|
|
41
42
|
destAdd: () => destAdd,
|
|
42
43
|
destRemove: () => destRemove,
|
|
@@ -49,6 +50,7 @@ __export(index_exports, {
|
|
|
49
50
|
getConfig: () => getConfig,
|
|
50
51
|
getHistory: () => getHistory,
|
|
51
52
|
getImportByDest: () => getImportByDest,
|
|
53
|
+
getLastImportSession: () => getLastImportSession,
|
|
52
54
|
getLastSession: () => getLastSession,
|
|
53
55
|
getMediaInfo: () => getMediaInfo,
|
|
54
56
|
history: () => history_default,
|
|
@@ -130,6 +132,10 @@ var db = () => {
|
|
|
130
132
|
_db.exec("ALTER TABLE shows ADD COLUMN title TEXT");
|
|
131
133
|
} catch {
|
|
132
134
|
}
|
|
135
|
+
try {
|
|
136
|
+
_db.exec("ALTER TABLE imports ADD COLUMN type TEXT");
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
133
139
|
return _db;
|
|
134
140
|
};
|
|
135
141
|
var recordRename = (sessionId, oldPath, newPath) => {
|
|
@@ -143,8 +149,16 @@ var getLastSession = () => {
|
|
|
143
149
|
var deleteSession = (sessionId) => {
|
|
144
150
|
db().prepare("DELETE FROM renameHistory WHERE sessionId = ?").run(sessionId);
|
|
145
151
|
};
|
|
146
|
-
var
|
|
147
|
-
db().prepare("
|
|
152
|
+
var getLastImportSession = () => {
|
|
153
|
+
const last = db().prepare("SELECT sessionId FROM imports ORDER BY id DESC LIMIT 1").get();
|
|
154
|
+
if (!last) return [];
|
|
155
|
+
return db().prepare("SELECT * FROM imports WHERE sessionId = ? ORDER BY id DESC").all(last.sessionId);
|
|
156
|
+
};
|
|
157
|
+
var deleteImportSession = (sessionId) => {
|
|
158
|
+
db().prepare("DELETE FROM imports WHERE sessionId = ?").run(sessionId);
|
|
159
|
+
};
|
|
160
|
+
var recordImport = (sessionId, sourcePath, destPath, mode, tmdbId, type) => {
|
|
161
|
+
db().prepare("INSERT INTO imports (sessionId, sourcePath, destinationPath, mode, tmdbId, type) VALUES (?, ?, ?, ?, ?, ?)").run(sessionId, sourcePath, destPath, mode, tmdbId ?? null, type ?? null);
|
|
148
162
|
};
|
|
149
163
|
var getMediaInfo = (filePath) => {
|
|
150
164
|
return db().prepare("SELECT * FROM mediaInfo WHERE filePath = ?").get(filePath);
|
|
@@ -320,10 +334,10 @@ var saveConfig = (config) => {
|
|
|
320
334
|
};
|
|
321
335
|
|
|
322
336
|
// src/helpers/formatEpisode.ts
|
|
323
|
-
var DEFAULT_EPISODE_FORMAT = "{s}x{ee}";
|
|
337
|
+
var DEFAULT_EPISODE_FORMAT = "{s}x{ee} - {title}";
|
|
324
338
|
var DEFAULT_SEASON_FORMAT = "Season {s}";
|
|
325
339
|
var renderEpisode = (format, season, episode, title, name) => {
|
|
326
|
-
return format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).replace(/\{eee\}/g, episode.toString().padStart(3, "0")).replace(/\{ee\}/g, episode.toString().padStart(2, "0")).replace(/\{e\}/g, episode.toString()).replace(/\{
|
|
340
|
+
return format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).replace(/\{eee\}/g, episode.toString().padStart(3, "0")).replace(/\{ee\}/g, episode.toString().padStart(2, "0")).replace(/\{e\}/g, episode.toString()).replace(/\{show\}/g, title ?? "").replace(/\{title\}/g, name ?? "").replace(/(\s*-\s*)+$/, "").replace(/^(\s*-\s*)+/, "").replace(/\s+/g, " ").trim();
|
|
327
341
|
};
|
|
328
342
|
var formatSeasonFolder = (format, season) => format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).trim();
|
|
329
343
|
var formatEpisode = (season, episode, format = DEFAULT_EPISODE_FORMAT, double = false, title, name) => {
|
|
@@ -1386,6 +1400,35 @@ var findShowFolder = (destRoot, title) => {
|
|
|
1386
1400
|
}
|
|
1387
1401
|
}).find((f) => normalize(f) === target) ?? null;
|
|
1388
1402
|
};
|
|
1403
|
+
var findShowFolderByContent = (destRoot, title) => {
|
|
1404
|
+
if (!(0, import_fs10.existsSync)(destRoot)) return null;
|
|
1405
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1406
|
+
const target = normalize(title);
|
|
1407
|
+
const matchesTitle = (name) => {
|
|
1408
|
+
if (!isTvEpisodeName(name)) return false;
|
|
1409
|
+
const p = parseDownloadName(name);
|
|
1410
|
+
return !!p && normalize(p.title) === target;
|
|
1411
|
+
};
|
|
1412
|
+
for (const folder of (0, import_fs10.readdirSync)(destRoot)) {
|
|
1413
|
+
try {
|
|
1414
|
+
const folderPath = (0, import_path11.resolve)(destRoot, folder);
|
|
1415
|
+
if (!(0, import_fs10.lstatSync)(folderPath).isDirectory()) continue;
|
|
1416
|
+
const children = (0, import_fs10.readdirSync)(folderPath);
|
|
1417
|
+
if (children.some(matchesTitle)) return folder;
|
|
1418
|
+
for (const child of children) {
|
|
1419
|
+
if (!isSeasonDirName(child)) continue;
|
|
1420
|
+
try {
|
|
1421
|
+
const seasonPath = (0, import_path11.resolve)(folderPath, child);
|
|
1422
|
+
if (!(0, import_fs10.lstatSync)(seasonPath).isDirectory()) continue;
|
|
1423
|
+
if ((0, import_fs10.readdirSync)(seasonPath).some(matchesTitle)) return folder;
|
|
1424
|
+
} catch {
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
} catch {
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
return null;
|
|
1431
|
+
};
|
|
1389
1432
|
var findSeasonFolder = (showPath, season) => {
|
|
1390
1433
|
if (!(0, import_fs10.existsSync)(showPath)) return null;
|
|
1391
1434
|
const folders = (0, import_fs10.readdirSync)(showPath).filter((f) => {
|
|
@@ -1410,11 +1453,12 @@ var classifyMovieConfidence = (entry) => {
|
|
|
1410
1453
|
return "ambiguous";
|
|
1411
1454
|
};
|
|
1412
1455
|
var typeColor = {
|
|
1413
|
-
movie: import_termkit10.Color.
|
|
1414
|
-
tv: import_termkit10.Color.
|
|
1415
|
-
book: import_termkit10.Color.
|
|
1416
|
-
ps3: import_termkit10.Color.
|
|
1456
|
+
movie: (s) => import_termkit10.Color.cyan.encoder(s),
|
|
1457
|
+
tv: (s) => import_termkit10.Color.green.encoder(s),
|
|
1458
|
+
book: (s) => import_termkit10.Color.yellow.encoder(s),
|
|
1459
|
+
ps3: (s) => import_termkit10.Color.magenta.encoder(s)
|
|
1417
1460
|
};
|
|
1461
|
+
var typeGlyph = (t) => typeColor[t]("\u25CF");
|
|
1418
1462
|
var typeTag = (t) => isVerbose() ? import_termkit10.Color.white.faint.encoder(` (${t})`) : "";
|
|
1419
1463
|
var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
|
|
1420
1464
|
const config = getConfig();
|
|
@@ -1487,7 +1531,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1487
1531
|
mode = "copy";
|
|
1488
1532
|
}
|
|
1489
1533
|
if (subtitleSourcePath && destSubtitleName) (0, import_fs10.cpSync)(subtitleSourcePath, (0, import_path11.resolve)(destFolder, destSubtitleName));
|
|
1490
|
-
recordImport(sessionId, entryPath, destFolder, mode, tmdbId);
|
|
1534
|
+
recordImport(sessionId, entryPath, destFolder, mode, tmdbId, "movie");
|
|
1491
1535
|
} else {
|
|
1492
1536
|
if (isDir) {
|
|
1493
1537
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
@@ -1505,10 +1549,10 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1505
1549
|
(0, import_fs10.rmSync)(videoSourcePath);
|
|
1506
1550
|
}
|
|
1507
1551
|
}
|
|
1508
|
-
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1552
|
+
recordImport(sessionId, entryPath, destFolder, "move", tmdbId, "movie");
|
|
1509
1553
|
}
|
|
1510
1554
|
}
|
|
1511
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.movie
|
|
1555
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("movie")} ${typeColor.movie(folderName)}${typeTag("movie")}`);
|
|
1512
1556
|
return true;
|
|
1513
1557
|
};
|
|
1514
1558
|
spinner_default.start();
|
|
@@ -1567,9 +1611,9 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1567
1611
|
}
|
|
1568
1612
|
if (!dryRun) {
|
|
1569
1613
|
moveFolder(entryPath, destPath);
|
|
1570
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
1614
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "ps3");
|
|
1571
1615
|
}
|
|
1572
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.ps3
|
|
1616
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("ps3")} ${typeColor.ps3(destName)}${typeTag("ps3")}`);
|
|
1573
1617
|
imported++;
|
|
1574
1618
|
continue;
|
|
1575
1619
|
}
|
|
@@ -1592,9 +1636,9 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1592
1636
|
(0, import_fs10.rmSync)(entryPath);
|
|
1593
1637
|
}
|
|
1594
1638
|
}
|
|
1595
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
1639
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
|
|
1596
1640
|
}
|
|
1597
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.book
|
|
1641
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("book")} ${typeColor.book(entry)}${typeTag("book")}`);
|
|
1598
1642
|
imported++;
|
|
1599
1643
|
continue;
|
|
1600
1644
|
}
|
|
@@ -1662,7 +1706,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1662
1706
|
showPath = registeredShow.path;
|
|
1663
1707
|
showFolderName = showPath.split("/").pop() ?? registeredShow.path;
|
|
1664
1708
|
} else {
|
|
1665
|
-
const existingFolder = findShowFolder(destRoot, resolvedTitle);
|
|
1709
|
+
const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
|
|
1666
1710
|
if (existingFolder) {
|
|
1667
1711
|
showFolderName = existingFolder;
|
|
1668
1712
|
showPath = (0, import_path11.resolve)(destRoot, existingFolder);
|
|
@@ -1741,9 +1785,9 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1741
1785
|
if (subtitleSourcePath && destSubtitleName) (0, import_fs10.renameSync)(subtitleSourcePath, (0, import_path11.resolve)(seasonPath, destSubtitleName));
|
|
1742
1786
|
if (isDir) (0, import_fs10.rmSync)(entryPath, { recursive: true, force: true });
|
|
1743
1787
|
}
|
|
1744
|
-
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
1788
|
+
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId, "tv");
|
|
1745
1789
|
}
|
|
1746
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.tv
|
|
1790
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("tv")} ${typeColor.tv(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
|
|
1747
1791
|
imported++;
|
|
1748
1792
|
continue;
|
|
1749
1793
|
}
|
|
@@ -1756,7 +1800,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1756
1800
|
}
|
|
1757
1801
|
if (pendingMovies.length > 0) {
|
|
1758
1802
|
spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
|
|
1759
|
-
for (const p of pendingMovies) spinner_default.info(` ${
|
|
1803
|
+
for (const p of pendingMovies) spinner_default.info(` ${typeGlyph("movie")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
|
|
1760
1804
|
let toProcess = [];
|
|
1761
1805
|
if (interactive) {
|
|
1762
1806
|
spinner_default.stop();
|
|
@@ -1785,7 +1829,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1785
1829
|
}
|
|
1786
1830
|
if (pendingTv.length > 0) {
|
|
1787
1831
|
spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
|
|
1788
|
-
for (const p of pendingTv) spinner_default.info(` ${
|
|
1832
|
+
for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
1789
1833
|
skipped += pendingTv.length;
|
|
1790
1834
|
}
|
|
1791
1835
|
if (ignoreSet.size > 0) {
|
|
@@ -1808,20 +1852,51 @@ var import_fs11 = require("fs");
|
|
|
1808
1852
|
var import_termkit11 = require("termkit");
|
|
1809
1853
|
var undo = async () => {
|
|
1810
1854
|
spinner_default.start();
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1855
|
+
const renameRecords = getLastSession();
|
|
1856
|
+
const importRecords = getLastImportSession();
|
|
1857
|
+
if (renameRecords.length === 0 && importRecords.length === 0) {
|
|
1813
1858
|
spinner_default.info("nothing to undo");
|
|
1814
1859
|
spinner_default.stop();
|
|
1815
1860
|
return;
|
|
1816
1861
|
}
|
|
1862
|
+
const useImports = importRecords.length > 0 && (renameRecords.length === 0 || importRecords[0].sessionId > renameRecords[0].sessionId);
|
|
1863
|
+
if (!useImports) {
|
|
1864
|
+
let undone2 = 0;
|
|
1865
|
+
for (const record of renameRecords) {
|
|
1866
|
+
(0, import_fs11.renameSync)(record.newPath, record.oldPath);
|
|
1867
|
+
spinner_default.succeed(`${import_termkit11.Color.green.encoder(record.newPath)} \u2192 ${import_termkit11.Color.white.encoder(record.oldPath)}`);
|
|
1868
|
+
undone2++;
|
|
1869
|
+
}
|
|
1870
|
+
deleteSession(renameRecords[0].sessionId);
|
|
1871
|
+
spinner_default.succeed(`undid ${undone2} renames`);
|
|
1872
|
+
spinner_default.stop();
|
|
1873
|
+
return;
|
|
1874
|
+
}
|
|
1817
1875
|
let undone = 0;
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1876
|
+
let skipped = 0;
|
|
1877
|
+
for (const record of importRecords) {
|
|
1878
|
+
if (record.mode !== "move") {
|
|
1879
|
+
spinner_default.info(`skipped ${record.destinationPath} (${record.mode} \u2014 source file unchanged)`);
|
|
1880
|
+
skipped++;
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
if (record.type === "tv") {
|
|
1884
|
+
spinner_default.info(`skipped TV import \u2014 season folder cannot be cleanly reversed: ${record.destinationPath}`);
|
|
1885
|
+
skipped++;
|
|
1886
|
+
continue;
|
|
1887
|
+
}
|
|
1888
|
+
if (!(0, import_fs11.existsSync)(record.destinationPath)) {
|
|
1889
|
+
spinner_default.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
|
|
1890
|
+
skipped++;
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
(0, import_fs11.renameSync)(record.destinationPath, record.sourcePath);
|
|
1894
|
+
spinner_default.succeed(`${import_termkit11.Color.green.encoder(record.destinationPath)} \u2192 ${import_termkit11.Color.white.encoder(record.sourcePath)}`);
|
|
1821
1895
|
undone++;
|
|
1822
1896
|
}
|
|
1823
|
-
|
|
1824
|
-
spinner_default.succeed(`undid ${undone}
|
|
1897
|
+
deleteImportSession(importRecords[0].sessionId);
|
|
1898
|
+
if (undone > 0) spinner_default.succeed(`undid ${undone} import${undone !== 1 ? "s" : ""}`);
|
|
1899
|
+
if (skipped > 0) spinner_default.info(`skipped ${skipped} item${skipped !== 1 ? "s" : ""} (TV or non-move mode)`);
|
|
1825
1900
|
spinner_default.stop();
|
|
1826
1901
|
};
|
|
1827
1902
|
var undo_default = undo;
|
|
@@ -1984,7 +2059,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
1984
2059
|
return;
|
|
1985
2060
|
}
|
|
1986
2061
|
moveItem(entryPath, destPath);
|
|
1987
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
2062
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "ps3");
|
|
1988
2063
|
spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(destName)}`);
|
|
1989
2064
|
return;
|
|
1990
2065
|
}
|
|
@@ -2005,7 +2080,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2005
2080
|
(0, import_fs12.rmSync)(entryPath);
|
|
2006
2081
|
}
|
|
2007
2082
|
}
|
|
2008
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
2083
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
|
|
2009
2084
|
spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(entry)}`);
|
|
2010
2085
|
return;
|
|
2011
2086
|
}
|
|
@@ -2078,7 +2153,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2078
2153
|
if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs12.renameSync)(subtitleSourcePath2, (0, import_path12.resolve)(seasonPath, destSubtitleName2));
|
|
2079
2154
|
if (isDir) (0, import_fs12.rmSync)(entryPath, { recursive: true, force: true });
|
|
2080
2155
|
}
|
|
2081
|
-
recordImport(sessionId, entryPath, seasonPath, mode);
|
|
2156
|
+
recordImport(sessionId, entryPath, seasonPath, mode, void 0, "tv");
|
|
2082
2157
|
spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
|
|
2083
2158
|
return;
|
|
2084
2159
|
}
|
|
@@ -2116,7 +2191,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2116
2191
|
mode = "copy";
|
|
2117
2192
|
}
|
|
2118
2193
|
if (subtitleSourcePath && destSubtitleName) (0, import_fs12.cpSync)(subtitleSourcePath, (0, import_path12.resolve)(destFolder, destSubtitleName));
|
|
2119
|
-
recordImport(sessionId, entryPath, destFolder, mode);
|
|
2194
|
+
recordImport(sessionId, entryPath, destFolder, mode, void 0, "movie");
|
|
2120
2195
|
} else {
|
|
2121
2196
|
if (isDir) {
|
|
2122
2197
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
@@ -2134,7 +2209,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2134
2209
|
(0, import_fs12.rmSync)(videoSourcePath);
|
|
2135
2210
|
}
|
|
2136
2211
|
}
|
|
2137
|
-
recordImport(sessionId, entryPath, destFolder, "move");
|
|
2212
|
+
recordImport(sessionId, entryPath, destFolder, "move", void 0, "movie");
|
|
2138
2213
|
}
|
|
2139
2214
|
spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(folderName)}`);
|
|
2140
2215
|
};
|
|
@@ -2183,6 +2258,7 @@ var watch_default = watch;
|
|
|
2183
2258
|
configSet,
|
|
2184
2259
|
configShow,
|
|
2185
2260
|
deleteImport,
|
|
2261
|
+
deleteImportSession,
|
|
2186
2262
|
deleteSession,
|
|
2187
2263
|
destAdd,
|
|
2188
2264
|
destRemove,
|
|
@@ -2195,6 +2271,7 @@ var watch_default = watch;
|
|
|
2195
2271
|
getConfig,
|
|
2196
2272
|
getHistory,
|
|
2197
2273
|
getImportByDest,
|
|
2274
|
+
getLastImportSession,
|
|
2198
2275
|
getLastSession,
|
|
2199
2276
|
getMediaInfo,
|
|
2200
2277
|
history,
|
package/dist/index.mjs
CHANGED
|
@@ -54,6 +54,10 @@ var db = () => {
|
|
|
54
54
|
_db.exec("ALTER TABLE shows ADD COLUMN title TEXT");
|
|
55
55
|
} catch {
|
|
56
56
|
}
|
|
57
|
+
try {
|
|
58
|
+
_db.exec("ALTER TABLE imports ADD COLUMN type TEXT");
|
|
59
|
+
} catch {
|
|
60
|
+
}
|
|
57
61
|
return _db;
|
|
58
62
|
};
|
|
59
63
|
var recordRename = (sessionId, oldPath, newPath) => {
|
|
@@ -67,8 +71,16 @@ var getLastSession = () => {
|
|
|
67
71
|
var deleteSession = (sessionId) => {
|
|
68
72
|
db().prepare("DELETE FROM renameHistory WHERE sessionId = ?").run(sessionId);
|
|
69
73
|
};
|
|
70
|
-
var
|
|
71
|
-
db().prepare("
|
|
74
|
+
var getLastImportSession = () => {
|
|
75
|
+
const last = db().prepare("SELECT sessionId FROM imports ORDER BY id DESC LIMIT 1").get();
|
|
76
|
+
if (!last) return [];
|
|
77
|
+
return db().prepare("SELECT * FROM imports WHERE sessionId = ? ORDER BY id DESC").all(last.sessionId);
|
|
78
|
+
};
|
|
79
|
+
var deleteImportSession = (sessionId) => {
|
|
80
|
+
db().prepare("DELETE FROM imports WHERE sessionId = ?").run(sessionId);
|
|
81
|
+
};
|
|
82
|
+
var recordImport = (sessionId, sourcePath, destPath, mode, tmdbId, type) => {
|
|
83
|
+
db().prepare("INSERT INTO imports (sessionId, sourcePath, destinationPath, mode, tmdbId, type) VALUES (?, ?, ?, ?, ?, ?)").run(sessionId, sourcePath, destPath, mode, tmdbId ?? null, type ?? null);
|
|
72
84
|
};
|
|
73
85
|
var getMediaInfo = (filePath) => {
|
|
74
86
|
return db().prepare("SELECT * FROM mediaInfo WHERE filePath = ?").get(filePath);
|
|
@@ -244,10 +256,10 @@ var saveConfig = (config) => {
|
|
|
244
256
|
};
|
|
245
257
|
|
|
246
258
|
// src/helpers/formatEpisode.ts
|
|
247
|
-
var DEFAULT_EPISODE_FORMAT = "{s}x{ee}";
|
|
259
|
+
var DEFAULT_EPISODE_FORMAT = "{s}x{ee} - {title}";
|
|
248
260
|
var DEFAULT_SEASON_FORMAT = "Season {s}";
|
|
249
261
|
var renderEpisode = (format, season, episode, title, name) => {
|
|
250
|
-
return format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).replace(/\{eee\}/g, episode.toString().padStart(3, "0")).replace(/\{ee\}/g, episode.toString().padStart(2, "0")).replace(/\{e\}/g, episode.toString()).replace(/\{
|
|
262
|
+
return format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).replace(/\{eee\}/g, episode.toString().padStart(3, "0")).replace(/\{ee\}/g, episode.toString().padStart(2, "0")).replace(/\{e\}/g, episode.toString()).replace(/\{show\}/g, title ?? "").replace(/\{title\}/g, name ?? "").replace(/(\s*-\s*)+$/, "").replace(/^(\s*-\s*)+/, "").replace(/\s+/g, " ").trim();
|
|
251
263
|
};
|
|
252
264
|
var formatSeasonFolder = (format, season) => format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).trim();
|
|
253
265
|
var formatEpisode = (season, episode, format = DEFAULT_EPISODE_FORMAT, double = false, title, name) => {
|
|
@@ -1310,6 +1322,35 @@ var findShowFolder = (destRoot, title) => {
|
|
|
1310
1322
|
}
|
|
1311
1323
|
}).find((f) => normalize(f) === target) ?? null;
|
|
1312
1324
|
};
|
|
1325
|
+
var findShowFolderByContent = (destRoot, title) => {
|
|
1326
|
+
if (!existsSync9(destRoot)) return null;
|
|
1327
|
+
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1328
|
+
const target = normalize(title);
|
|
1329
|
+
const matchesTitle = (name) => {
|
|
1330
|
+
if (!isTvEpisodeName(name)) return false;
|
|
1331
|
+
const p = parseDownloadName(name);
|
|
1332
|
+
return !!p && normalize(p.title) === target;
|
|
1333
|
+
};
|
|
1334
|
+
for (const folder of readdirSync7(destRoot)) {
|
|
1335
|
+
try {
|
|
1336
|
+
const folderPath = resolve8(destRoot, folder);
|
|
1337
|
+
if (!lstatSync4(folderPath).isDirectory()) continue;
|
|
1338
|
+
const children = readdirSync7(folderPath);
|
|
1339
|
+
if (children.some(matchesTitle)) return folder;
|
|
1340
|
+
for (const child of children) {
|
|
1341
|
+
if (!isSeasonDirName(child)) continue;
|
|
1342
|
+
try {
|
|
1343
|
+
const seasonPath = resolve8(folderPath, child);
|
|
1344
|
+
if (!lstatSync4(seasonPath).isDirectory()) continue;
|
|
1345
|
+
if (readdirSync7(seasonPath).some(matchesTitle)) return folder;
|
|
1346
|
+
} catch {
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
return null;
|
|
1353
|
+
};
|
|
1313
1354
|
var findSeasonFolder = (showPath, season) => {
|
|
1314
1355
|
if (!existsSync9(showPath)) return null;
|
|
1315
1356
|
const folders = readdirSync7(showPath).filter((f) => {
|
|
@@ -1334,11 +1375,12 @@ var classifyMovieConfidence = (entry) => {
|
|
|
1334
1375
|
return "ambiguous";
|
|
1335
1376
|
};
|
|
1336
1377
|
var typeColor = {
|
|
1337
|
-
movie: Color9.
|
|
1338
|
-
tv: Color9.
|
|
1339
|
-
book: Color9.
|
|
1340
|
-
ps3: Color9.
|
|
1378
|
+
movie: (s) => Color9.cyan.encoder(s),
|
|
1379
|
+
tv: (s) => Color9.green.encoder(s),
|
|
1380
|
+
book: (s) => Color9.yellow.encoder(s),
|
|
1381
|
+
ps3: (s) => Color9.magenta.encoder(s)
|
|
1341
1382
|
};
|
|
1383
|
+
var typeGlyph = (t) => typeColor[t]("\u25CF");
|
|
1342
1384
|
var typeTag = (t) => isVerbose() ? Color9.white.faint.encoder(` (${t})`) : "";
|
|
1343
1385
|
var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
|
|
1344
1386
|
const config = getConfig();
|
|
@@ -1411,7 +1453,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1411
1453
|
mode = "copy";
|
|
1412
1454
|
}
|
|
1413
1455
|
if (subtitleSourcePath && destSubtitleName) cpSync(subtitleSourcePath, resolve8(destFolder, destSubtitleName));
|
|
1414
|
-
recordImport(sessionId, entryPath, destFolder, mode, tmdbId);
|
|
1456
|
+
recordImport(sessionId, entryPath, destFolder, mode, tmdbId, "movie");
|
|
1415
1457
|
} else {
|
|
1416
1458
|
if (isDir) {
|
|
1417
1459
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
@@ -1429,10 +1471,10 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1429
1471
|
rmSync2(videoSourcePath);
|
|
1430
1472
|
}
|
|
1431
1473
|
}
|
|
1432
|
-
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1474
|
+
recordImport(sessionId, entryPath, destFolder, "move", tmdbId, "movie");
|
|
1433
1475
|
}
|
|
1434
1476
|
}
|
|
1435
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.movie
|
|
1477
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("movie")} ${typeColor.movie(folderName)}${typeTag("movie")}`);
|
|
1436
1478
|
return true;
|
|
1437
1479
|
};
|
|
1438
1480
|
spinner_default.start();
|
|
@@ -1491,9 +1533,9 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1491
1533
|
}
|
|
1492
1534
|
if (!dryRun) {
|
|
1493
1535
|
moveFolder(entryPath, destPath);
|
|
1494
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
1536
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "ps3");
|
|
1495
1537
|
}
|
|
1496
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.ps3
|
|
1538
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("ps3")} ${typeColor.ps3(destName)}${typeTag("ps3")}`);
|
|
1497
1539
|
imported++;
|
|
1498
1540
|
continue;
|
|
1499
1541
|
}
|
|
@@ -1516,9 +1558,9 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1516
1558
|
rmSync2(entryPath);
|
|
1517
1559
|
}
|
|
1518
1560
|
}
|
|
1519
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
1561
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
|
|
1520
1562
|
}
|
|
1521
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.book
|
|
1563
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("book")} ${typeColor.book(entry)}${typeTag("book")}`);
|
|
1522
1564
|
imported++;
|
|
1523
1565
|
continue;
|
|
1524
1566
|
}
|
|
@@ -1586,7 +1628,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1586
1628
|
showPath = registeredShow.path;
|
|
1587
1629
|
showFolderName = showPath.split("/").pop() ?? registeredShow.path;
|
|
1588
1630
|
} else {
|
|
1589
|
-
const existingFolder = findShowFolder(destRoot, resolvedTitle);
|
|
1631
|
+
const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
|
|
1590
1632
|
if (existingFolder) {
|
|
1591
1633
|
showFolderName = existingFolder;
|
|
1592
1634
|
showPath = resolve8(destRoot, existingFolder);
|
|
@@ -1665,9 +1707,9 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1665
1707
|
if (subtitleSourcePath && destSubtitleName) renameSync3(subtitleSourcePath, resolve8(seasonPath, destSubtitleName));
|
|
1666
1708
|
if (isDir) rmSync2(entryPath, { recursive: true, force: true });
|
|
1667
1709
|
}
|
|
1668
|
-
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
1710
|
+
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId, "tv");
|
|
1669
1711
|
}
|
|
1670
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeColor.tv
|
|
1712
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("tv")} ${typeColor.tv(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
|
|
1671
1713
|
imported++;
|
|
1672
1714
|
continue;
|
|
1673
1715
|
}
|
|
@@ -1680,7 +1722,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1680
1722
|
}
|
|
1681
1723
|
if (pendingMovies.length > 0) {
|
|
1682
1724
|
spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
|
|
1683
|
-
for (const p of pendingMovies) spinner_default.info(` ${
|
|
1725
|
+
for (const p of pendingMovies) spinner_default.info(` ${typeGlyph("movie")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
|
|
1684
1726
|
let toProcess = [];
|
|
1685
1727
|
if (interactive) {
|
|
1686
1728
|
spinner_default.stop();
|
|
@@ -1709,7 +1751,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1709
1751
|
}
|
|
1710
1752
|
if (pendingTv.length > 0) {
|
|
1711
1753
|
spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
|
|
1712
|
-
for (const p of pendingTv) spinner_default.info(` ${
|
|
1754
|
+
for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
1713
1755
|
skipped += pendingTv.length;
|
|
1714
1756
|
}
|
|
1715
1757
|
if (ignoreSet.size > 0) {
|
|
@@ -1728,37 +1770,68 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1728
1770
|
var scan_default = scan;
|
|
1729
1771
|
|
|
1730
1772
|
// src/actions/undo.ts
|
|
1731
|
-
import { renameSync as renameSync4 } from "fs";
|
|
1773
|
+
import { existsSync as existsSync10, renameSync as renameSync4 } from "fs";
|
|
1732
1774
|
import { Color as Color10 } from "termkit";
|
|
1733
1775
|
var undo = async () => {
|
|
1734
1776
|
spinner_default.start();
|
|
1735
|
-
const
|
|
1736
|
-
|
|
1777
|
+
const renameRecords = getLastSession();
|
|
1778
|
+
const importRecords = getLastImportSession();
|
|
1779
|
+
if (renameRecords.length === 0 && importRecords.length === 0) {
|
|
1737
1780
|
spinner_default.info("nothing to undo");
|
|
1738
1781
|
spinner_default.stop();
|
|
1739
1782
|
return;
|
|
1740
1783
|
}
|
|
1784
|
+
const useImports = importRecords.length > 0 && (renameRecords.length === 0 || importRecords[0].sessionId > renameRecords[0].sessionId);
|
|
1785
|
+
if (!useImports) {
|
|
1786
|
+
let undone2 = 0;
|
|
1787
|
+
for (const record of renameRecords) {
|
|
1788
|
+
renameSync4(record.newPath, record.oldPath);
|
|
1789
|
+
spinner_default.succeed(`${Color10.green.encoder(record.newPath)} \u2192 ${Color10.white.encoder(record.oldPath)}`);
|
|
1790
|
+
undone2++;
|
|
1791
|
+
}
|
|
1792
|
+
deleteSession(renameRecords[0].sessionId);
|
|
1793
|
+
spinner_default.succeed(`undid ${undone2} renames`);
|
|
1794
|
+
spinner_default.stop();
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1741
1797
|
let undone = 0;
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1798
|
+
let skipped = 0;
|
|
1799
|
+
for (const record of importRecords) {
|
|
1800
|
+
if (record.mode !== "move") {
|
|
1801
|
+
spinner_default.info(`skipped ${record.destinationPath} (${record.mode} \u2014 source file unchanged)`);
|
|
1802
|
+
skipped++;
|
|
1803
|
+
continue;
|
|
1804
|
+
}
|
|
1805
|
+
if (record.type === "tv") {
|
|
1806
|
+
spinner_default.info(`skipped TV import \u2014 season folder cannot be cleanly reversed: ${record.destinationPath}`);
|
|
1807
|
+
skipped++;
|
|
1808
|
+
continue;
|
|
1809
|
+
}
|
|
1810
|
+
if (!existsSync10(record.destinationPath)) {
|
|
1811
|
+
spinner_default.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
|
|
1812
|
+
skipped++;
|
|
1813
|
+
continue;
|
|
1814
|
+
}
|
|
1815
|
+
renameSync4(record.destinationPath, record.sourcePath);
|
|
1816
|
+
spinner_default.succeed(`${Color10.green.encoder(record.destinationPath)} \u2192 ${Color10.white.encoder(record.sourcePath)}`);
|
|
1745
1817
|
undone++;
|
|
1746
1818
|
}
|
|
1747
|
-
|
|
1748
|
-
spinner_default.succeed(`undid ${undone}
|
|
1819
|
+
deleteImportSession(importRecords[0].sessionId);
|
|
1820
|
+
if (undone > 0) spinner_default.succeed(`undid ${undone} import${undone !== 1 ? "s" : ""}`);
|
|
1821
|
+
if (skipped > 0) spinner_default.info(`skipped ${skipped} item${skipped !== 1 ? "s" : ""} (TV or non-move mode)`);
|
|
1749
1822
|
spinner_default.stop();
|
|
1750
1823
|
};
|
|
1751
1824
|
var undo_default = undo;
|
|
1752
1825
|
|
|
1753
1826
|
// src/actions/watch.ts
|
|
1754
1827
|
import chokidar from "chokidar";
|
|
1755
|
-
import { cpSync as cpSync2, existsSync as
|
|
1828
|
+
import { cpSync as cpSync2, existsSync as existsSync11, linkSync as linkSync2, lstatSync as lstatSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync8, renameSync as renameSync5, rmSync as rmSync3, statSync as statSync3 } from "fs";
|
|
1756
1829
|
import { basename as basename3, dirname as dirname3, resolve as resolve9 } from "path";
|
|
1757
1830
|
import { Color as Color11 } from "termkit";
|
|
1758
1831
|
var sameDev2 = (a, b) => {
|
|
1759
1832
|
try {
|
|
1760
1833
|
let bExisting = b;
|
|
1761
|
-
while (!
|
|
1834
|
+
while (!existsSync11(bExisting)) bExisting = dirname3(bExisting);
|
|
1762
1835
|
return statSync3(a).dev === statSync3(bExisting).dev;
|
|
1763
1836
|
} catch {
|
|
1764
1837
|
return false;
|
|
@@ -1857,7 +1930,7 @@ var expandWatchPath = (p) => {
|
|
|
1857
1930
|
return [p];
|
|
1858
1931
|
};
|
|
1859
1932
|
var findSeasonFolder2 = (showPath, season) => {
|
|
1860
|
-
if (!
|
|
1933
|
+
if (!existsSync11(showPath)) return null;
|
|
1861
1934
|
const folders = readdirSync8(showPath).filter((f) => {
|
|
1862
1935
|
try {
|
|
1863
1936
|
return lstatSync5(resolve9(showPath, f)).isDirectory();
|
|
@@ -1903,18 +1976,18 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
1903
1976
|
if (!nameMatch || !id) return;
|
|
1904
1977
|
const destName = `${nameMatch[0]} [${id}]`;
|
|
1905
1978
|
const destPath = resolve9(destRoot, destName);
|
|
1906
|
-
if (
|
|
1979
|
+
if (existsSync11(destPath)) {
|
|
1907
1980
|
spinner_default.warn(`already exists: ${destName}`);
|
|
1908
1981
|
return;
|
|
1909
1982
|
}
|
|
1910
1983
|
moveItem(entryPath, destPath);
|
|
1911
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
1984
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "ps3");
|
|
1912
1985
|
spinner_default.succeed(`imported ${Color11.green.encoder(destName)}`);
|
|
1913
1986
|
return;
|
|
1914
1987
|
}
|
|
1915
1988
|
if (detectedType === "book") {
|
|
1916
1989
|
const destPath = resolve9(destRoot, entry);
|
|
1917
|
-
if (
|
|
1990
|
+
if (existsSync11(destPath)) {
|
|
1918
1991
|
spinner_default.warn(`already exists: ${entry}`);
|
|
1919
1992
|
return;
|
|
1920
1993
|
}
|
|
@@ -1929,7 +2002,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
1929
2002
|
rmSync3(entryPath);
|
|
1930
2003
|
}
|
|
1931
2004
|
}
|
|
1932
|
-
recordImport(sessionId, entryPath, destPath, "move");
|
|
2005
|
+
recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
|
|
1933
2006
|
spinner_default.succeed(`imported ${Color11.green.encoder(entry)}`);
|
|
1934
2007
|
return;
|
|
1935
2008
|
}
|
|
@@ -1970,7 +2043,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
1970
2043
|
const destVideoName2 = `${episodeName}.${videoExt2}`;
|
|
1971
2044
|
const destVideoPath = resolve9(seasonPath, destVideoName2);
|
|
1972
2045
|
const videoSourcePath2 = isDir ? resolve9(entryPath, videoFile2) : entryPath;
|
|
1973
|
-
if (
|
|
2046
|
+
if (existsSync11(destVideoPath)) {
|
|
1974
2047
|
spinner_default.warn(`already exists: ${episodeName}`);
|
|
1975
2048
|
return;
|
|
1976
2049
|
}
|
|
@@ -2002,14 +2075,14 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2002
2075
|
if (subtitleSourcePath2 && destSubtitleName2) renameSync5(subtitleSourcePath2, resolve9(seasonPath, destSubtitleName2));
|
|
2003
2076
|
if (isDir) rmSync3(entryPath, { recursive: true, force: true });
|
|
2004
2077
|
}
|
|
2005
|
-
recordImport(sessionId, entryPath, seasonPath, mode);
|
|
2078
|
+
recordImport(sessionId, entryPath, seasonPath, mode, void 0, "tv");
|
|
2006
2079
|
spinner_default.succeed(`imported ${Color11.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
|
|
2007
2080
|
return;
|
|
2008
2081
|
}
|
|
2009
2082
|
const edition = detectEdition(entry);
|
|
2010
2083
|
const folderName = formatMovieName(movieFormat, parsed.title, parsed.year, edition);
|
|
2011
2084
|
const destFolder = resolve9(destRoot, folderName);
|
|
2012
|
-
if (
|
|
2085
|
+
if (existsSync11(destFolder)) {
|
|
2013
2086
|
spinner_default.warn(`already exists: ${folderName}`);
|
|
2014
2087
|
return;
|
|
2015
2088
|
}
|
|
@@ -2040,7 +2113,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2040
2113
|
mode = "copy";
|
|
2041
2114
|
}
|
|
2042
2115
|
if (subtitleSourcePath && destSubtitleName) cpSync2(subtitleSourcePath, resolve9(destFolder, destSubtitleName));
|
|
2043
|
-
recordImport(sessionId, entryPath, destFolder, mode);
|
|
2116
|
+
recordImport(sessionId, entryPath, destFolder, mode, void 0, "movie");
|
|
2044
2117
|
} else {
|
|
2045
2118
|
if (isDir) {
|
|
2046
2119
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
@@ -2058,7 +2131,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2058
2131
|
rmSync3(videoSourcePath);
|
|
2059
2132
|
}
|
|
2060
2133
|
}
|
|
2061
|
-
recordImport(sessionId, entryPath, destFolder, "move");
|
|
2134
|
+
recordImport(sessionId, entryPath, destFolder, "move", void 0, "movie");
|
|
2062
2135
|
}
|
|
2063
2136
|
spinner_default.succeed(`imported ${Color11.green.encoder(folderName)}`);
|
|
2064
2137
|
};
|
|
@@ -2106,6 +2179,7 @@ export {
|
|
|
2106
2179
|
configSet,
|
|
2107
2180
|
configShow,
|
|
2108
2181
|
deleteImport,
|
|
2182
|
+
deleteImportSession,
|
|
2109
2183
|
deleteSession,
|
|
2110
2184
|
destAdd,
|
|
2111
2185
|
destRemove,
|
|
@@ -2118,6 +2192,7 @@ export {
|
|
|
2118
2192
|
getConfig,
|
|
2119
2193
|
getHistory,
|
|
2120
2194
|
getImportByDest,
|
|
2195
|
+
getLastImportSession,
|
|
2121
2196
|
getLastSession,
|
|
2122
2197
|
getMediaInfo,
|
|
2123
2198
|
history_default as history,
|
package/package.json
CHANGED