reelsort 0.2.5 → 0.2.6

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 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 import_fs14 = require("fs");
1500
- var import_path14 = require("path");
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 = (name) => {
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,31 +1604,31 @@ var bookExtensions_default = ["epub", "mobi", "azw3", "azw"];
1565
1604
  var sameDev = (a, b) => {
1566
1605
  try {
1567
1606
  let bExisting = b;
1568
- while (!(0, import_fs14.existsSync)(bExisting)) bExisting = (0, import_path14.dirname)(bExisting);
1569
- return (0, import_fs14.statSync)(a).dev === (0, import_fs14.statSync)(bExisting).dev;
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, import_fs14.renameSync)(src, dest);
1615
+ (0, import_fs15.renameSync)(src, dest);
1577
1616
  } else {
1578
- (0, import_fs14.cpSync)(src, dest, { recursive: true });
1579
- (0, import_fs14.rmSync)(src, { recursive: true, force: true });
1617
+ (0, import_fs15.cpSync)(src, dest, { recursive: true });
1618
+ trashPath(src, { recursive: true });
1580
1619
  }
1581
1620
  };
1582
- var findVideo = (dir) => (0, import_fs14.readdirSync)(dir).find((f) => {
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, import_fs14.readdirSync)(dir).some((f) => {
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, import_path14.resolve)(dir, f);
1592
- if ((0, import_fs14.lstatSync)(sub).isDirectory()) return containsBook(sub, depth - 1);
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
  }
@@ -1599,11 +1638,11 @@ var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i
1599
1638
  var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
1600
1639
  var gatherEntries = (source) => {
1601
1640
  const result = [];
1602
- for (const name of (0, import_fs14.readdirSync)(source)) {
1603
- const fullPath = (0, import_path14.resolve)(source, name);
1641
+ for (const name of (0, import_fs15.readdirSync)(source)) {
1642
+ const fullPath = (0, import_path15.resolve)(source, name);
1604
1643
  let isDir;
1605
1644
  try {
1606
- isDir = (0, import_fs14.lstatSync)(fullPath).isDirectory();
1645
+ isDir = (0, import_fs15.lstatSync)(fullPath).isDirectory();
1607
1646
  } catch {
1608
1647
  continue;
1609
1648
  }
@@ -1621,17 +1660,17 @@ var gatherEntries = (source) => {
1621
1660
  }
1622
1661
  let children;
1623
1662
  try {
1624
- children = (0, import_fs14.readdirSync)(fullPath);
1663
+ children = (0, import_fs15.readdirSync)(fullPath);
1625
1664
  } catch {
1626
1665
  result.push({ entry: name, entryPath: fullPath, isDir: true });
1627
1666
  continue;
1628
1667
  }
1629
1668
  if (children.some((c) => isTvEpisodeName(c))) {
1630
1669
  for (const child of children) {
1631
- const childPath = (0, import_path14.resolve)(fullPath, child);
1670
+ const childPath = (0, import_path15.resolve)(fullPath, child);
1632
1671
  let childIsDir;
1633
1672
  try {
1634
- childIsDir = (0, import_fs14.lstatSync)(childPath).isDirectory();
1673
+ childIsDir = (0, import_fs15.lstatSync)(childPath).isDirectory();
1635
1674
  } catch {
1636
1675
  continue;
1637
1676
  }
@@ -1643,25 +1682,25 @@ var gatherEntries = (source) => {
1643
1682
  }
1644
1683
  const seasonDirs = children.filter((c) => {
1645
1684
  try {
1646
- return isSeasonDirName(c) && (0, import_fs14.lstatSync)((0, import_path14.resolve)(fullPath, c)).isDirectory();
1685
+ return isSeasonDirName(c) && (0, import_fs15.lstatSync)((0, import_path15.resolve)(fullPath, c)).isDirectory();
1647
1686
  } catch {
1648
1687
  return false;
1649
1688
  }
1650
1689
  });
1651
1690
  if (seasonDirs.length > 0) {
1652
1691
  for (const seasonDir of seasonDirs) {
1653
- const seasonPath = (0, import_path14.resolve)(fullPath, seasonDir);
1692
+ const seasonPath = (0, import_path15.resolve)(fullPath, seasonDir);
1654
1693
  let seasonChildren;
1655
1694
  try {
1656
- seasonChildren = (0, import_fs14.readdirSync)(seasonPath);
1695
+ seasonChildren = (0, import_fs15.readdirSync)(seasonPath);
1657
1696
  } catch {
1658
1697
  continue;
1659
1698
  }
1660
1699
  for (const child of seasonChildren) {
1661
- const childPath = (0, import_path14.resolve)(seasonPath, child);
1700
+ const childPath = (0, import_path15.resolve)(seasonPath, child);
1662
1701
  let childIsDir;
1663
1702
  try {
1664
- childIsDir = (0, import_fs14.lstatSync)(childPath).isDirectory();
1703
+ childIsDir = (0, import_fs15.lstatSync)(childPath).isDirectory();
1665
1704
  } catch {
1666
1705
  continue;
1667
1706
  }
@@ -1677,19 +1716,19 @@ var gatherEntries = (source) => {
1677
1716
  return result;
1678
1717
  };
1679
1718
  var findShowFolder = (destRoot, title) => {
1680
- if (!(0, import_fs14.existsSync)(destRoot)) return null;
1719
+ if (!(0, import_fs15.existsSync)(destRoot)) return null;
1681
1720
  const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1682
1721
  const target = normalize(title);
1683
- return (0, import_fs14.readdirSync)(destRoot).filter((f) => {
1722
+ return (0, import_fs15.readdirSync)(destRoot).filter((f) => {
1684
1723
  try {
1685
- return (0, import_fs14.lstatSync)((0, import_path14.resolve)(destRoot, f)).isDirectory();
1724
+ return (0, import_fs15.lstatSync)((0, import_path15.resolve)(destRoot, f)).isDirectory();
1686
1725
  } catch {
1687
1726
  return false;
1688
1727
  }
1689
1728
  }).find((f) => normalize(f) === target) ?? null;
1690
1729
  };
1691
1730
  var findShowFolderByContent = (destRoot, title) => {
1692
- if (!(0, import_fs14.existsSync)(destRoot)) return null;
1731
+ if (!(0, import_fs15.existsSync)(destRoot)) return null;
1693
1732
  const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1694
1733
  const target = normalize(title);
1695
1734
  const matchesTitle = (name) => {
@@ -1697,18 +1736,18 @@ var findShowFolderByContent = (destRoot, title) => {
1697
1736
  const p = parseDownloadName(name);
1698
1737
  return !!p && normalize(p.title) === target;
1699
1738
  };
1700
- for (const folder of (0, import_fs14.readdirSync)(destRoot)) {
1739
+ for (const folder of (0, import_fs15.readdirSync)(destRoot)) {
1701
1740
  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);
1741
+ const folderPath = (0, import_path15.resolve)(destRoot, folder);
1742
+ if (!(0, import_fs15.lstatSync)(folderPath).isDirectory()) continue;
1743
+ const children = (0, import_fs15.readdirSync)(folderPath);
1705
1744
  if (children.some(matchesTitle)) return folder;
1706
1745
  for (const child of children) {
1707
1746
  if (!isSeasonDirName(child)) continue;
1708
1747
  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;
1748
+ const seasonPath = (0, import_path15.resolve)(folderPath, child);
1749
+ if (!(0, import_fs15.lstatSync)(seasonPath).isDirectory()) continue;
1750
+ if ((0, import_fs15.readdirSync)(seasonPath).some(matchesTitle)) return folder;
1712
1751
  } catch {
1713
1752
  }
1714
1753
  }
@@ -1717,15 +1756,19 @@ var findShowFolderByContent = (destRoot, title) => {
1717
1756
  }
1718
1757
  return null;
1719
1758
  };
1720
- var findSeasonFolder = (showPath, season) => {
1721
- if (!(0, import_fs14.existsSync)(showPath)) return null;
1722
- const folders = (0, import_fs14.readdirSync)(showPath).filter((f) => {
1759
+ var findSeasonFolder = (showPath, season, specialsFolder) => {
1760
+ if (!(0, import_fs15.existsSync)(showPath)) return null;
1761
+ const folders = (0, import_fs15.readdirSync)(showPath).filter((f) => {
1723
1762
  try {
1724
- return (0, import_fs14.lstatSync)((0, import_path14.resolve)(showPath, f)).isDirectory();
1763
+ return (0, import_fs15.lstatSync)((0, import_path15.resolve)(showPath, f)).isDirectory();
1725
1764
  } catch {
1726
1765
  return false;
1727
1766
  }
1728
1767
  });
1768
+ if (season === 0 && specialsFolder) {
1769
+ const existing = folders.find((f) => f.toLowerCase() === specialsFolder.toLowerCase());
1770
+ if (existing) return existing;
1771
+ }
1729
1772
  return folders.find((f) => {
1730
1773
  const match = f.match(/(?:season|s)\s*0*(\d+)/i);
1731
1774
  return match && parseInt(match[1]) === season;
@@ -1754,11 +1797,13 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1754
1797
  const language = config.language ?? "eng";
1755
1798
  const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
1756
1799
  const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
1800
+ const specialsFolder = config.specialsFolder ?? "Specials";
1757
1801
  const lookupMovie = async (parsed) => {
1758
1802
  let tmdbId;
1759
1803
  let resolvedTitle = parsed.title;
1760
1804
  let resolvedYear = parsed.year;
1761
1805
  if (config.tmdbApiKey) {
1806
+ spinner_default.text = `TMDb: ${parsed.title}`;
1762
1807
  const results = await searchMovie(parsed.title, parsed.year, config.tmdbApiKey);
1763
1808
  if (results.length === 1) {
1764
1809
  tmdbId = results[0].id;
@@ -1786,8 +1831,8 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1786
1831
  const importMovie = async (entry, entryPath, isDir, resolvedTitle, resolvedYear, tmdbId, destRoot) => {
1787
1832
  const edition = detectEdition(entry);
1788
1833
  const folderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear, edition);
1789
- const destFolder = (0, import_path14.resolve)(destRoot, folderName);
1790
- if ((0, import_fs14.existsSync)(destFolder)) {
1834
+ const destFolder = (0, import_path15.resolve)(destRoot, folderName);
1835
+ if ((0, import_fs15.existsSync)(destFolder)) {
1791
1836
  spinner_default.warn(`already exists: ${folderName}`);
1792
1837
  return false;
1793
1838
  }
@@ -1798,43 +1843,43 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1798
1843
  }
1799
1844
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
1800
1845
  const destVideoName = `${folderName}.${videoExt}`;
1801
- const videoSourcePath = isDir ? (0, import_path14.resolve)(entryPath, videoFile) : entryPath;
1802
- const dirFiles = isDir ? (0, import_fs14.readdirSync)(entryPath) : [];
1846
+ const videoSourcePath = isDir ? (0, import_path15.resolve)(entryPath, videoFile) : entryPath;
1847
+ const dirFiles = isDir ? (0, import_fs15.readdirSync)(entryPath) : [];
1803
1848
  const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
1804
1849
  const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
1805
- const subtitleSourcePath = subtitle ? (0, import_path14.resolve)(entryPath, subtitle) : null;
1850
+ const subtitleSourcePath = subtitle ? (0, import_path15.resolve)(entryPath, subtitle) : null;
1806
1851
  const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
1807
1852
  if (!dryRun) {
1808
1853
  if (useHardlink) {
1809
- (0, import_fs14.mkdirSync)(destFolder, { recursive: true });
1810
- const destVideoPath = (0, import_path14.resolve)(destFolder, destVideoName);
1854
+ (0, import_fs15.mkdirSync)(destFolder, { recursive: true });
1855
+ const destVideoPath = (0, import_path15.resolve)(destFolder, destVideoName);
1811
1856
  let mode;
1812
1857
  try {
1813
1858
  if (!sameDev(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
1814
- (0, import_fs14.linkSync)(videoSourcePath, destVideoPath);
1859
+ (0, import_fs15.linkSync)(videoSourcePath, destVideoPath);
1815
1860
  mode = "hardlink";
1816
1861
  } catch {
1817
1862
  spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
1818
- (0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
1863
+ (0, import_fs15.cpSync)(videoSourcePath, destVideoPath);
1819
1864
  mode = "copy";
1820
1865
  }
1821
- if (subtitleSourcePath && destSubtitleName) (0, import_fs14.cpSync)(subtitleSourcePath, (0, import_path14.resolve)(destFolder, destSubtitleName));
1866
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs15.cpSync)(subtitleSourcePath, (0, import_path15.resolve)(destFolder, destSubtitleName));
1822
1867
  recordImport(sessionId, entryPath, destFolder, mode, tmdbId, "movie");
1823
1868
  } else {
1824
1869
  if (isDir) {
1825
1870
  const keep = new Set([videoFile, subtitle].filter(Boolean));
1826
- for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs14.rmSync)((0, import_path14.resolve)(entryPath, f), { recursive: true, force: true });
1827
- (0, import_fs14.renameSync)(videoSourcePath, (0, import_path14.resolve)(entryPath, destVideoName));
1828
- if (subtitleSourcePath && destSubtitleName) (0, import_fs14.renameSync)(subtitleSourcePath, (0, import_path14.resolve)(entryPath, destSubtitleName));
1871
+ for (const f of dirFiles.filter((f2) => !keep.has(f2))) trashPath((0, import_path15.resolve)(entryPath, f), { recursive: true });
1872
+ (0, import_fs15.renameSync)(videoSourcePath, (0, import_path15.resolve)(entryPath, destVideoName));
1873
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs15.renameSync)(subtitleSourcePath, (0, import_path15.resolve)(entryPath, destSubtitleName));
1829
1874
  moveFolder(entryPath, destFolder);
1830
1875
  } else {
1831
- (0, import_fs14.mkdirSync)(destFolder, { recursive: true });
1832
- const destVideoPath = (0, import_path14.resolve)(destFolder, destVideoName);
1876
+ (0, import_fs15.mkdirSync)(destFolder, { recursive: true });
1877
+ const destVideoPath = (0, import_path15.resolve)(destFolder, destVideoName);
1833
1878
  if (sameDev(videoSourcePath, destRoot)) {
1834
- (0, import_fs14.renameSync)(videoSourcePath, destVideoPath);
1879
+ (0, import_fs15.renameSync)(videoSourcePath, destVideoPath);
1835
1880
  } else {
1836
- (0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
1837
- (0, import_fs14.rmSync)(videoSourcePath);
1881
+ (0, import_fs15.cpSync)(videoSourcePath, destVideoPath);
1882
+ trashPath(videoSourcePath);
1838
1883
  }
1839
1884
  }
1840
1885
  recordImport(sessionId, entryPath, destFolder, "move", tmdbId, "movie");
@@ -1851,12 +1896,13 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1851
1896
  const ignoreSet = new Set(config.ignore ?? []);
1852
1897
  const seenIgnored = /* @__PURE__ */ new Set();
1853
1898
  for (const source of config.sources) {
1854
- if (!(0, import_fs14.existsSync)(source)) {
1899
+ if (!(0, import_fs15.existsSync)(source)) {
1855
1900
  spinner_default.warn(`source not found: ${import_termkit14.Color.white.encoder(source)}`);
1856
1901
  continue;
1857
1902
  }
1858
1903
  spinner_default.text = `scanning ${import_termkit14.Color.white.encoder(source)}`;
1859
1904
  for (const { entry, entryPath, isDir } of gatherEntries(source)) {
1905
+ spinner_default.text = `scanning: ${entry}`;
1860
1906
  if (ignoreSet.has(entry)) {
1861
1907
  seenIgnored.add(entry);
1862
1908
  if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
@@ -1891,8 +1937,8 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1891
1937
  continue;
1892
1938
  }
1893
1939
  const destName = `${nameMatch[0]} [${id}]`;
1894
- const destPath = (0, import_path14.resolve)(destRoot, destName);
1895
- if ((0, import_fs14.existsSync)(destPath)) {
1940
+ const destPath = (0, import_path15.resolve)(destRoot, destName);
1941
+ if ((0, import_fs15.existsSync)(destPath)) {
1896
1942
  spinner_default.warn(`already exists: ${destName}`);
1897
1943
  skipped++;
1898
1944
  continue;
@@ -1906,8 +1952,8 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1906
1952
  continue;
1907
1953
  }
1908
1954
  if (detectedType === "book") {
1909
- const destPath = (0, import_path14.resolve)(destRoot, entry);
1910
- if ((0, import_fs14.existsSync)(destPath)) {
1955
+ const destPath = (0, import_path15.resolve)(destRoot, entry);
1956
+ if ((0, import_fs15.existsSync)(destPath)) {
1911
1957
  spinner_default.warn(`already exists: ${entry}`);
1912
1958
  skipped++;
1913
1959
  continue;
@@ -1916,12 +1962,12 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1916
1962
  if (isDir || isBookDir) {
1917
1963
  moveFolder(entryPath, destPath);
1918
1964
  } else {
1919
- (0, import_fs14.mkdirSync)(destRoot, { recursive: true });
1965
+ (0, import_fs15.mkdirSync)(destRoot, { recursive: true });
1920
1966
  if (sameDev(entryPath, destRoot)) {
1921
- (0, import_fs14.renameSync)(entryPath, destPath);
1967
+ (0, import_fs15.renameSync)(entryPath, destPath);
1922
1968
  } else {
1923
- (0, import_fs14.cpSync)(entryPath, destPath);
1924
- (0, import_fs14.rmSync)(entryPath);
1969
+ (0, import_fs15.cpSync)(entryPath, destPath);
1970
+ (0, import_fs15.rmSync)(entryPath);
1925
1971
  }
1926
1972
  }
1927
1973
  recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
@@ -1953,6 +1999,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1953
1999
  let resolvedYear = parsed.year;
1954
2000
  if (config.tmdbApiKey) {
1955
2001
  if (detectedType === "tv") {
2002
+ spinner_default.text = `TMDb: ${parsed.title}`;
1956
2003
  const results = await searchTv(parsed.title, config.tmdbApiKey);
1957
2004
  if (results.length === 1) {
1958
2005
  tmdbId = results[0].id;
@@ -1997,18 +2044,18 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1997
2044
  const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
1998
2045
  if (existingFolder) {
1999
2046
  showFolderName = existingFolder;
2000
- showPath = (0, import_path14.resolve)(destRoot, existingFolder);
2047
+ showPath = (0, import_path15.resolve)(destRoot, existingFolder);
2001
2048
  } else if (auto) {
2002
2049
  showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
2003
- showPath = (0, import_path14.resolve)(destRoot, showFolderName);
2050
+ showPath = (0, import_path15.resolve)(destRoot, showFolderName);
2004
2051
  if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
2005
2052
  } else {
2006
2053
  pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
2007
2054
  continue;
2008
2055
  }
2009
2056
  }
2010
- const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
2011
- const seasonPath = (0, import_path14.resolve)(showPath, seasonFolderName);
2057
+ const seasonFolderName = parsed.season === 0 ? findSeasonFolder(showPath, 0, specialsFolder) ?? specialsFolder : findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
2058
+ const seasonPath = (0, import_path15.resolve)(showPath, seasonFolderName);
2012
2059
  const videoFile = isDir ? findVideo(entryPath) : entry;
2013
2060
  if (!videoFile) {
2014
2061
  if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
@@ -2016,13 +2063,15 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2016
2063
  continue;
2017
2064
  }
2018
2065
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
2066
+ if (tmdbId && config.tmdbApiKey) spinner_default.text = `TMDb: episode name for ${resolvedTitle}`;
2019
2067
  const tmdbEpisodeName = tmdbId && config.tmdbApiKey ? await getEpisodeName(tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
2020
2068
  const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, resolvedTitle, tmdbEpisodeName ?? void 0);
2021
2069
  const destVideoName = `${episodeName}.${videoExt}`;
2022
- const destVideoPath = (0, import_path14.resolve)(seasonPath, destVideoName);
2023
- const videoSourcePath = isDir ? (0, import_path14.resolve)(entryPath, videoFile) : entryPath;
2024
- if ((0, import_fs14.existsSync)(destVideoPath)) {
2025
- let shouldReplace = force;
2070
+ const destVideoPath = (0, import_path15.resolve)(seasonPath, destVideoName);
2071
+ const videoSourcePath = isDir ? (0, import_path15.resolve)(entryPath, videoFile) : entryPath;
2072
+ if ((0, import_fs15.existsSync)(destVideoPath)) {
2073
+ const isRepack = /\brepack\d*\b|\bproper\b/i.test(entry);
2074
+ let shouldReplace = force || isRepack;
2026
2075
  if (!shouldReplace && interactive) {
2027
2076
  spinner_default.stop();
2028
2077
  const select = new import_termkit14.Select();
@@ -2039,39 +2088,39 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2039
2088
  continue;
2040
2089
  }
2041
2090
  if (!dryRun) {
2042
- for (const f of (0, import_fs14.readdirSync)(seasonPath)) {
2043
- if (f.startsWith(`${episodeName}.`)) (0, import_fs14.rmSync)((0, import_path14.resolve)(seasonPath, f));
2091
+ for (const f of (0, import_fs15.readdirSync)(seasonPath)) {
2092
+ if (f.startsWith(`${episodeName}.`)) trashPath((0, import_path15.resolve)(seasonPath, f));
2044
2093
  }
2045
2094
  }
2046
2095
  }
2047
- const dirFiles = isDir ? (0, import_fs14.readdirSync)(entryPath) : [];
2096
+ const dirFiles = isDir ? (0, import_fs15.readdirSync)(entryPath) : [];
2048
2097
  const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
2049
2098
  const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
2050
- const subtitleSourcePath = subtitle ? (0, import_path14.resolve)(entryPath, subtitle) : null;
2099
+ const subtitleSourcePath = subtitle ? (0, import_path15.resolve)(entryPath, subtitle) : null;
2051
2100
  const destSubtitleName = subtitle && subtitleExt ? `${episodeName}.${subtitleExt}` : null;
2052
2101
  if (!dryRun) {
2053
- (0, import_fs14.mkdirSync)(seasonPath, { recursive: true });
2102
+ (0, import_fs15.mkdirSync)(seasonPath, { recursive: true });
2054
2103
  let mode = "move";
2055
2104
  if (useHardlink) {
2056
2105
  try {
2057
2106
  if (!sameDev(videoSourcePath, seasonPath)) throw new Error("cross-filesystem");
2058
- (0, import_fs14.linkSync)(videoSourcePath, destVideoPath);
2107
+ (0, import_fs15.linkSync)(videoSourcePath, destVideoPath);
2059
2108
  mode = "hardlink";
2060
2109
  } catch {
2061
2110
  spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
2062
- (0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
2111
+ (0, import_fs15.cpSync)(videoSourcePath, destVideoPath);
2063
2112
  mode = "copy";
2064
2113
  }
2065
- if (subtitleSourcePath && destSubtitleName) (0, import_fs14.cpSync)(subtitleSourcePath, (0, import_path14.resolve)(seasonPath, destSubtitleName));
2114
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs15.cpSync)(subtitleSourcePath, (0, import_path15.resolve)(seasonPath, destSubtitleName));
2066
2115
  } else {
2067
2116
  if (sameDev(videoSourcePath, seasonPath)) {
2068
- (0, import_fs14.renameSync)(videoSourcePath, destVideoPath);
2117
+ (0, import_fs15.renameSync)(videoSourcePath, destVideoPath);
2069
2118
  } else {
2070
- (0, import_fs14.cpSync)(videoSourcePath, destVideoPath);
2071
- (0, import_fs14.rmSync)(videoSourcePath);
2119
+ (0, import_fs15.cpSync)(videoSourcePath, destVideoPath);
2120
+ trashPath(videoSourcePath);
2072
2121
  }
2073
- if (subtitleSourcePath && destSubtitleName) (0, import_fs14.renameSync)(subtitleSourcePath, (0, import_path14.resolve)(seasonPath, destSubtitleName));
2074
- if (isDir) (0, import_fs14.rmSync)(entryPath, { recursive: true, force: true });
2122
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs15.renameSync)(subtitleSourcePath, (0, import_path15.resolve)(seasonPath, destSubtitleName));
2123
+ if (isDir) trashPath(entryPath, { recursive: true });
2075
2124
  }
2076
2125
  recordImport(sessionId, entryPath, seasonPath, mode, tmdbId, "tv");
2077
2126
  }
@@ -2136,7 +2185,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
2136
2185
  var scan_default = scan;
2137
2186
 
2138
2187
  // src/actions/shows.ts
2139
- var import_fs15 = require("fs");
2188
+ var import_fs16 = require("fs");
2140
2189
  var import_termkit15 = require("termkit");
2141
2190
  var dim = (s) => `\x1B[2m${s}\x1B[0m`;
2142
2191
  var shows = async () => {
@@ -2153,7 +2202,7 @@ ${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termki
2153
2202
  new import_termkit15.Table(
2154
2203
  allShows.map((show) => ({
2155
2204
  name: show.path.split("/").pop() ?? show.path,
2156
- size: (0, import_fs15.existsSync)(show.path) ? formatSize(dirSize(show.path)) : "\u2014",
2205
+ size: (0, import_fs16.existsSync)(show.path) ? formatSize(dirSize(show.path)) : "\u2014",
2157
2206
  tmdbId: show.tmdbId,
2158
2207
  ended: show.ended
2159
2208
  })),
@@ -2186,15 +2235,15 @@ ${import_termkit15.Color.yellow.encoder("SHOWS")}${destRoot ? ` ${import_termki
2186
2235
  var shows_default = shows;
2187
2236
 
2188
2237
  // src/actions/stats.ts
2189
- var import_fs16 = require("fs");
2190
- var import_path15 = require("path");
2238
+ var import_fs17 = require("fs");
2239
+ var import_path16 = require("path");
2191
2240
  var import_termkit16 = require("termkit");
2192
2241
  var countVideos = (dir) => {
2193
2242
  let count = 0;
2194
2243
  try {
2195
- for (const entry of (0, import_fs16.readdirSync)(dir, { withFileTypes: true })) {
2244
+ for (const entry of (0, import_fs17.readdirSync)(dir, { withFileTypes: true })) {
2196
2245
  if (entry.isDirectory()) {
2197
- count += countVideos((0, import_path15.resolve)(dir, entry.name));
2246
+ count += countVideos((0, import_path16.resolve)(dir, entry.name));
2198
2247
  } else {
2199
2248
  const ext = entry.name.match(/([^.]+$)/)?.[0]?.toLowerCase();
2200
2249
  if (ext && videoExtensions_default.includes(ext)) count++;
@@ -2206,9 +2255,9 @@ var countVideos = (dir) => {
2206
2255
  };
2207
2256
  var countDirs = (dir) => {
2208
2257
  try {
2209
- return (0, import_fs16.readdirSync)(dir).filter((f) => {
2258
+ return (0, import_fs17.readdirSync)(dir).filter((f) => {
2210
2259
  try {
2211
- return (0, import_fs16.lstatSync)((0, import_path15.resolve)(dir, f)).isDirectory();
2260
+ return (0, import_fs17.lstatSync)((0, import_path16.resolve)(dir, f)).isDirectory();
2212
2261
  } catch {
2213
2262
  return false;
2214
2263
  }
@@ -2222,16 +2271,16 @@ var stats = async () => {
2222
2271
  const shows2 = getShows();
2223
2272
  const rows = [];
2224
2273
  const movieDest = config.dest.movie;
2225
- if (movieDest && (0, import_fs16.existsSync)(movieDest)) {
2274
+ if (movieDest && (0, import_fs17.existsSync)(movieDest)) {
2226
2275
  rows.push({ category: "Movies", count: countDirs(movieDest), size: formatSize(dirSize(movieDest)) });
2227
2276
  }
2228
2277
  const tvDest = config.dest.tv;
2229
- if (tvDest && (0, import_fs16.existsSync)(tvDest)) {
2278
+ if (tvDest && (0, import_fs17.existsSync)(tvDest)) {
2230
2279
  rows.push({ category: "Shows", count: shows2.length, size: formatSize(dirSize(tvDest)) });
2231
2280
  rows.push({ category: "Episodes", count: countVideos(tvDest) });
2232
2281
  }
2233
2282
  const ps3Dest = config.dest.ps3;
2234
- if (ps3Dest && (0, import_fs16.existsSync)(ps3Dest)) {
2283
+ if (ps3Dest && (0, import_fs17.existsSync)(ps3Dest)) {
2235
2284
  rows.push({ category: "PS3", count: countDirs(ps3Dest), size: formatSize(dirSize(ps3Dest)) });
2236
2285
  }
2237
2286
  if (rows.length === 0) return;
@@ -2250,7 +2299,7 @@ var stats = async () => {
2250
2299
  var stats_default = stats;
2251
2300
 
2252
2301
  // src/actions/undo.ts
2253
- var import_fs17 = require("fs");
2302
+ var import_fs18 = require("fs");
2254
2303
  var import_termkit17 = require("termkit");
2255
2304
  var undo = async () => {
2256
2305
  spinner_default.start();
@@ -2265,7 +2314,7 @@ var undo = async () => {
2265
2314
  if (!useImports) {
2266
2315
  let undone2 = 0;
2267
2316
  for (const record of renameRecords) {
2268
- (0, import_fs17.renameSync)(record.newPath, record.oldPath);
2317
+ (0, import_fs18.renameSync)(record.newPath, record.oldPath);
2269
2318
  spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.newPath)} \u2192 ${import_termkit17.Color.white.encoder(record.oldPath)}`);
2270
2319
  undone2++;
2271
2320
  }
@@ -2287,12 +2336,12 @@ var undo = async () => {
2287
2336
  skipped++;
2288
2337
  continue;
2289
2338
  }
2290
- if (!(0, import_fs17.existsSync)(record.destinationPath)) {
2339
+ if (!(0, import_fs18.existsSync)(record.destinationPath)) {
2291
2340
  spinner_default.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
2292
2341
  skipped++;
2293
2342
  continue;
2294
2343
  }
2295
- (0, import_fs17.renameSync)(record.destinationPath, record.sourcePath);
2344
+ (0, import_fs18.renameSync)(record.destinationPath, record.sourcePath);
2296
2345
  spinner_default.succeed(`${import_termkit17.Color.green.encoder(record.destinationPath)} \u2192 ${import_termkit17.Color.white.encoder(record.sourcePath)}`);
2297
2346
  undone++;
2298
2347
  }
@@ -2305,37 +2354,37 @@ var undo_default = undo;
2305
2354
 
2306
2355
  // src/actions/watch.ts
2307
2356
  var import_chokidar = __toESM(require("chokidar"));
2308
- var import_fs18 = require("fs");
2309
- var import_path16 = require("path");
2357
+ var import_fs19 = require("fs");
2358
+ var import_path17 = require("path");
2310
2359
  var import_termkit18 = require("termkit");
2311
2360
  var sameDev2 = (a, b) => {
2312
2361
  try {
2313
2362
  let bExisting = b;
2314
- while (!(0, import_fs18.existsSync)(bExisting)) bExisting = (0, import_path16.dirname)(bExisting);
2315
- return (0, import_fs18.statSync)(a).dev === (0, import_fs18.statSync)(bExisting).dev;
2363
+ while (!(0, import_fs19.existsSync)(bExisting)) bExisting = (0, import_path17.dirname)(bExisting);
2364
+ return (0, import_fs19.statSync)(a).dev === (0, import_fs19.statSync)(bExisting).dev;
2316
2365
  } catch {
2317
2366
  return false;
2318
2367
  }
2319
2368
  };
2320
2369
  var moveItem = (src, dest) => {
2321
2370
  if (sameDev2(src, dest)) {
2322
- (0, import_fs18.renameSync)(src, dest);
2371
+ (0, import_fs19.renameSync)(src, dest);
2323
2372
  } else {
2324
- (0, import_fs18.cpSync)(src, dest, { recursive: true });
2325
- (0, import_fs18.rmSync)(src, { recursive: true, force: true });
2373
+ (0, import_fs19.cpSync)(src, dest, { recursive: true });
2374
+ (0, import_fs19.rmSync)(src, { recursive: true, force: true });
2326
2375
  }
2327
2376
  };
2328
- var findVideo2 = (dir) => (0, import_fs18.readdirSync)(dir).find((f) => {
2377
+ var findVideo2 = (dir) => (0, import_fs19.readdirSync)(dir).find((f) => {
2329
2378
  const ext = f.match(/([^.]+$)/)?.[0];
2330
2379
  return ext && videoExtensions_default.includes(ext);
2331
2380
  }) ?? null;
2332
- var containsBook2 = (dir, depth = 2) => (0, import_fs18.readdirSync)(dir).some((f) => {
2381
+ var containsBook2 = (dir, depth = 2) => (0, import_fs19.readdirSync)(dir).some((f) => {
2333
2382
  const ext = f.match(/([^.]+$)/)?.[0];
2334
2383
  if (ext && bookExtensions_default.includes(ext)) return true;
2335
2384
  if (depth > 1) {
2336
2385
  try {
2337
- const sub = (0, import_path16.resolve)(dir, f);
2338
- if ((0, import_fs18.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
2386
+ const sub = (0, import_path17.resolve)(dir, f);
2387
+ if ((0, import_fs19.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
2339
2388
  } catch {
2340
2389
  }
2341
2390
  }
@@ -2346,26 +2395,26 @@ var isSeasonDirName2 = (name) => !isTvEpisodeName2(name) && /(?:^|[.\s_-])(?:sea
2346
2395
  var expandWatchPath = (p) => {
2347
2396
  let isDir;
2348
2397
  try {
2349
- isDir = (0, import_fs18.lstatSync)(p).isDirectory();
2398
+ isDir = (0, import_fs19.lstatSync)(p).isDirectory();
2350
2399
  } catch {
2351
2400
  return [p];
2352
2401
  }
2353
2402
  if (!isDir) return [p];
2354
- const name = (0, import_path16.basename)(p);
2403
+ const name = (0, import_path17.basename)(p);
2355
2404
  if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
2356
2405
  let children;
2357
2406
  try {
2358
- children = (0, import_fs18.readdirSync)(p);
2407
+ children = (0, import_fs19.readdirSync)(p);
2359
2408
  } catch {
2360
2409
  return [p];
2361
2410
  }
2362
2411
  if (children.some((c) => isTvEpisodeName2(c))) {
2363
2412
  const entries = [];
2364
2413
  for (const child of children) {
2365
- const cp = (0, import_path16.resolve)(p, child);
2414
+ const cp = (0, import_path17.resolve)(p, child);
2366
2415
  let cd;
2367
2416
  try {
2368
- cd = (0, import_fs18.lstatSync)(cp).isDirectory();
2417
+ cd = (0, import_fs19.lstatSync)(cp).isDirectory();
2369
2418
  } catch {
2370
2419
  continue;
2371
2420
  }
@@ -2377,7 +2426,7 @@ var expandWatchPath = (p) => {
2377
2426
  }
2378
2427
  const seasonDirs = children.filter((c) => {
2379
2428
  try {
2380
- return isSeasonDirName2(c) && (0, import_fs18.lstatSync)((0, import_path16.resolve)(p, c)).isDirectory();
2429
+ return isSeasonDirName2(c) && (0, import_fs19.lstatSync)((0, import_path17.resolve)(p, c)).isDirectory();
2381
2430
  } catch {
2382
2431
  return false;
2383
2432
  }
@@ -2385,18 +2434,18 @@ var expandWatchPath = (p) => {
2385
2434
  if (seasonDirs.length > 0) {
2386
2435
  const entries = [];
2387
2436
  for (const sd of seasonDirs) {
2388
- const sp = (0, import_path16.resolve)(p, sd);
2437
+ const sp = (0, import_path17.resolve)(p, sd);
2389
2438
  let sc;
2390
2439
  try {
2391
- sc = (0, import_fs18.readdirSync)(sp);
2440
+ sc = (0, import_fs19.readdirSync)(sp);
2392
2441
  } catch {
2393
2442
  continue;
2394
2443
  }
2395
2444
  for (const child of sc) {
2396
- const cp = (0, import_path16.resolve)(sp, child);
2445
+ const cp = (0, import_path17.resolve)(sp, child);
2397
2446
  let cd;
2398
2447
  try {
2399
- cd = (0, import_fs18.lstatSync)(cp).isDirectory();
2448
+ cd = (0, import_fs19.lstatSync)(cp).isDirectory();
2400
2449
  } catch {
2401
2450
  continue;
2402
2451
  }
@@ -2410,10 +2459,10 @@ var expandWatchPath = (p) => {
2410
2459
  return [p];
2411
2460
  };
2412
2461
  var findSeasonFolder2 = (showPath, season) => {
2413
- if (!(0, import_fs18.existsSync)(showPath)) return null;
2414
- const folders = (0, import_fs18.readdirSync)(showPath).filter((f) => {
2462
+ if (!(0, import_fs19.existsSync)(showPath)) return null;
2463
+ const folders = (0, import_fs19.readdirSync)(showPath).filter((f) => {
2415
2464
  try {
2416
- return (0, import_fs18.lstatSync)((0, import_path16.resolve)(showPath, f)).isDirectory();
2465
+ return (0, import_fs19.lstatSync)((0, import_path17.resolve)(showPath, f)).isDirectory();
2417
2466
  } catch {
2418
2467
  return false;
2419
2468
  }
@@ -2426,10 +2475,10 @@ var findSeasonFolder2 = (showPath, season) => {
2426
2475
  var processItem = async (entryPath, useHardlink, language, auto) => {
2427
2476
  const config = getConfig();
2428
2477
  const sessionId = (/* @__PURE__ */ new Date()).toISOString();
2429
- const entry = (0, import_path16.basename)(entryPath);
2478
+ const entry = (0, import_path17.basename)(entryPath);
2430
2479
  const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
2431
2480
  const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
2432
- const isDir = (0, import_fs18.lstatSync)(entryPath).isDirectory();
2481
+ const isDir = (0, import_fs19.lstatSync)(entryPath).isDirectory();
2433
2482
  const ext = entry.match(/([^.]+$)/)?.[0];
2434
2483
  const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
2435
2484
  const isBook = !isDir && ext && bookExtensions_default.includes(ext);
@@ -2455,8 +2504,8 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2455
2504
  const id = entry.split("-")[0];
2456
2505
  if (!nameMatch || !id) return;
2457
2506
  const destName = `${nameMatch[0]} [${id}]`;
2458
- const destPath = (0, import_path16.resolve)(destRoot, destName);
2459
- if ((0, import_fs18.existsSync)(destPath)) {
2507
+ const destPath = (0, import_path17.resolve)(destRoot, destName);
2508
+ if ((0, import_fs19.existsSync)(destPath)) {
2460
2509
  spinner_default.warn(`already exists: ${destName}`);
2461
2510
  return;
2462
2511
  }
@@ -2466,20 +2515,20 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2466
2515
  return;
2467
2516
  }
2468
2517
  if (detectedType === "book") {
2469
- const destPath = (0, import_path16.resolve)(destRoot, entry);
2470
- if ((0, import_fs18.existsSync)(destPath)) {
2518
+ const destPath = (0, import_path17.resolve)(destRoot, entry);
2519
+ if ((0, import_fs19.existsSync)(destPath)) {
2471
2520
  spinner_default.warn(`already exists: ${entry}`);
2472
2521
  return;
2473
2522
  }
2474
2523
  if (isDir || isBookDir) {
2475
2524
  moveItem(entryPath, destPath);
2476
2525
  } else {
2477
- (0, import_fs18.mkdirSync)(destRoot, { recursive: true });
2526
+ (0, import_fs19.mkdirSync)(destRoot, { recursive: true });
2478
2527
  if (sameDev2(entryPath, destRoot)) {
2479
- (0, import_fs18.renameSync)(entryPath, destPath);
2528
+ (0, import_fs19.renameSync)(entryPath, destPath);
2480
2529
  } else {
2481
- (0, import_fs18.cpSync)(entryPath, destPath);
2482
- (0, import_fs18.rmSync)(entryPath);
2530
+ (0, import_fs19.cpSync)(entryPath, destPath);
2531
+ (0, import_fs19.rmSync)(entryPath);
2483
2532
  }
2484
2533
  }
2485
2534
  recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
@@ -2504,14 +2553,14 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2504
2553
  showFolderName = showPath.split("/").pop() ?? registeredShow.path;
2505
2554
  } else if (auto) {
2506
2555
  showFolderName = formatMovieName(movieFormat, parsed.title, parsed.year);
2507
- showPath = (0, import_path16.resolve)(destRoot, showFolderName);
2556
+ showPath = (0, import_path17.resolve)(destRoot, showFolderName);
2508
2557
  upsertShow(showPath, null, parsed.title);
2509
2558
  } else {
2510
2559
  if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
2511
2560
  return;
2512
2561
  }
2513
2562
  const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
2514
- const seasonPath = (0, import_path16.resolve)(showPath, seasonFolderName);
2563
+ const seasonPath = (0, import_path17.resolve)(showPath, seasonFolderName);
2515
2564
  const videoFile2 = isDir ? findVideo2(entryPath) : entry;
2516
2565
  if (!videoFile2) {
2517
2566
  if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
@@ -2521,39 +2570,39 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2521
2570
  const tmdbEpisodeName = registeredShow?.tmdbId && config.tmdbApiKey ? await getEpisodeName(registeredShow.tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
2522
2571
  const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, parsed.title, tmdbEpisodeName ?? void 0);
2523
2572
  const destVideoName2 = `${episodeName}.${videoExt2}`;
2524
- const destVideoPath = (0, import_path16.resolve)(seasonPath, destVideoName2);
2525
- const videoSourcePath2 = isDir ? (0, import_path16.resolve)(entryPath, videoFile2) : entryPath;
2526
- if ((0, import_fs18.existsSync)(destVideoPath)) {
2573
+ const destVideoPath = (0, import_path17.resolve)(seasonPath, destVideoName2);
2574
+ const videoSourcePath2 = isDir ? (0, import_path17.resolve)(entryPath, videoFile2) : entryPath;
2575
+ if ((0, import_fs19.existsSync)(destVideoPath)) {
2527
2576
  spinner_default.warn(`already exists: ${episodeName}`);
2528
2577
  return;
2529
2578
  }
2530
- const dirFiles2 = isDir ? (0, import_fs18.readdirSync)(entryPath) : [];
2579
+ const dirFiles2 = isDir ? (0, import_fs19.readdirSync)(entryPath) : [];
2531
2580
  const subtitle2 = isDir ? findSubtitle(dirFiles2, language) : null;
2532
2581
  const subtitleExt2 = subtitle2?.match(/([^.]+$)/)?.[0];
2533
- const subtitleSourcePath2 = subtitle2 ? (0, import_path16.resolve)(entryPath, subtitle2) : null;
2582
+ const subtitleSourcePath2 = subtitle2 ? (0, import_path17.resolve)(entryPath, subtitle2) : null;
2534
2583
  const destSubtitleName2 = subtitle2 && subtitleExt2 ? `${episodeName}.${subtitleExt2}` : null;
2535
- (0, import_fs18.mkdirSync)(seasonPath, { recursive: true });
2584
+ (0, import_fs19.mkdirSync)(seasonPath, { recursive: true });
2536
2585
  let mode = "move";
2537
2586
  if (useHardlink) {
2538
2587
  try {
2539
2588
  if (!sameDev2(videoSourcePath2, seasonPath)) throw new Error("cross-filesystem");
2540
- (0, import_fs18.linkSync)(videoSourcePath2, destVideoPath);
2589
+ (0, import_fs19.linkSync)(videoSourcePath2, destVideoPath);
2541
2590
  mode = "hardlink";
2542
2591
  } catch {
2543
2592
  spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
2544
- (0, import_fs18.cpSync)(videoSourcePath2, destVideoPath);
2593
+ (0, import_fs19.cpSync)(videoSourcePath2, destVideoPath);
2545
2594
  mode = "copy";
2546
2595
  }
2547
- if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs18.cpSync)(subtitleSourcePath2, (0, import_path16.resolve)(seasonPath, destSubtitleName2));
2596
+ if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs19.cpSync)(subtitleSourcePath2, (0, import_path17.resolve)(seasonPath, destSubtitleName2));
2548
2597
  } else {
2549
2598
  if (sameDev2(videoSourcePath2, seasonPath)) {
2550
- (0, import_fs18.renameSync)(videoSourcePath2, destVideoPath);
2599
+ (0, import_fs19.renameSync)(videoSourcePath2, destVideoPath);
2551
2600
  } else {
2552
- (0, import_fs18.cpSync)(videoSourcePath2, destVideoPath);
2553
- (0, import_fs18.rmSync)(videoSourcePath2);
2601
+ (0, import_fs19.cpSync)(videoSourcePath2, destVideoPath);
2602
+ (0, import_fs19.rmSync)(videoSourcePath2);
2554
2603
  }
2555
- if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs18.renameSync)(subtitleSourcePath2, (0, import_path16.resolve)(seasonPath, destSubtitleName2));
2556
- if (isDir) (0, import_fs18.rmSync)(entryPath, { recursive: true, force: true });
2604
+ if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs19.renameSync)(subtitleSourcePath2, (0, import_path17.resolve)(seasonPath, destSubtitleName2));
2605
+ if (isDir) (0, import_fs19.rmSync)(entryPath, { recursive: true, force: true });
2557
2606
  }
2558
2607
  recordImport(sessionId, entryPath, seasonPath, mode, void 0, "tv");
2559
2608
  spinner_default.succeed(`imported ${import_termkit18.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
@@ -2561,8 +2610,8 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2561
2610
  }
2562
2611
  const edition = detectEdition(entry);
2563
2612
  const folderName = formatMovieName(movieFormat, parsed.title, parsed.year, edition);
2564
- const destFolder = (0, import_path16.resolve)(destRoot, folderName);
2565
- if ((0, import_fs18.existsSync)(destFolder)) {
2613
+ const destFolder = (0, import_path17.resolve)(destRoot, folderName);
2614
+ if ((0, import_fs19.existsSync)(destFolder)) {
2566
2615
  spinner_default.warn(`already exists: ${folderName}`);
2567
2616
  return;
2568
2617
  }
@@ -2573,42 +2622,42 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2573
2622
  }
2574
2623
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
2575
2624
  const destVideoName = `${folderName}.${videoExt}`;
2576
- const videoSourcePath = isDir ? (0, import_path16.resolve)(entryPath, videoFile) : entryPath;
2577
- const dirFiles = isDir ? (0, import_fs18.readdirSync)(entryPath) : [];
2625
+ const videoSourcePath = isDir ? (0, import_path17.resolve)(entryPath, videoFile) : entryPath;
2626
+ const dirFiles = isDir ? (0, import_fs19.readdirSync)(entryPath) : [];
2578
2627
  const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
2579
2628
  const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
2580
- const subtitleSourcePath = subtitle ? (0, import_path16.resolve)(entryPath, subtitle) : null;
2629
+ const subtitleSourcePath = subtitle ? (0, import_path17.resolve)(entryPath, subtitle) : null;
2581
2630
  const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
2582
2631
  if (useHardlink) {
2583
- (0, import_fs18.mkdirSync)(destFolder, { recursive: true });
2584
- const destVideoPath = (0, import_path16.resolve)(destFolder, destVideoName);
2632
+ (0, import_fs19.mkdirSync)(destFolder, { recursive: true });
2633
+ const destVideoPath = (0, import_path17.resolve)(destFolder, destVideoName);
2585
2634
  let mode;
2586
2635
  try {
2587
2636
  if (!sameDev2(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
2588
- (0, import_fs18.linkSync)(videoSourcePath, destVideoPath);
2637
+ (0, import_fs19.linkSync)(videoSourcePath, destVideoPath);
2589
2638
  mode = "hardlink";
2590
2639
  } catch {
2591
2640
  spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
2592
- (0, import_fs18.cpSync)(videoSourcePath, destVideoPath);
2641
+ (0, import_fs19.cpSync)(videoSourcePath, destVideoPath);
2593
2642
  mode = "copy";
2594
2643
  }
2595
- if (subtitleSourcePath && destSubtitleName) (0, import_fs18.cpSync)(subtitleSourcePath, (0, import_path16.resolve)(destFolder, destSubtitleName));
2644
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs19.cpSync)(subtitleSourcePath, (0, import_path17.resolve)(destFolder, destSubtitleName));
2596
2645
  recordImport(sessionId, entryPath, destFolder, mode, void 0, "movie");
2597
2646
  } else {
2598
2647
  if (isDir) {
2599
2648
  const keep = new Set([videoFile, subtitle].filter(Boolean));
2600
- for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs18.rmSync)((0, import_path16.resolve)(entryPath, f), { recursive: true, force: true });
2601
- (0, import_fs18.renameSync)(videoSourcePath, (0, import_path16.resolve)(entryPath, destVideoName));
2602
- if (subtitleSourcePath && destSubtitleName) (0, import_fs18.renameSync)(subtitleSourcePath, (0, import_path16.resolve)(entryPath, destSubtitleName));
2649
+ for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs19.rmSync)((0, import_path17.resolve)(entryPath, f), { recursive: true, force: true });
2650
+ (0, import_fs19.renameSync)(videoSourcePath, (0, import_path17.resolve)(entryPath, destVideoName));
2651
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs19.renameSync)(subtitleSourcePath, (0, import_path17.resolve)(entryPath, destSubtitleName));
2603
2652
  moveItem(entryPath, destFolder);
2604
2653
  } else {
2605
- (0, import_fs18.mkdirSync)(destFolder, { recursive: true });
2606
- const destVideoPath = (0, import_path16.resolve)(destFolder, destVideoName);
2654
+ (0, import_fs19.mkdirSync)(destFolder, { recursive: true });
2655
+ const destVideoPath = (0, import_path17.resolve)(destFolder, destVideoName);
2607
2656
  if (sameDev2(videoSourcePath, destRoot)) {
2608
- (0, import_fs18.renameSync)(videoSourcePath, destVideoPath);
2657
+ (0, import_fs19.renameSync)(videoSourcePath, destVideoPath);
2609
2658
  } else {
2610
- (0, import_fs18.cpSync)(videoSourcePath, destVideoPath);
2611
- (0, import_fs18.rmSync)(videoSourcePath);
2659
+ (0, import_fs19.cpSync)(videoSourcePath, destVideoPath);
2660
+ (0, import_fs19.rmSync)(videoSourcePath);
2612
2661
  }
2613
2662
  }
2614
2663
  recordImport(sessionId, entryPath, destFolder, "move", void 0, "movie");
@@ -2653,7 +2702,7 @@ var watch = async ({ hardlink = false, auto = false }) => {
2653
2702
  var watch_default = watch;
2654
2703
 
2655
2704
  // package.json
2656
- var version = "0.2.5";
2705
+ var version = "0.2.6";
2657
2706
 
2658
2707
  // src/program.ts
2659
2708
  var toCamel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());