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/cli.js
CHANGED
|
@@ -1496,8 +1496,8 @@ var reset = async ({ dir: inputDir, double }) => {
|
|
|
1496
1496
|
var reset_default = reset;
|
|
1497
1497
|
|
|
1498
1498
|
// src/actions/scan.ts
|
|
1499
|
-
var
|
|
1500
|
-
var
|
|
1499
|
+
var import_fs15 = require("fs");
|
|
1500
|
+
var import_path15 = require("path");
|
|
1501
1501
|
var import_termkit14 = require("termkit");
|
|
1502
1502
|
|
|
1503
1503
|
// src/helpers/detectEdition.ts
|
|
@@ -1518,10 +1518,49 @@ var detectEdition = (filename) => {
|
|
|
1518
1518
|
return null;
|
|
1519
1519
|
};
|
|
1520
1520
|
|
|
1521
|
+
// src/helpers/trash.ts
|
|
1522
|
+
var import_child_process2 = require("child_process");
|
|
1523
|
+
var import_fs14 = require("fs");
|
|
1524
|
+
var import_os3 = require("os");
|
|
1525
|
+
var import_path14 = require("path");
|
|
1526
|
+
var trashDir = () => {
|
|
1527
|
+
if (process.platform === "linux") return (0, import_path14.join)((0, import_os3.homedir)(), ".local", "share", "Trash", "files");
|
|
1528
|
+
return (0, import_path14.join)((0, import_os3.homedir)(), ".Trash");
|
|
1529
|
+
};
|
|
1530
|
+
var trashPath = (filePath, opts) => {
|
|
1531
|
+
if (process.platform === "darwin" || process.platform === "linux") {
|
|
1532
|
+
try {
|
|
1533
|
+
const dir = trashDir();
|
|
1534
|
+
if (!(0, import_fs14.existsSync)(dir)) (0, import_fs14.mkdirSync)(dir, { recursive: true });
|
|
1535
|
+
const name = (0, import_path14.basename)(filePath);
|
|
1536
|
+
let dest = (0, import_path14.join)(dir, name);
|
|
1537
|
+
let i = 1;
|
|
1538
|
+
while ((0, import_fs14.existsSync)(dest)) dest = (0, import_path14.join)(dir, `${name} ${i++}`);
|
|
1539
|
+
(0, import_fs14.renameSync)(filePath, dest);
|
|
1540
|
+
return;
|
|
1541
|
+
} catch {
|
|
1542
|
+
}
|
|
1543
|
+
} else if (process.platform === "win32") {
|
|
1544
|
+
try {
|
|
1545
|
+
const escaped = filePath.replace(/'/g, "''");
|
|
1546
|
+
const isDir = opts?.recursive ?? false;
|
|
1547
|
+
const method = isDir ? "DeleteDirectory" : "DeleteFile";
|
|
1548
|
+
(0, import_child_process2.execSync)(
|
|
1549
|
+
`powershell -NoProfile -NonInteractive -Command "Add-Type -AssemblyName Microsoft.VisualBasic; [Microsoft.VisualBasic.FileIO.FileSystem]::${method}('${escaped}', 'OnlyErrorDialogs', 'SendToRecycleBin')"`,
|
|
1550
|
+
{ stdio: "ignore" }
|
|
1551
|
+
);
|
|
1552
|
+
return;
|
|
1553
|
+
} catch {
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
(0, import_fs14.rmSync)(filePath, { recursive: opts?.recursive ?? false, force: true });
|
|
1557
|
+
};
|
|
1558
|
+
|
|
1521
1559
|
// src/helpers/parseDownloadName.ts
|
|
1522
1560
|
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"]);
|
|
1523
1561
|
var TV_PATTERN = /^(.*?)[.\s_-]*(?:S(\d{2,3})E(\d{2,3})|(\d{1,2})x(\d{2,3})|Season[\s.](\d+))/i;
|
|
1524
|
-
var parseDownloadName = (
|
|
1562
|
+
var parseDownloadName = (rawName) => {
|
|
1563
|
+
const name = rawName.replace(/^[\w.-]+\.\w{2,6}\s+-+\s+/i, "");
|
|
1525
1564
|
const base = name.replace(/\.[a-z0-9]{2,4}$/i, "");
|
|
1526
1565
|
const tvMatch = TV_PATTERN.exec(base);
|
|
1527
1566
|
if (tvMatch) {
|
|
@@ -1565,45 +1604,63 @@ var bookExtensions_default = ["epub", "mobi", "azw3", "azw"];
|
|
|
1565
1604
|
var sameDev = (a, b) => {
|
|
1566
1605
|
try {
|
|
1567
1606
|
let bExisting = b;
|
|
1568
|
-
while (!(0,
|
|
1569
|
-
return (0,
|
|
1607
|
+
while (!(0, import_fs15.existsSync)(bExisting)) bExisting = (0, import_path15.dirname)(bExisting);
|
|
1608
|
+
return (0, import_fs15.statSync)(a).dev === (0, import_fs15.statSync)(bExisting).dev;
|
|
1570
1609
|
} catch {
|
|
1571
1610
|
return false;
|
|
1572
1611
|
}
|
|
1573
1612
|
};
|
|
1574
1613
|
var moveFolder = (src, dest) => {
|
|
1575
1614
|
if (sameDev(src, dest)) {
|
|
1576
|
-
(0,
|
|
1615
|
+
(0, import_fs15.renameSync)(src, dest);
|
|
1577
1616
|
} else {
|
|
1578
|
-
(0,
|
|
1579
|
-
(
|
|
1617
|
+
(0, import_fs15.cpSync)(src, dest, { recursive: true });
|
|
1618
|
+
trashPath(src, { recursive: true });
|
|
1580
1619
|
}
|
|
1581
1620
|
};
|
|
1582
|
-
var findVideo = (dir) => (0,
|
|
1621
|
+
var findVideo = (dir) => (0, import_fs15.readdirSync)(dir).find((f) => {
|
|
1583
1622
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1584
1623
|
return ext && videoExtensions_default.includes(ext);
|
|
1585
1624
|
}) ?? null;
|
|
1586
|
-
var containsBook = (dir, depth = 2) => (0,
|
|
1625
|
+
var containsBook = (dir, depth = 2) => (0, import_fs15.readdirSync)(dir).some((f) => {
|
|
1587
1626
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1588
1627
|
if (ext && bookExtensions_default.includes(ext)) return true;
|
|
1589
1628
|
if (depth > 1) {
|
|
1590
1629
|
try {
|
|
1591
|
-
const sub = (0,
|
|
1592
|
-
if ((0,
|
|
1630
|
+
const sub = (0, import_path15.resolve)(dir, f);
|
|
1631
|
+
if ((0, import_fs15.lstatSync)(sub).isDirectory()) return containsBook(sub, depth - 1);
|
|
1593
1632
|
} catch {
|
|
1594
1633
|
}
|
|
1595
1634
|
}
|
|
1596
1635
|
return false;
|
|
1597
1636
|
});
|
|
1637
|
+
var containsPdf = (dir) => {
|
|
1638
|
+
try {
|
|
1639
|
+
return (0, import_fs15.readdirSync)(dir).some((f) => /\.pdf$/i.test(f));
|
|
1640
|
+
} catch {
|
|
1641
|
+
return false;
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
var countVideos = (dir) => {
|
|
1645
|
+
try {
|
|
1646
|
+
return (0, import_fs15.readdirSync)(dir).filter((f) => {
|
|
1647
|
+
if (/\bsample\b/i.test(f)) return false;
|
|
1648
|
+
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1649
|
+
return !!(ext && videoExtensions_default.includes(ext));
|
|
1650
|
+
}).length;
|
|
1651
|
+
} catch {
|
|
1652
|
+
return 0;
|
|
1653
|
+
}
|
|
1654
|
+
};
|
|
1598
1655
|
var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
|
|
1599
1656
|
var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
|
|
1600
1657
|
var gatherEntries = (source) => {
|
|
1601
1658
|
const result = [];
|
|
1602
|
-
for (const name of (0,
|
|
1603
|
-
const fullPath = (0,
|
|
1659
|
+
for (const name of (0, import_fs15.readdirSync)(source)) {
|
|
1660
|
+
const fullPath = (0, import_path15.resolve)(source, name);
|
|
1604
1661
|
let isDir;
|
|
1605
1662
|
try {
|
|
1606
|
-
isDir = (0,
|
|
1663
|
+
isDir = (0, import_fs15.lstatSync)(fullPath).isDirectory();
|
|
1607
1664
|
} catch {
|
|
1608
1665
|
continue;
|
|
1609
1666
|
}
|
|
@@ -1621,17 +1678,17 @@ var gatherEntries = (source) => {
|
|
|
1621
1678
|
}
|
|
1622
1679
|
let children;
|
|
1623
1680
|
try {
|
|
1624
|
-
children = (0,
|
|
1681
|
+
children = (0, import_fs15.readdirSync)(fullPath);
|
|
1625
1682
|
} catch {
|
|
1626
1683
|
result.push({ entry: name, entryPath: fullPath, isDir: true });
|
|
1627
1684
|
continue;
|
|
1628
1685
|
}
|
|
1629
1686
|
if (children.some((c) => isTvEpisodeName(c))) {
|
|
1630
1687
|
for (const child of children) {
|
|
1631
|
-
const childPath = (0,
|
|
1688
|
+
const childPath = (0, import_path15.resolve)(fullPath, child);
|
|
1632
1689
|
let childIsDir;
|
|
1633
1690
|
try {
|
|
1634
|
-
childIsDir = (0,
|
|
1691
|
+
childIsDir = (0, import_fs15.lstatSync)(childPath).isDirectory();
|
|
1635
1692
|
} catch {
|
|
1636
1693
|
continue;
|
|
1637
1694
|
}
|
|
@@ -1643,25 +1700,25 @@ var gatherEntries = (source) => {
|
|
|
1643
1700
|
}
|
|
1644
1701
|
const seasonDirs = children.filter((c) => {
|
|
1645
1702
|
try {
|
|
1646
|
-
return isSeasonDirName(c) && (0,
|
|
1703
|
+
return isSeasonDirName(c) && (0, import_fs15.lstatSync)((0, import_path15.resolve)(fullPath, c)).isDirectory();
|
|
1647
1704
|
} catch {
|
|
1648
1705
|
return false;
|
|
1649
1706
|
}
|
|
1650
1707
|
});
|
|
1651
1708
|
if (seasonDirs.length > 0) {
|
|
1652
1709
|
for (const seasonDir of seasonDirs) {
|
|
1653
|
-
const seasonPath = (0,
|
|
1710
|
+
const seasonPath = (0, import_path15.resolve)(fullPath, seasonDir);
|
|
1654
1711
|
let seasonChildren;
|
|
1655
1712
|
try {
|
|
1656
|
-
seasonChildren = (0,
|
|
1713
|
+
seasonChildren = (0, import_fs15.readdirSync)(seasonPath);
|
|
1657
1714
|
} catch {
|
|
1658
1715
|
continue;
|
|
1659
1716
|
}
|
|
1660
1717
|
for (const child of seasonChildren) {
|
|
1661
|
-
const childPath = (0,
|
|
1718
|
+
const childPath = (0, import_path15.resolve)(seasonPath, child);
|
|
1662
1719
|
let childIsDir;
|
|
1663
1720
|
try {
|
|
1664
|
-
childIsDir = (0,
|
|
1721
|
+
childIsDir = (0, import_fs15.lstatSync)(childPath).isDirectory();
|
|
1665
1722
|
} catch {
|
|
1666
1723
|
continue;
|
|
1667
1724
|
}
|
|
@@ -1677,19 +1734,19 @@ var gatherEntries = (source) => {
|
|
|
1677
1734
|
return result;
|
|
1678
1735
|
};
|
|
1679
1736
|
var findShowFolder = (destRoot, title) => {
|
|
1680
|
-
if (!(0,
|
|
1737
|
+
if (!(0, import_fs15.existsSync)(destRoot)) return null;
|
|
1681
1738
|
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1682
1739
|
const target = normalize(title);
|
|
1683
|
-
return (0,
|
|
1740
|
+
return (0, import_fs15.readdirSync)(destRoot).filter((f) => {
|
|
1684
1741
|
try {
|
|
1685
|
-
return (0,
|
|
1742
|
+
return (0, import_fs15.lstatSync)((0, import_path15.resolve)(destRoot, f)).isDirectory();
|
|
1686
1743
|
} catch {
|
|
1687
1744
|
return false;
|
|
1688
1745
|
}
|
|
1689
1746
|
}).find((f) => normalize(f) === target) ?? null;
|
|
1690
1747
|
};
|
|
1691
1748
|
var findShowFolderByContent = (destRoot, title) => {
|
|
1692
|
-
if (!(0,
|
|
1749
|
+
if (!(0, import_fs15.existsSync)(destRoot)) return null;
|
|
1693
1750
|
const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1694
1751
|
const target = normalize(title);
|
|
1695
1752
|
const matchesTitle = (name) => {
|
|
@@ -1697,18 +1754,18 @@ var findShowFolderByContent = (destRoot, title) => {
|
|
|
1697
1754
|
const p = parseDownloadName(name);
|
|
1698
1755
|
return !!p && normalize(p.title) === target;
|
|
1699
1756
|
};
|
|
1700
|
-
for (const folder of (0,
|
|
1757
|
+
for (const folder of (0, import_fs15.readdirSync)(destRoot)) {
|
|
1701
1758
|
try {
|
|
1702
|
-
const folderPath = (0,
|
|
1703
|
-
if (!(0,
|
|
1704
|
-
const children = (0,
|
|
1759
|
+
const folderPath = (0, import_path15.resolve)(destRoot, folder);
|
|
1760
|
+
if (!(0, import_fs15.lstatSync)(folderPath).isDirectory()) continue;
|
|
1761
|
+
const children = (0, import_fs15.readdirSync)(folderPath);
|
|
1705
1762
|
if (children.some(matchesTitle)) return folder;
|
|
1706
1763
|
for (const child of children) {
|
|
1707
1764
|
if (!isSeasonDirName(child)) continue;
|
|
1708
1765
|
try {
|
|
1709
|
-
const seasonPath = (0,
|
|
1710
|
-
if (!(0,
|
|
1711
|
-
if ((0,
|
|
1766
|
+
const seasonPath = (0, import_path15.resolve)(folderPath, child);
|
|
1767
|
+
if (!(0, import_fs15.lstatSync)(seasonPath).isDirectory()) continue;
|
|
1768
|
+
if ((0, import_fs15.readdirSync)(seasonPath).some(matchesTitle)) return folder;
|
|
1712
1769
|
} catch {
|
|
1713
1770
|
}
|
|
1714
1771
|
}
|
|
@@ -1717,15 +1774,19 @@ var findShowFolderByContent = (destRoot, title) => {
|
|
|
1717
1774
|
}
|
|
1718
1775
|
return null;
|
|
1719
1776
|
};
|
|
1720
|
-
var findSeasonFolder = (showPath, season) => {
|
|
1721
|
-
if (!(0,
|
|
1722
|
-
const folders = (0,
|
|
1777
|
+
var findSeasonFolder = (showPath, season, specialsFolder) => {
|
|
1778
|
+
if (!(0, import_fs15.existsSync)(showPath)) return null;
|
|
1779
|
+
const folders = (0, import_fs15.readdirSync)(showPath).filter((f) => {
|
|
1723
1780
|
try {
|
|
1724
|
-
return (0,
|
|
1781
|
+
return (0, import_fs15.lstatSync)((0, import_path15.resolve)(showPath, f)).isDirectory();
|
|
1725
1782
|
} catch {
|
|
1726
1783
|
return false;
|
|
1727
1784
|
}
|
|
1728
1785
|
});
|
|
1786
|
+
if (season === 0 && specialsFolder) {
|
|
1787
|
+
const existing = folders.find((f) => f.toLowerCase() === specialsFolder.toLowerCase());
|
|
1788
|
+
if (existing) return existing;
|
|
1789
|
+
}
|
|
1729
1790
|
return folders.find((f) => {
|
|
1730
1791
|
const match = f.match(/(?:season|s)\s*0*(\d+)/i);
|
|
1731
1792
|
return match && parseInt(match[1]) === season;
|
|
@@ -1754,11 +1815,13 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1754
1815
|
const language = config.language ?? "eng";
|
|
1755
1816
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
1756
1817
|
const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
|
|
1818
|
+
const specialsFolder = config.specialsFolder ?? "Specials";
|
|
1757
1819
|
const lookupMovie = async (parsed) => {
|
|
1758
1820
|
let tmdbId;
|
|
1759
1821
|
let resolvedTitle = parsed.title;
|
|
1760
1822
|
let resolvedYear = parsed.year;
|
|
1761
1823
|
if (config.tmdbApiKey) {
|
|
1824
|
+
spinner_default.text = `TMDb: ${parsed.title}`;
|
|
1762
1825
|
const results = await searchMovie(parsed.title, parsed.year, config.tmdbApiKey);
|
|
1763
1826
|
if (results.length === 1) {
|
|
1764
1827
|
tmdbId = results[0].id;
|
|
@@ -1786,8 +1849,8 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1786
1849
|
const importMovie = async (entry, entryPath, isDir, resolvedTitle, resolvedYear, tmdbId, destRoot) => {
|
|
1787
1850
|
const edition = detectEdition(entry);
|
|
1788
1851
|
const folderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear, edition);
|
|
1789
|
-
const destFolder = (0,
|
|
1790
|
-
if ((0,
|
|
1852
|
+
const destFolder = (0, import_path15.resolve)(destRoot, folderName);
|
|
1853
|
+
if ((0, import_fs15.existsSync)(destFolder)) {
|
|
1791
1854
|
spinner_default.warn(`already exists: ${folderName}`);
|
|
1792
1855
|
return false;
|
|
1793
1856
|
}
|
|
@@ -1798,43 +1861,43 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1798
1861
|
}
|
|
1799
1862
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
1800
1863
|
const destVideoName = `${folderName}.${videoExt}`;
|
|
1801
|
-
const videoSourcePath = isDir ? (0,
|
|
1802
|
-
const dirFiles = isDir ? (0,
|
|
1864
|
+
const videoSourcePath = isDir ? (0, import_path15.resolve)(entryPath, videoFile) : entryPath;
|
|
1865
|
+
const dirFiles = isDir ? (0, import_fs15.readdirSync)(entryPath) : [];
|
|
1803
1866
|
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1804
1867
|
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1805
|
-
const subtitleSourcePath = subtitle ? (0,
|
|
1868
|
+
const subtitleSourcePath = subtitle ? (0, import_path15.resolve)(entryPath, subtitle) : null;
|
|
1806
1869
|
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
1807
1870
|
if (!dryRun) {
|
|
1808
1871
|
if (useHardlink) {
|
|
1809
|
-
(0,
|
|
1810
|
-
const destVideoPath = (0,
|
|
1872
|
+
(0, import_fs15.mkdirSync)(destFolder, { recursive: true });
|
|
1873
|
+
const destVideoPath = (0, import_path15.resolve)(destFolder, destVideoName);
|
|
1811
1874
|
let mode;
|
|
1812
1875
|
try {
|
|
1813
1876
|
if (!sameDev(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
|
|
1814
|
-
(0,
|
|
1877
|
+
(0, import_fs15.linkSync)(videoSourcePath, destVideoPath);
|
|
1815
1878
|
mode = "hardlink";
|
|
1816
1879
|
} catch {
|
|
1817
1880
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
|
|
1818
|
-
(0,
|
|
1881
|
+
(0, import_fs15.cpSync)(videoSourcePath, destVideoPath);
|
|
1819
1882
|
mode = "copy";
|
|
1820
1883
|
}
|
|
1821
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
1884
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs15.cpSync)(subtitleSourcePath, (0, import_path15.resolve)(destFolder, destSubtitleName));
|
|
1822
1885
|
recordImport(sessionId, entryPath, destFolder, mode, tmdbId, "movie");
|
|
1823
1886
|
} else {
|
|
1824
1887
|
if (isDir) {
|
|
1825
1888
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
1826
|
-
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (
|
|
1827
|
-
(0,
|
|
1828
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
1889
|
+
for (const f of dirFiles.filter((f2) => !keep.has(f2))) trashPath((0, import_path15.resolve)(entryPath, f), { recursive: true });
|
|
1890
|
+
(0, import_fs15.renameSync)(videoSourcePath, (0, import_path15.resolve)(entryPath, destVideoName));
|
|
1891
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs15.renameSync)(subtitleSourcePath, (0, import_path15.resolve)(entryPath, destSubtitleName));
|
|
1829
1892
|
moveFolder(entryPath, destFolder);
|
|
1830
1893
|
} else {
|
|
1831
|
-
(0,
|
|
1832
|
-
const destVideoPath = (0,
|
|
1894
|
+
(0, import_fs15.mkdirSync)(destFolder, { recursive: true });
|
|
1895
|
+
const destVideoPath = (0, import_path15.resolve)(destFolder, destVideoName);
|
|
1833
1896
|
if (sameDev(videoSourcePath, destRoot)) {
|
|
1834
|
-
(0,
|
|
1897
|
+
(0, import_fs15.renameSync)(videoSourcePath, destVideoPath);
|
|
1835
1898
|
} else {
|
|
1836
|
-
(0,
|
|
1837
|
-
(
|
|
1899
|
+
(0, import_fs15.cpSync)(videoSourcePath, destVideoPath);
|
|
1900
|
+
trashPath(videoSourcePath);
|
|
1838
1901
|
}
|
|
1839
1902
|
}
|
|
1840
1903
|
recordImport(sessionId, entryPath, destFolder, "move", tmdbId, "movie");
|
|
@@ -1848,15 +1911,18 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1848
1911
|
let imported = 0, skipped = 0;
|
|
1849
1912
|
const pendingMovies = [];
|
|
1850
1913
|
const pendingTv = [];
|
|
1914
|
+
const pendingBooks = [];
|
|
1915
|
+
const pendingAnime = [];
|
|
1851
1916
|
const ignoreSet = new Set(config.ignore ?? []);
|
|
1852
1917
|
const seenIgnored = /* @__PURE__ */ new Set();
|
|
1853
1918
|
for (const source of config.sources) {
|
|
1854
|
-
if (!(0,
|
|
1919
|
+
if (!(0, import_fs15.existsSync)(source)) {
|
|
1855
1920
|
spinner_default.warn(`source not found: ${import_termkit14.Color.white.encoder(source)}`);
|
|
1856
1921
|
continue;
|
|
1857
1922
|
}
|
|
1858
1923
|
spinner_default.text = `scanning ${import_termkit14.Color.white.encoder(source)}`;
|
|
1859
1924
|
for (const { entry, entryPath, isDir } of gatherEntries(source)) {
|
|
1925
|
+
spinner_default.text = `scanning: ${entry}`;
|
|
1860
1926
|
if (ignoreSet.has(entry)) {
|
|
1861
1927
|
seenIgnored.add(entry);
|
|
1862
1928
|
if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
|
|
@@ -1891,8 +1957,8 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1891
1957
|
continue;
|
|
1892
1958
|
}
|
|
1893
1959
|
const destName = `${nameMatch[0]} [${id}]`;
|
|
1894
|
-
const destPath = (0,
|
|
1895
|
-
if ((0,
|
|
1960
|
+
const destPath = (0, import_path15.resolve)(destRoot, destName);
|
|
1961
|
+
if ((0, import_fs15.existsSync)(destPath)) {
|
|
1896
1962
|
spinner_default.warn(`already exists: ${destName}`);
|
|
1897
1963
|
skipped++;
|
|
1898
1964
|
continue;
|
|
@@ -1906,8 +1972,8 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1906
1972
|
continue;
|
|
1907
1973
|
}
|
|
1908
1974
|
if (detectedType === "book") {
|
|
1909
|
-
const destPath = (0,
|
|
1910
|
-
if ((0,
|
|
1975
|
+
const destPath = (0, import_path15.resolve)(destRoot, entry);
|
|
1976
|
+
if ((0, import_fs15.existsSync)(destPath)) {
|
|
1911
1977
|
spinner_default.warn(`already exists: ${entry}`);
|
|
1912
1978
|
skipped++;
|
|
1913
1979
|
continue;
|
|
@@ -1916,12 +1982,12 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1916
1982
|
if (isDir || isBookDir) {
|
|
1917
1983
|
moveFolder(entryPath, destPath);
|
|
1918
1984
|
} else {
|
|
1919
|
-
(0,
|
|
1985
|
+
(0, import_fs15.mkdirSync)(destRoot, { recursive: true });
|
|
1920
1986
|
if (sameDev(entryPath, destRoot)) {
|
|
1921
|
-
(0,
|
|
1987
|
+
(0, import_fs15.renameSync)(entryPath, destPath);
|
|
1922
1988
|
} else {
|
|
1923
|
-
(0,
|
|
1924
|
-
(0,
|
|
1989
|
+
(0, import_fs15.cpSync)(entryPath, destPath);
|
|
1990
|
+
(0, import_fs15.rmSync)(entryPath);
|
|
1925
1991
|
}
|
|
1926
1992
|
}
|
|
1927
1993
|
recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
|
|
@@ -1930,6 +1996,22 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1930
1996
|
imported++;
|
|
1931
1997
|
continue;
|
|
1932
1998
|
}
|
|
1999
|
+
if (detectedType === "movie" && isDir) {
|
|
2000
|
+
const videoCount = countVideos(entryPath);
|
|
2001
|
+
if (videoCount === 0) {
|
|
2002
|
+
if (containsPdf(entryPath)) {
|
|
2003
|
+
pendingBooks.push({ entry, entryPath });
|
|
2004
|
+
} else if (isVerbose()) {
|
|
2005
|
+
spinner_default.info(`no media found, skipped: ${entry}`);
|
|
2006
|
+
}
|
|
2007
|
+
skipped++;
|
|
2008
|
+
continue;
|
|
2009
|
+
}
|
|
2010
|
+
if (videoCount >= 2) {
|
|
2011
|
+
pendingAnime.push({ entry, entryPath, videoCount });
|
|
2012
|
+
continue;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
1933
2015
|
const parsed = parseDownloadName(entry);
|
|
1934
2016
|
if (!parsed) {
|
|
1935
2017
|
if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
|
|
@@ -1953,6 +2035,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1953
2035
|
let resolvedYear = parsed.year;
|
|
1954
2036
|
if (config.tmdbApiKey) {
|
|
1955
2037
|
if (detectedType === "tv") {
|
|
2038
|
+
spinner_default.text = `TMDb: ${parsed.title}`;
|
|
1956
2039
|
const results = await searchTv(parsed.title, config.tmdbApiKey);
|
|
1957
2040
|
if (results.length === 1) {
|
|
1958
2041
|
tmdbId = results[0].id;
|
|
@@ -1997,18 +2080,18 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
1997
2080
|
const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
|
|
1998
2081
|
if (existingFolder) {
|
|
1999
2082
|
showFolderName = existingFolder;
|
|
2000
|
-
showPath = (0,
|
|
2083
|
+
showPath = (0, import_path15.resolve)(destRoot, existingFolder);
|
|
2001
2084
|
} else if (auto) {
|
|
2002
2085
|
showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
|
|
2003
|
-
showPath = (0,
|
|
2086
|
+
showPath = (0, import_path15.resolve)(destRoot, showFolderName);
|
|
2004
2087
|
if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
|
|
2005
2088
|
} else {
|
|
2006
2089
|
pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
|
|
2007
2090
|
continue;
|
|
2008
2091
|
}
|
|
2009
2092
|
}
|
|
2010
|
-
const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
2011
|
-
const seasonPath = (0,
|
|
2093
|
+
const seasonFolderName = parsed.season === 0 ? findSeasonFolder(showPath, 0, specialsFolder) ?? specialsFolder : findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
2094
|
+
const seasonPath = (0, import_path15.resolve)(showPath, seasonFolderName);
|
|
2012
2095
|
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
2013
2096
|
if (!videoFile) {
|
|
2014
2097
|
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
@@ -2016,13 +2099,15 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
2016
2099
|
continue;
|
|
2017
2100
|
}
|
|
2018
2101
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
2102
|
+
if (tmdbId && config.tmdbApiKey) spinner_default.text = `TMDb: episode name for ${resolvedTitle}`;
|
|
2019
2103
|
const tmdbEpisodeName = tmdbId && config.tmdbApiKey ? await getEpisodeName(tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
|
|
2020
2104
|
const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, resolvedTitle, tmdbEpisodeName ?? void 0);
|
|
2021
2105
|
const destVideoName = `${episodeName}.${videoExt}`;
|
|
2022
|
-
const destVideoPath = (0,
|
|
2023
|
-
const videoSourcePath = isDir ? (0,
|
|
2024
|
-
if ((0,
|
|
2025
|
-
|
|
2106
|
+
const destVideoPath = (0, import_path15.resolve)(seasonPath, destVideoName);
|
|
2107
|
+
const videoSourcePath = isDir ? (0, import_path15.resolve)(entryPath, videoFile) : entryPath;
|
|
2108
|
+
if ((0, import_fs15.existsSync)(destVideoPath)) {
|
|
2109
|
+
const isRepack = /\brepack\d*\b|\bproper\b/i.test(entry);
|
|
2110
|
+
let shouldReplace = force || isRepack;
|
|
2026
2111
|
if (!shouldReplace && interactive) {
|
|
2027
2112
|
spinner_default.stop();
|
|
2028
2113
|
const select = new import_termkit14.Select();
|
|
@@ -2039,39 +2124,39 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
2039
2124
|
continue;
|
|
2040
2125
|
}
|
|
2041
2126
|
if (!dryRun) {
|
|
2042
|
-
for (const f of (0,
|
|
2043
|
-
if (f.startsWith(`${episodeName}.`)) (
|
|
2127
|
+
for (const f of (0, import_fs15.readdirSync)(seasonPath)) {
|
|
2128
|
+
if (f.startsWith(`${episodeName}.`)) trashPath((0, import_path15.resolve)(seasonPath, f));
|
|
2044
2129
|
}
|
|
2045
2130
|
}
|
|
2046
2131
|
}
|
|
2047
|
-
const dirFiles = isDir ? (0,
|
|
2132
|
+
const dirFiles = isDir ? (0, import_fs15.readdirSync)(entryPath) : [];
|
|
2048
2133
|
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
2049
2134
|
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
2050
|
-
const subtitleSourcePath = subtitle ? (0,
|
|
2135
|
+
const subtitleSourcePath = subtitle ? (0, import_path15.resolve)(entryPath, subtitle) : null;
|
|
2051
2136
|
const destSubtitleName = subtitle && subtitleExt ? `${episodeName}.${subtitleExt}` : null;
|
|
2052
2137
|
if (!dryRun) {
|
|
2053
|
-
(0,
|
|
2138
|
+
(0, import_fs15.mkdirSync)(seasonPath, { recursive: true });
|
|
2054
2139
|
let mode = "move";
|
|
2055
2140
|
if (useHardlink) {
|
|
2056
2141
|
try {
|
|
2057
2142
|
if (!sameDev(videoSourcePath, seasonPath)) throw new Error("cross-filesystem");
|
|
2058
|
-
(0,
|
|
2143
|
+
(0, import_fs15.linkSync)(videoSourcePath, destVideoPath);
|
|
2059
2144
|
mode = "hardlink";
|
|
2060
2145
|
} catch {
|
|
2061
2146
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
|
|
2062
|
-
(0,
|
|
2147
|
+
(0, import_fs15.cpSync)(videoSourcePath, destVideoPath);
|
|
2063
2148
|
mode = "copy";
|
|
2064
2149
|
}
|
|
2065
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
2150
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs15.cpSync)(subtitleSourcePath, (0, import_path15.resolve)(seasonPath, destSubtitleName));
|
|
2066
2151
|
} else {
|
|
2067
2152
|
if (sameDev(videoSourcePath, seasonPath)) {
|
|
2068
|
-
(0,
|
|
2153
|
+
(0, import_fs15.renameSync)(videoSourcePath, destVideoPath);
|
|
2069
2154
|
} else {
|
|
2070
|
-
(0,
|
|
2071
|
-
(
|
|
2155
|
+
(0, import_fs15.cpSync)(videoSourcePath, destVideoPath);
|
|
2156
|
+
trashPath(videoSourcePath);
|
|
2072
2157
|
}
|
|
2073
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
2074
|
-
if (isDir) (
|
|
2158
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs15.renameSync)(subtitleSourcePath, (0, import_path15.resolve)(seasonPath, destSubtitleName));
|
|
2159
|
+
if (isDir) trashPath(entryPath, { recursive: true });
|
|
2075
2160
|
}
|
|
2076
2161
|
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId, "tv");
|
|
2077
2162
|
}
|
|
@@ -2120,6 +2205,15 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
2120
2205
|
for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
|
|
2121
2206
|
skipped += pendingTv.length;
|
|
2122
2207
|
}
|
|
2208
|
+
if (pendingBooks.length > 0) {
|
|
2209
|
+
spinner_default.warn(`${pendingBooks.length} uncertain book${pendingBooks.length > 1 ? "s" : ""} skipped \u2014 contains PDFs, review manually`);
|
|
2210
|
+
for (const p of pendingBooks) spinner_default.info(` ${typeGlyph("book")} ${p.entry}${typeTag("book")}`);
|
|
2211
|
+
}
|
|
2212
|
+
if (pendingAnime.length > 0) {
|
|
2213
|
+
spinner_default.warn(`${pendingAnime.length} uncertain anime/TV director${pendingAnime.length > 1 ? "ies" : "y"} skipped \u2014 multiple videos with no episode naming`);
|
|
2214
|
+
for (const p of pendingAnime) spinner_default.info(` ${typeGlyph("tv")} ${p.entry} (${p.videoCount} video${p.videoCount > 1 ? "s" : ""})${typeTag("tv")}`);
|
|
2215
|
+
skipped += pendingAnime.length;
|
|
2216
|
+
}
|
|
2123
2217
|
if (ignoreSet.size > 0) {
|
|
2124
2218
|
const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
|
|
2125
2219
|
if (stale.length > 0 && !dryRun) {
|
|
@@ -2136,7 +2230,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
|
|
|
2136
2230
|
var scan_default = scan;
|
|
2137
2231
|
|
|
2138
2232
|
// src/actions/shows.ts
|
|
2139
|
-
var
|
|
2233
|
+
var import_fs16 = require("fs");
|
|
2140
2234
|
var import_termkit15 = require("termkit");
|
|
2141
2235
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
2142
2236
|
var shows = async () => {
|
|
@@ -2153,7 +2247,7 @@ ${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termki
|
|
|
2153
2247
|
new import_termkit15.Table(
|
|
2154
2248
|
allShows.map((show) => ({
|
|
2155
2249
|
name: show.path.split("/").pop() ?? show.path,
|
|
2156
|
-
size: (0,
|
|
2250
|
+
size: (0, import_fs16.existsSync)(show.path) ? formatSize(dirSize(show.path)) : "\u2014",
|
|
2157
2251
|
tmdbId: show.tmdbId,
|
|
2158
2252
|
ended: show.ended
|
|
2159
2253
|
})),
|
|
@@ -2186,15 +2280,15 @@ ${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termki
|
|
|
2186
2280
|
var shows_default = shows;
|
|
2187
2281
|
|
|
2188
2282
|
// src/actions/stats.ts
|
|
2189
|
-
var
|
|
2190
|
-
var
|
|
2283
|
+
var import_fs17 = require("fs");
|
|
2284
|
+
var import_path16 = require("path");
|
|
2191
2285
|
var import_termkit16 = require("termkit");
|
|
2192
|
-
var
|
|
2286
|
+
var countVideos2 = (dir) => {
|
|
2193
2287
|
let count = 0;
|
|
2194
2288
|
try {
|
|
2195
|
-
for (const entry of (0,
|
|
2289
|
+
for (const entry of (0, import_fs17.readdirSync)(dir, { withFileTypes: true })) {
|
|
2196
2290
|
if (entry.isDirectory()) {
|
|
2197
|
-
count +=
|
|
2291
|
+
count += countVideos2((0, import_path16.resolve)(dir, entry.name));
|
|
2198
2292
|
} else {
|
|
2199
2293
|
const ext = entry.name.match(/([^.]+$)/)?.[0]?.toLowerCase();
|
|
2200
2294
|
if (ext && videoExtensions_default.includes(ext)) count++;
|
|
@@ -2206,9 +2300,9 @@ var countVideos = (dir) => {
|
|
|
2206
2300
|
};
|
|
2207
2301
|
var countDirs = (dir) => {
|
|
2208
2302
|
try {
|
|
2209
|
-
return (0,
|
|
2303
|
+
return (0, import_fs17.readdirSync)(dir).filter((f) => {
|
|
2210
2304
|
try {
|
|
2211
|
-
return (0,
|
|
2305
|
+
return (0, import_fs17.lstatSync)((0, import_path16.resolve)(dir, f)).isDirectory();
|
|
2212
2306
|
} catch {
|
|
2213
2307
|
return false;
|
|
2214
2308
|
}
|
|
@@ -2222,16 +2316,16 @@ var stats = async () => {
|
|
|
2222
2316
|
const shows2 = getShows();
|
|
2223
2317
|
const rows = [];
|
|
2224
2318
|
const movieDest = config.dest.movie;
|
|
2225
|
-
if (movieDest && (0,
|
|
2319
|
+
if (movieDest && (0, import_fs17.existsSync)(movieDest)) {
|
|
2226
2320
|
rows.push({ category: "Movies", count: countDirs(movieDest), size: formatSize(dirSize(movieDest)) });
|
|
2227
2321
|
}
|
|
2228
2322
|
const tvDest = config.dest.tv;
|
|
2229
|
-
if (tvDest && (0,
|
|
2323
|
+
if (tvDest && (0, import_fs17.existsSync)(tvDest)) {
|
|
2230
2324
|
rows.push({ category: "Shows", count: shows2.length, size: formatSize(dirSize(tvDest)) });
|
|
2231
|
-
rows.push({ category: "Episodes", count:
|
|
2325
|
+
rows.push({ category: "Episodes", count: countVideos2(tvDest) });
|
|
2232
2326
|
}
|
|
2233
2327
|
const ps3Dest = config.dest.ps3;
|
|
2234
|
-
if (ps3Dest && (0,
|
|
2328
|
+
if (ps3Dest && (0, import_fs17.existsSync)(ps3Dest)) {
|
|
2235
2329
|
rows.push({ category: "PS3", count: countDirs(ps3Dest), size: formatSize(dirSize(ps3Dest)) });
|
|
2236
2330
|
}
|
|
2237
2331
|
if (rows.length === 0) return;
|
|
@@ -2250,7 +2344,7 @@ var stats = async () => {
|
|
|
2250
2344
|
var stats_default = stats;
|
|
2251
2345
|
|
|
2252
2346
|
// src/actions/undo.ts
|
|
2253
|
-
var
|
|
2347
|
+
var import_fs18 = require("fs");
|
|
2254
2348
|
var import_termkit17 = require("termkit");
|
|
2255
2349
|
var undo = async () => {
|
|
2256
2350
|
spinner_default.start();
|
|
@@ -2265,7 +2359,7 @@ var undo = async () => {
|
|
|
2265
2359
|
if (!useImports) {
|
|
2266
2360
|
let undone2 = 0;
|
|
2267
2361
|
for (const record of renameRecords) {
|
|
2268
|
-
(0,
|
|
2362
|
+
(0, import_fs18.renameSync)(record.newPath, record.oldPath);
|
|
2269
2363
|
spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.newPath)} \u2192 ${import_termkit17.Color.white.encoder(record.oldPath)}`);
|
|
2270
2364
|
undone2++;
|
|
2271
2365
|
}
|
|
@@ -2287,12 +2381,12 @@ var undo = async () => {
|
|
|
2287
2381
|
skipped++;
|
|
2288
2382
|
continue;
|
|
2289
2383
|
}
|
|
2290
|
-
if (!(0,
|
|
2384
|
+
if (!(0, import_fs18.existsSync)(record.destinationPath)) {
|
|
2291
2385
|
spinner_default.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
|
|
2292
2386
|
skipped++;
|
|
2293
2387
|
continue;
|
|
2294
2388
|
}
|
|
2295
|
-
(0,
|
|
2389
|
+
(0, import_fs18.renameSync)(record.destinationPath, record.sourcePath);
|
|
2296
2390
|
spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.destinationPath)} \u2192 ${import_termkit17.Color.white.encoder(record.sourcePath)}`);
|
|
2297
2391
|
undone++;
|
|
2298
2392
|
}
|
|
@@ -2305,37 +2399,37 @@ var undo_default = undo;
|
|
|
2305
2399
|
|
|
2306
2400
|
// src/actions/watch.ts
|
|
2307
2401
|
var import_chokidar = __toESM(require("chokidar"));
|
|
2308
|
-
var
|
|
2309
|
-
var
|
|
2402
|
+
var import_fs19 = require("fs");
|
|
2403
|
+
var import_path17 = require("path");
|
|
2310
2404
|
var import_termkit18 = require("termkit");
|
|
2311
2405
|
var sameDev2 = (a, b) => {
|
|
2312
2406
|
try {
|
|
2313
2407
|
let bExisting = b;
|
|
2314
|
-
while (!(0,
|
|
2315
|
-
return (0,
|
|
2408
|
+
while (!(0, import_fs19.existsSync)(bExisting)) bExisting = (0, import_path17.dirname)(bExisting);
|
|
2409
|
+
return (0, import_fs19.statSync)(a).dev === (0, import_fs19.statSync)(bExisting).dev;
|
|
2316
2410
|
} catch {
|
|
2317
2411
|
return false;
|
|
2318
2412
|
}
|
|
2319
2413
|
};
|
|
2320
2414
|
var moveItem = (src, dest) => {
|
|
2321
2415
|
if (sameDev2(src, dest)) {
|
|
2322
|
-
(0,
|
|
2416
|
+
(0, import_fs19.renameSync)(src, dest);
|
|
2323
2417
|
} else {
|
|
2324
|
-
(0,
|
|
2325
|
-
(0,
|
|
2418
|
+
(0, import_fs19.cpSync)(src, dest, { recursive: true });
|
|
2419
|
+
(0, import_fs19.rmSync)(src, { recursive: true, force: true });
|
|
2326
2420
|
}
|
|
2327
2421
|
};
|
|
2328
|
-
var findVideo2 = (dir) => (0,
|
|
2422
|
+
var findVideo2 = (dir) => (0, import_fs19.readdirSync)(dir).find((f) => {
|
|
2329
2423
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
2330
2424
|
return ext && videoExtensions_default.includes(ext);
|
|
2331
2425
|
}) ?? null;
|
|
2332
|
-
var containsBook2 = (dir, depth = 2) => (0,
|
|
2426
|
+
var containsBook2 = (dir, depth = 2) => (0, import_fs19.readdirSync)(dir).some((f) => {
|
|
2333
2427
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
2334
2428
|
if (ext && bookExtensions_default.includes(ext)) return true;
|
|
2335
2429
|
if (depth > 1) {
|
|
2336
2430
|
try {
|
|
2337
|
-
const sub = (0,
|
|
2338
|
-
if ((0,
|
|
2431
|
+
const sub = (0, import_path17.resolve)(dir, f);
|
|
2432
|
+
if ((0, import_fs19.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
|
|
2339
2433
|
} catch {
|
|
2340
2434
|
}
|
|
2341
2435
|
}
|
|
@@ -2346,26 +2440,26 @@ var isSeasonDirName2 = (name) => !isTvEpisodeName2(name) && /(?:^|[.\s_-])(?:sea
|
|
|
2346
2440
|
var expandWatchPath = (p) => {
|
|
2347
2441
|
let isDir;
|
|
2348
2442
|
try {
|
|
2349
|
-
isDir = (0,
|
|
2443
|
+
isDir = (0, import_fs19.lstatSync)(p).isDirectory();
|
|
2350
2444
|
} catch {
|
|
2351
2445
|
return [p];
|
|
2352
2446
|
}
|
|
2353
2447
|
if (!isDir) return [p];
|
|
2354
|
-
const name = (0,
|
|
2448
|
+
const name = (0, import_path17.basename)(p);
|
|
2355
2449
|
if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
|
|
2356
2450
|
let children;
|
|
2357
2451
|
try {
|
|
2358
|
-
children = (0,
|
|
2452
|
+
children = (0, import_fs19.readdirSync)(p);
|
|
2359
2453
|
} catch {
|
|
2360
2454
|
return [p];
|
|
2361
2455
|
}
|
|
2362
2456
|
if (children.some((c) => isTvEpisodeName2(c))) {
|
|
2363
2457
|
const entries = [];
|
|
2364
2458
|
for (const child of children) {
|
|
2365
|
-
const cp = (0,
|
|
2459
|
+
const cp = (0, import_path17.resolve)(p, child);
|
|
2366
2460
|
let cd;
|
|
2367
2461
|
try {
|
|
2368
|
-
cd = (0,
|
|
2462
|
+
cd = (0, import_fs19.lstatSync)(cp).isDirectory();
|
|
2369
2463
|
} catch {
|
|
2370
2464
|
continue;
|
|
2371
2465
|
}
|
|
@@ -2377,7 +2471,7 @@ var expandWatchPath = (p) => {
|
|
|
2377
2471
|
}
|
|
2378
2472
|
const seasonDirs = children.filter((c) => {
|
|
2379
2473
|
try {
|
|
2380
|
-
return isSeasonDirName2(c) && (0,
|
|
2474
|
+
return isSeasonDirName2(c) && (0, import_fs19.lstatSync)((0, import_path17.resolve)(p, c)).isDirectory();
|
|
2381
2475
|
} catch {
|
|
2382
2476
|
return false;
|
|
2383
2477
|
}
|
|
@@ -2385,18 +2479,18 @@ var expandWatchPath = (p) => {
|
|
|
2385
2479
|
if (seasonDirs.length > 0) {
|
|
2386
2480
|
const entries = [];
|
|
2387
2481
|
for (const sd of seasonDirs) {
|
|
2388
|
-
const sp = (0,
|
|
2482
|
+
const sp = (0, import_path17.resolve)(p, sd);
|
|
2389
2483
|
let sc;
|
|
2390
2484
|
try {
|
|
2391
|
-
sc = (0,
|
|
2485
|
+
sc = (0, import_fs19.readdirSync)(sp);
|
|
2392
2486
|
} catch {
|
|
2393
2487
|
continue;
|
|
2394
2488
|
}
|
|
2395
2489
|
for (const child of sc) {
|
|
2396
|
-
const cp = (0,
|
|
2490
|
+
const cp = (0, import_path17.resolve)(sp, child);
|
|
2397
2491
|
let cd;
|
|
2398
2492
|
try {
|
|
2399
|
-
cd = (0,
|
|
2493
|
+
cd = (0, import_fs19.lstatSync)(cp).isDirectory();
|
|
2400
2494
|
} catch {
|
|
2401
2495
|
continue;
|
|
2402
2496
|
}
|
|
@@ -2410,10 +2504,10 @@ var expandWatchPath = (p) => {
|
|
|
2410
2504
|
return [p];
|
|
2411
2505
|
};
|
|
2412
2506
|
var findSeasonFolder2 = (showPath, season) => {
|
|
2413
|
-
if (!(0,
|
|
2414
|
-
const folders = (0,
|
|
2507
|
+
if (!(0, import_fs19.existsSync)(showPath)) return null;
|
|
2508
|
+
const folders = (0, import_fs19.readdirSync)(showPath).filter((f) => {
|
|
2415
2509
|
try {
|
|
2416
|
-
return (0,
|
|
2510
|
+
return (0, import_fs19.lstatSync)((0, import_path17.resolve)(showPath, f)).isDirectory();
|
|
2417
2511
|
} catch {
|
|
2418
2512
|
return false;
|
|
2419
2513
|
}
|
|
@@ -2426,10 +2520,10 @@ var findSeasonFolder2 = (showPath, season) => {
|
|
|
2426
2520
|
var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
2427
2521
|
const config = getConfig();
|
|
2428
2522
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
2429
|
-
const entry = (0,
|
|
2523
|
+
const entry = (0, import_path17.basename)(entryPath);
|
|
2430
2524
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
2431
2525
|
const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
|
|
2432
|
-
const isDir = (0,
|
|
2526
|
+
const isDir = (0, import_fs19.lstatSync)(entryPath).isDirectory();
|
|
2433
2527
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
2434
2528
|
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
2435
2529
|
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
@@ -2455,8 +2549,8 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2455
2549
|
const id = entry.split("-")[0];
|
|
2456
2550
|
if (!nameMatch || !id) return;
|
|
2457
2551
|
const destName = `${nameMatch[0]} [${id}]`;
|
|
2458
|
-
const destPath = (0,
|
|
2459
|
-
if ((0,
|
|
2552
|
+
const destPath = (0, import_path17.resolve)(destRoot, destName);
|
|
2553
|
+
if ((0, import_fs19.existsSync)(destPath)) {
|
|
2460
2554
|
spinner_default.warn(`already exists: ${destName}`);
|
|
2461
2555
|
return;
|
|
2462
2556
|
}
|
|
@@ -2466,20 +2560,20 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2466
2560
|
return;
|
|
2467
2561
|
}
|
|
2468
2562
|
if (detectedType === "book") {
|
|
2469
|
-
const destPath = (0,
|
|
2470
|
-
if ((0,
|
|
2563
|
+
const destPath = (0, import_path17.resolve)(destRoot, entry);
|
|
2564
|
+
if ((0, import_fs19.existsSync)(destPath)) {
|
|
2471
2565
|
spinner_default.warn(`already exists: ${entry}`);
|
|
2472
2566
|
return;
|
|
2473
2567
|
}
|
|
2474
2568
|
if (isDir || isBookDir) {
|
|
2475
2569
|
moveItem(entryPath, destPath);
|
|
2476
2570
|
} else {
|
|
2477
|
-
(0,
|
|
2571
|
+
(0, import_fs19.mkdirSync)(destRoot, { recursive: true });
|
|
2478
2572
|
if (sameDev2(entryPath, destRoot)) {
|
|
2479
|
-
(0,
|
|
2573
|
+
(0, import_fs19.renameSync)(entryPath, destPath);
|
|
2480
2574
|
} else {
|
|
2481
|
-
(0,
|
|
2482
|
-
(0,
|
|
2575
|
+
(0, import_fs19.cpSync)(entryPath, destPath);
|
|
2576
|
+
(0, import_fs19.rmSync)(entryPath);
|
|
2483
2577
|
}
|
|
2484
2578
|
}
|
|
2485
2579
|
recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
|
|
@@ -2504,14 +2598,14 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2504
2598
|
showFolderName = showPath.split("/").pop() ?? registeredShow.path;
|
|
2505
2599
|
} else if (auto) {
|
|
2506
2600
|
showFolderName = formatMovieName(movieFormat, parsed.title, parsed.year);
|
|
2507
|
-
showPath = (0,
|
|
2601
|
+
showPath = (0, import_path17.resolve)(destRoot, showFolderName);
|
|
2508
2602
|
upsertShow(showPath, null, parsed.title);
|
|
2509
2603
|
} else {
|
|
2510
2604
|
if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
|
|
2511
2605
|
return;
|
|
2512
2606
|
}
|
|
2513
2607
|
const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
2514
|
-
const seasonPath = (0,
|
|
2608
|
+
const seasonPath = (0, import_path17.resolve)(showPath, seasonFolderName);
|
|
2515
2609
|
const videoFile2 = isDir ? findVideo2(entryPath) : entry;
|
|
2516
2610
|
if (!videoFile2) {
|
|
2517
2611
|
if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
|
|
@@ -2521,39 +2615,39 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2521
2615
|
const tmdbEpisodeName = registeredShow?.tmdbId && config.tmdbApiKey ? await getEpisodeName(registeredShow.tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
|
|
2522
2616
|
const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, parsed.title, tmdbEpisodeName ?? void 0);
|
|
2523
2617
|
const destVideoName2 = `${episodeName}.${videoExt2}`;
|
|
2524
|
-
const destVideoPath = (0,
|
|
2525
|
-
const videoSourcePath2 = isDir ? (0,
|
|
2526
|
-
if ((0,
|
|
2618
|
+
const destVideoPath = (0, import_path17.resolve)(seasonPath, destVideoName2);
|
|
2619
|
+
const videoSourcePath2 = isDir ? (0, import_path17.resolve)(entryPath, videoFile2) : entryPath;
|
|
2620
|
+
if ((0, import_fs19.existsSync)(destVideoPath)) {
|
|
2527
2621
|
spinner_default.warn(`already exists: ${episodeName}`);
|
|
2528
2622
|
return;
|
|
2529
2623
|
}
|
|
2530
|
-
const dirFiles2 = isDir ? (0,
|
|
2624
|
+
const dirFiles2 = isDir ? (0, import_fs19.readdirSync)(entryPath) : [];
|
|
2531
2625
|
const subtitle2 = isDir ? findSubtitle(dirFiles2, language) : null;
|
|
2532
2626
|
const subtitleExt2 = subtitle2?.match(/([^.]+$)/)?.[0];
|
|
2533
|
-
const subtitleSourcePath2 = subtitle2 ? (0,
|
|
2627
|
+
const subtitleSourcePath2 = subtitle2 ? (0, import_path17.resolve)(entryPath, subtitle2) : null;
|
|
2534
2628
|
const destSubtitleName2 = subtitle2 && subtitleExt2 ? `${episodeName}.${subtitleExt2}` : null;
|
|
2535
|
-
(0,
|
|
2629
|
+
(0, import_fs19.mkdirSync)(seasonPath, { recursive: true });
|
|
2536
2630
|
let mode = "move";
|
|
2537
2631
|
if (useHardlink) {
|
|
2538
2632
|
try {
|
|
2539
2633
|
if (!sameDev2(videoSourcePath2, seasonPath)) throw new Error("cross-filesystem");
|
|
2540
|
-
(0,
|
|
2634
|
+
(0, import_fs19.linkSync)(videoSourcePath2, destVideoPath);
|
|
2541
2635
|
mode = "hardlink";
|
|
2542
2636
|
} catch {
|
|
2543
2637
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
|
|
2544
|
-
(0,
|
|
2638
|
+
(0, import_fs19.cpSync)(videoSourcePath2, destVideoPath);
|
|
2545
2639
|
mode = "copy";
|
|
2546
2640
|
}
|
|
2547
|
-
if (subtitleSourcePath2 && destSubtitleName2) (0,
|
|
2641
|
+
if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs19.cpSync)(subtitleSourcePath2, (0, import_path17.resolve)(seasonPath, destSubtitleName2));
|
|
2548
2642
|
} else {
|
|
2549
2643
|
if (sameDev2(videoSourcePath2, seasonPath)) {
|
|
2550
|
-
(0,
|
|
2644
|
+
(0, import_fs19.renameSync)(videoSourcePath2, destVideoPath);
|
|
2551
2645
|
} else {
|
|
2552
|
-
(0,
|
|
2553
|
-
(0,
|
|
2646
|
+
(0, import_fs19.cpSync)(videoSourcePath2, destVideoPath);
|
|
2647
|
+
(0, import_fs19.rmSync)(videoSourcePath2);
|
|
2554
2648
|
}
|
|
2555
|
-
if (subtitleSourcePath2 && destSubtitleName2) (0,
|
|
2556
|
-
if (isDir) (0,
|
|
2649
|
+
if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs19.renameSync)(subtitleSourcePath2, (0, import_path17.resolve)(seasonPath, destSubtitleName2));
|
|
2650
|
+
if (isDir) (0, import_fs19.rmSync)(entryPath, { recursive: true, force: true });
|
|
2557
2651
|
}
|
|
2558
2652
|
recordImport(sessionId, entryPath, seasonPath, mode, void 0, "tv");
|
|
2559
2653
|
spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
|
|
@@ -2561,8 +2655,8 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2561
2655
|
}
|
|
2562
2656
|
const edition = detectEdition(entry);
|
|
2563
2657
|
const folderName = formatMovieName(movieFormat, parsed.title, parsed.year, edition);
|
|
2564
|
-
const destFolder = (0,
|
|
2565
|
-
if ((0,
|
|
2658
|
+
const destFolder = (0, import_path17.resolve)(destRoot, folderName);
|
|
2659
|
+
if ((0, import_fs19.existsSync)(destFolder)) {
|
|
2566
2660
|
spinner_default.warn(`already exists: ${folderName}`);
|
|
2567
2661
|
return;
|
|
2568
2662
|
}
|
|
@@ -2573,42 +2667,42 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
|
|
|
2573
2667
|
}
|
|
2574
2668
|
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
2575
2669
|
const destVideoName = `${folderName}.${videoExt}`;
|
|
2576
|
-
const videoSourcePath = isDir ? (0,
|
|
2577
|
-
const dirFiles = isDir ? (0,
|
|
2670
|
+
const videoSourcePath = isDir ? (0, import_path17.resolve)(entryPath, videoFile) : entryPath;
|
|
2671
|
+
const dirFiles = isDir ? (0, import_fs19.readdirSync)(entryPath) : [];
|
|
2578
2672
|
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
2579
2673
|
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
2580
|
-
const subtitleSourcePath = subtitle ? (0,
|
|
2674
|
+
const subtitleSourcePath = subtitle ? (0, import_path17.resolve)(entryPath, subtitle) : null;
|
|
2581
2675
|
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
2582
2676
|
if (useHardlink) {
|
|
2583
|
-
(0,
|
|
2584
|
-
const destVideoPath = (0,
|
|
2677
|
+
(0, import_fs19.mkdirSync)(destFolder, { recursive: true });
|
|
2678
|
+
const destVideoPath = (0, import_path17.resolve)(destFolder, destVideoName);
|
|
2585
2679
|
let mode;
|
|
2586
2680
|
try {
|
|
2587
2681
|
if (!sameDev2(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
|
|
2588
|
-
(0,
|
|
2682
|
+
(0, import_fs19.linkSync)(videoSourcePath, destVideoPath);
|
|
2589
2683
|
mode = "hardlink";
|
|
2590
2684
|
} catch {
|
|
2591
2685
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
|
|
2592
|
-
(0,
|
|
2686
|
+
(0, import_fs19.cpSync)(videoSourcePath, destVideoPath);
|
|
2593
2687
|
mode = "copy";
|
|
2594
2688
|
}
|
|
2595
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
2689
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs19.cpSync)(subtitleSourcePath, (0, import_path17.resolve)(destFolder, destSubtitleName));
|
|
2596
2690
|
recordImport(sessionId, entryPath, destFolder, mode, void 0, "movie");
|
|
2597
2691
|
} else {
|
|
2598
2692
|
if (isDir) {
|
|
2599
2693
|
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
2600
|
-
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0,
|
|
2601
|
-
(0,
|
|
2602
|
-
if (subtitleSourcePath && destSubtitleName) (0,
|
|
2694
|
+
for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs19.rmSync)((0, import_path17.resolve)(entryPath, f), { recursive: true, force: true });
|
|
2695
|
+
(0, import_fs19.renameSync)(videoSourcePath, (0, import_path17.resolve)(entryPath, destVideoName));
|
|
2696
|
+
if (subtitleSourcePath && destSubtitleName) (0, import_fs19.renameSync)(subtitleSourcePath, (0, import_path17.resolve)(entryPath, destSubtitleName));
|
|
2603
2697
|
moveItem(entryPath, destFolder);
|
|
2604
2698
|
} else {
|
|
2605
|
-
(0,
|
|
2606
|
-
const destVideoPath = (0,
|
|
2699
|
+
(0, import_fs19.mkdirSync)(destFolder, { recursive: true });
|
|
2700
|
+
const destVideoPath = (0, import_path17.resolve)(destFolder, destVideoName);
|
|
2607
2701
|
if (sameDev2(videoSourcePath, destRoot)) {
|
|
2608
|
-
(0,
|
|
2702
|
+
(0, import_fs19.renameSync)(videoSourcePath, destVideoPath);
|
|
2609
2703
|
} else {
|
|
2610
|
-
(0,
|
|
2611
|
-
(0,
|
|
2704
|
+
(0, import_fs19.cpSync)(videoSourcePath, destVideoPath);
|
|
2705
|
+
(0, import_fs19.rmSync)(videoSourcePath);
|
|
2612
2706
|
}
|
|
2613
2707
|
}
|
|
2614
2708
|
recordImport(sessionId, entryPath, destFolder, "move", void 0, "movie");
|
|
@@ -2653,7 +2747,7 @@ var watch = async ({ hardlink = false, auto = false }) => {
|
|
|
2653
2747
|
var watch_default = watch;
|
|
2654
2748
|
|
|
2655
2749
|
// package.json
|
|
2656
|
-
var version = "0.2.
|
|
2750
|
+
var version = "0.2.7";
|
|
2657
2751
|
|
|
2658
2752
|
// src/program.ts
|
|
2659
2753
|
var toCamel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|