reelsort 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1441,10 +1441,99 @@ var findVideo = (dir) => (0, import_fs13.readdirSync)(dir).find((f) => {
1441
1441
  const ext = f.match(/([^.]+$)/)?.[0];
1442
1442
  return ext && videoExtensions_default.includes(ext);
1443
1443
  }) ?? null;
1444
- var containsBook = (dir) => (0, import_fs13.readdirSync)(dir).some((f) => {
1444
+ var containsBook = (dir, depth = 2) => (0, import_fs13.readdirSync)(dir).some((f) => {
1445
1445
  const ext = f.match(/([^.]+$)/)?.[0];
1446
- return ext && bookExtensions_default.includes(ext);
1446
+ if (ext && bookExtensions_default.includes(ext)) return true;
1447
+ if (depth > 1) {
1448
+ try {
1449
+ const sub = (0, import_path14.resolve)(dir, f);
1450
+ if ((0, import_fs13.lstatSync)(sub).isDirectory()) return containsBook(sub, depth - 1);
1451
+ } catch {
1452
+ }
1453
+ }
1454
+ return false;
1447
1455
  });
1456
+ var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
1457
+ var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
1458
+ var gatherEntries = (source) => {
1459
+ const result = [];
1460
+ for (const name of (0, import_fs13.readdirSync)(source)) {
1461
+ const fullPath = (0, import_path14.resolve)(source, name);
1462
+ let isDir;
1463
+ try {
1464
+ isDir = (0, import_fs13.lstatSync)(fullPath).isDirectory();
1465
+ } catch {
1466
+ continue;
1467
+ }
1468
+ const ext = name.match(/([^.]+$)/)?.[0];
1469
+ const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
1470
+ const isBook = !isDir && ext && bookExtensions_default.includes(ext);
1471
+ if (!isDir && !isVideo && !isBook) continue;
1472
+ if (!isDir) {
1473
+ result.push({ entry: name, entryPath: fullPath, isDir: false });
1474
+ continue;
1475
+ }
1476
+ if (/(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name) || isTvEpisodeName(name)) {
1477
+ result.push({ entry: name, entryPath: fullPath, isDir: true });
1478
+ continue;
1479
+ }
1480
+ let children;
1481
+ try {
1482
+ children = (0, import_fs13.readdirSync)(fullPath);
1483
+ } catch {
1484
+ result.push({ entry: name, entryPath: fullPath, isDir: true });
1485
+ continue;
1486
+ }
1487
+ if (children.some((c) => isTvEpisodeName(c))) {
1488
+ for (const child of children) {
1489
+ const childPath = (0, import_path14.resolve)(fullPath, child);
1490
+ let childIsDir;
1491
+ try {
1492
+ childIsDir = (0, import_fs13.lstatSync)(childPath).isDirectory();
1493
+ } catch {
1494
+ continue;
1495
+ }
1496
+ const childExt = child.match(/([^.]+$)/)?.[0];
1497
+ if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
1498
+ result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
1499
+ }
1500
+ continue;
1501
+ }
1502
+ const seasonDirs = children.filter((c) => {
1503
+ try {
1504
+ return isSeasonDirName(c) && (0, import_fs13.lstatSync)((0, import_path14.resolve)(fullPath, c)).isDirectory();
1505
+ } catch {
1506
+ return false;
1507
+ }
1508
+ });
1509
+ if (seasonDirs.length > 0) {
1510
+ for (const seasonDir of seasonDirs) {
1511
+ const seasonPath = (0, import_path14.resolve)(fullPath, seasonDir);
1512
+ let seasonChildren;
1513
+ try {
1514
+ seasonChildren = (0, import_fs13.readdirSync)(seasonPath);
1515
+ } catch {
1516
+ continue;
1517
+ }
1518
+ for (const child of seasonChildren) {
1519
+ const childPath = (0, import_path14.resolve)(seasonPath, child);
1520
+ let childIsDir;
1521
+ try {
1522
+ childIsDir = (0, import_fs13.lstatSync)(childPath).isDirectory();
1523
+ } catch {
1524
+ continue;
1525
+ }
1526
+ const childExt = child.match(/([^.]+$)/)?.[0];
1527
+ if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
1528
+ result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
1529
+ }
1530
+ }
1531
+ continue;
1532
+ }
1533
+ result.push({ entry: name, entryPath: fullPath, isDir: true });
1534
+ }
1535
+ return result;
1536
+ };
1448
1537
  var findSeasonFolder = (showPath, season) => {
1449
1538
  if (!(0, import_fs13.existsSync)(showPath)) return null;
1450
1539
  const folders = (0, import_fs13.readdirSync)(showPath).filter((f) => {
@@ -1573,24 +1662,16 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
1573
1662
  continue;
1574
1663
  }
1575
1664
  spinner_default.text = `scanning ${import_termkit14.Color.white.encoder(source)}`;
1576
- for (const entry of (0, import_fs13.readdirSync)(source)) {
1577
- const entryPath = (0, import_path14.resolve)(source, entry);
1578
- const isDir = (0, import_fs13.lstatSync)(entryPath).isDirectory();
1665
+ for (const { entry, entryPath, isDir } of gatherEntries(source)) {
1579
1666
  const ext = entry.match(/([^.]+$)/)?.[0];
1580
- const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
1581
1667
  const isBook = !isDir && ext && bookExtensions_default.includes(ext);
1582
1668
  const isBookDir = isDir && containsBook(entryPath);
1583
- if (!isDir && !isVideo && !isBook) {
1584
- if (verbose) spinner_default.info(`skipped ${entry}`);
1585
- skipped++;
1586
- continue;
1587
- }
1588
1669
  let detectedType;
1589
1670
  if (type) {
1590
1671
  detectedType = type;
1591
1672
  } else if (isBook || isBookDir) {
1592
1673
  detectedType = "book";
1593
- } else if (isDir && /(?<=\[).+?(?=\])/.test(entry)) {
1674
+ } else if (isDir && /^[A-Z]{4}\d{5}/i.test(entry)) {
1594
1675
  detectedType = "ps3";
1595
1676
  } else if (/S\d{2,3}E\d{2,3}/i.test(entry) || /\d+x\d{2,3}/i.test(entry)) {
1596
1677
  detectedType = "tv";
@@ -1830,7 +1911,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
1830
1911
  }
1831
1912
  }
1832
1913
  }
1833
- spinner_default.succeed(`imported ${imported} items`);
1914
+ spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
1834
1915
  if (skipped) spinner_default.info(`skipped ${skipped} items`);
1835
1916
  spinner_default.stop();
1836
1917
  };
@@ -1999,10 +2080,86 @@ var findVideo2 = (dir) => (0, import_fs17.readdirSync)(dir).find((f) => {
1999
2080
  const ext = f.match(/([^.]+$)/)?.[0];
2000
2081
  return ext && videoExtensions_default.includes(ext);
2001
2082
  }) ?? null;
2002
- var containsBook2 = (dir) => (0, import_fs17.readdirSync)(dir).some((f) => {
2083
+ var containsBook2 = (dir, depth = 2) => (0, import_fs17.readdirSync)(dir).some((f) => {
2003
2084
  const ext = f.match(/([^.]+$)/)?.[0];
2004
- return ext && bookExtensions_default.includes(ext);
2085
+ if (ext && bookExtensions_default.includes(ext)) return true;
2086
+ if (depth > 1) {
2087
+ try {
2088
+ const sub = (0, import_path16.resolve)(dir, f);
2089
+ if ((0, import_fs17.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
2090
+ } catch {
2091
+ }
2092
+ }
2093
+ return false;
2005
2094
  });
2095
+ var isTvEpisodeName2 = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
2096
+ var isSeasonDirName2 = (name) => !isTvEpisodeName2(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
2097
+ var expandWatchPath = (p) => {
2098
+ let isDir;
2099
+ try {
2100
+ isDir = (0, import_fs17.lstatSync)(p).isDirectory();
2101
+ } catch {
2102
+ return [p];
2103
+ }
2104
+ if (!isDir) return [p];
2105
+ const name = (0, import_path16.basename)(p);
2106
+ if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
2107
+ let children;
2108
+ try {
2109
+ children = (0, import_fs17.readdirSync)(p);
2110
+ } catch {
2111
+ return [p];
2112
+ }
2113
+ if (children.some((c) => isTvEpisodeName2(c))) {
2114
+ const entries = [];
2115
+ for (const child of children) {
2116
+ const cp = (0, import_path16.resolve)(p, child);
2117
+ let cd;
2118
+ try {
2119
+ cd = (0, import_fs17.lstatSync)(cp).isDirectory();
2120
+ } catch {
2121
+ continue;
2122
+ }
2123
+ const ext = child.match(/([^.]+$)/)?.[0];
2124
+ if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
2125
+ entries.push(cp);
2126
+ }
2127
+ return entries.length > 0 ? entries : [p];
2128
+ }
2129
+ const seasonDirs = children.filter((c) => {
2130
+ try {
2131
+ return isSeasonDirName2(c) && (0, import_fs17.lstatSync)((0, import_path16.resolve)(p, c)).isDirectory();
2132
+ } catch {
2133
+ return false;
2134
+ }
2135
+ });
2136
+ if (seasonDirs.length > 0) {
2137
+ const entries = [];
2138
+ for (const sd of seasonDirs) {
2139
+ const sp = (0, import_path16.resolve)(p, sd);
2140
+ let sc;
2141
+ try {
2142
+ sc = (0, import_fs17.readdirSync)(sp);
2143
+ } catch {
2144
+ continue;
2145
+ }
2146
+ for (const child of sc) {
2147
+ const cp = (0, import_path16.resolve)(sp, child);
2148
+ let cd;
2149
+ try {
2150
+ cd = (0, import_fs17.lstatSync)(cp).isDirectory();
2151
+ } catch {
2152
+ continue;
2153
+ }
2154
+ const ext = child.match(/([^.]+$)/)?.[0];
2155
+ if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
2156
+ entries.push(cp);
2157
+ }
2158
+ }
2159
+ return entries.length > 0 ? entries : [p];
2160
+ }
2161
+ return [p];
2162
+ };
2006
2163
  var findSeasonFolder2 = (showPath, season) => {
2007
2164
  if (!(0, import_fs17.existsSync)(showPath)) return null;
2008
2165
  const folders = (0, import_fs17.readdirSync)(showPath).filter((f) => {
@@ -2222,7 +2379,9 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
2222
2379
  setTimeout(async () => {
2223
2380
  pending.delete(path);
2224
2381
  try {
2225
- await processItem(path, hardlink, verbose, language, auto);
2382
+ for (const entry of expandWatchPath(path)) {
2383
+ await processItem(entry, hardlink, verbose, language, auto);
2384
+ }
2226
2385
  } catch (err) {
2227
2386
  spinner_default.fail(`error processing ${path}: ${err.message}`);
2228
2387
  }
@@ -2245,7 +2404,7 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
2245
2404
  var watch_default = watch;
2246
2405
 
2247
2406
  // package.json
2248
- var version = "0.2.1";
2407
+ var version = "0.2.2";
2249
2408
 
2250
2409
  // src/program.ts
2251
2410
  var toCamel = (s) => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
package/dist/index.js CHANGED
@@ -1244,10 +1244,99 @@ var findVideo = (dir) => (0, import_fs10.readdirSync)(dir).find((f) => {
1244
1244
  const ext = f.match(/([^.]+$)/)?.[0];
1245
1245
  return ext && videoExtensions_default.includes(ext);
1246
1246
  }) ?? null;
1247
- var containsBook = (dir) => (0, import_fs10.readdirSync)(dir).some((f) => {
1247
+ var containsBook = (dir, depth = 2) => (0, import_fs10.readdirSync)(dir).some((f) => {
1248
1248
  const ext = f.match(/([^.]+$)/)?.[0];
1249
- return ext && bookExtensions_default.includes(ext);
1249
+ if (ext && bookExtensions_default.includes(ext)) return true;
1250
+ if (depth > 1) {
1251
+ try {
1252
+ const sub = (0, import_path11.resolve)(dir, f);
1253
+ if ((0, import_fs10.lstatSync)(sub).isDirectory()) return containsBook(sub, depth - 1);
1254
+ } catch {
1255
+ }
1256
+ }
1257
+ return false;
1250
1258
  });
1259
+ var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
1260
+ var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
1261
+ var gatherEntries = (source) => {
1262
+ const result = [];
1263
+ for (const name of (0, import_fs10.readdirSync)(source)) {
1264
+ const fullPath = (0, import_path11.resolve)(source, name);
1265
+ let isDir;
1266
+ try {
1267
+ isDir = (0, import_fs10.lstatSync)(fullPath).isDirectory();
1268
+ } catch {
1269
+ continue;
1270
+ }
1271
+ const ext = name.match(/([^.]+$)/)?.[0];
1272
+ const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
1273
+ const isBook = !isDir && ext && bookExtensions_default.includes(ext);
1274
+ if (!isDir && !isVideo && !isBook) continue;
1275
+ if (!isDir) {
1276
+ result.push({ entry: name, entryPath: fullPath, isDir: false });
1277
+ continue;
1278
+ }
1279
+ if (/(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name) || isTvEpisodeName(name)) {
1280
+ result.push({ entry: name, entryPath: fullPath, isDir: true });
1281
+ continue;
1282
+ }
1283
+ let children;
1284
+ try {
1285
+ children = (0, import_fs10.readdirSync)(fullPath);
1286
+ } catch {
1287
+ result.push({ entry: name, entryPath: fullPath, isDir: true });
1288
+ continue;
1289
+ }
1290
+ if (children.some((c) => isTvEpisodeName(c))) {
1291
+ for (const child of children) {
1292
+ const childPath = (0, import_path11.resolve)(fullPath, child);
1293
+ let childIsDir;
1294
+ try {
1295
+ childIsDir = (0, import_fs10.lstatSync)(childPath).isDirectory();
1296
+ } catch {
1297
+ continue;
1298
+ }
1299
+ const childExt = child.match(/([^.]+$)/)?.[0];
1300
+ if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
1301
+ result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
1302
+ }
1303
+ continue;
1304
+ }
1305
+ const seasonDirs = children.filter((c) => {
1306
+ try {
1307
+ return isSeasonDirName(c) && (0, import_fs10.lstatSync)((0, import_path11.resolve)(fullPath, c)).isDirectory();
1308
+ } catch {
1309
+ return false;
1310
+ }
1311
+ });
1312
+ if (seasonDirs.length > 0) {
1313
+ for (const seasonDir of seasonDirs) {
1314
+ const seasonPath = (0, import_path11.resolve)(fullPath, seasonDir);
1315
+ let seasonChildren;
1316
+ try {
1317
+ seasonChildren = (0, import_fs10.readdirSync)(seasonPath);
1318
+ } catch {
1319
+ continue;
1320
+ }
1321
+ for (const child of seasonChildren) {
1322
+ const childPath = (0, import_path11.resolve)(seasonPath, child);
1323
+ let childIsDir;
1324
+ try {
1325
+ childIsDir = (0, import_fs10.lstatSync)(childPath).isDirectory();
1326
+ } catch {
1327
+ continue;
1328
+ }
1329
+ const childExt = child.match(/([^.]+$)/)?.[0];
1330
+ if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
1331
+ result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
1332
+ }
1333
+ }
1334
+ continue;
1335
+ }
1336
+ result.push({ entry: name, entryPath: fullPath, isDir: true });
1337
+ }
1338
+ return result;
1339
+ };
1251
1340
  var findSeasonFolder = (showPath, season) => {
1252
1341
  if (!(0, import_fs10.existsSync)(showPath)) return null;
1253
1342
  const folders = (0, import_fs10.readdirSync)(showPath).filter((f) => {
@@ -1376,24 +1465,16 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
1376
1465
  continue;
1377
1466
  }
1378
1467
  spinner_default.text = `scanning ${import_termkit10.Color.white.encoder(source)}`;
1379
- for (const entry of (0, import_fs10.readdirSync)(source)) {
1380
- const entryPath = (0, import_path11.resolve)(source, entry);
1381
- const isDir = (0, import_fs10.lstatSync)(entryPath).isDirectory();
1468
+ for (const { entry, entryPath, isDir } of gatherEntries(source)) {
1382
1469
  const ext = entry.match(/([^.]+$)/)?.[0];
1383
- const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
1384
1470
  const isBook = !isDir && ext && bookExtensions_default.includes(ext);
1385
1471
  const isBookDir = isDir && containsBook(entryPath);
1386
- if (!isDir && !isVideo && !isBook) {
1387
- if (verbose) spinner_default.info(`skipped ${entry}`);
1388
- skipped++;
1389
- continue;
1390
- }
1391
1472
  let detectedType;
1392
1473
  if (type) {
1393
1474
  detectedType = type;
1394
1475
  } else if (isBook || isBookDir) {
1395
1476
  detectedType = "book";
1396
- } else if (isDir && /(?<=\[).+?(?=\])/.test(entry)) {
1477
+ } else if (isDir && /^[A-Z]{4}\d{5}/i.test(entry)) {
1397
1478
  detectedType = "ps3";
1398
1479
  } else if (/S\d{2,3}E\d{2,3}/i.test(entry) || /\d+x\d{2,3}/i.test(entry)) {
1399
1480
  detectedType = "tv";
@@ -1633,7 +1714,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
1633
1714
  }
1634
1715
  }
1635
1716
  }
1636
- spinner_default.succeed(`imported ${imported} items`);
1717
+ spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
1637
1718
  if (skipped) spinner_default.info(`skipped ${skipped} items`);
1638
1719
  spinner_default.stop();
1639
1720
  };
@@ -1688,10 +1769,86 @@ var findVideo2 = (dir) => (0, import_fs12.readdirSync)(dir).find((f) => {
1688
1769
  const ext = f.match(/([^.]+$)/)?.[0];
1689
1770
  return ext && videoExtensions_default.includes(ext);
1690
1771
  }) ?? null;
1691
- var containsBook2 = (dir) => (0, import_fs12.readdirSync)(dir).some((f) => {
1772
+ var containsBook2 = (dir, depth = 2) => (0, import_fs12.readdirSync)(dir).some((f) => {
1692
1773
  const ext = f.match(/([^.]+$)/)?.[0];
1693
- return ext && bookExtensions_default.includes(ext);
1774
+ if (ext && bookExtensions_default.includes(ext)) return true;
1775
+ if (depth > 1) {
1776
+ try {
1777
+ const sub = (0, import_path12.resolve)(dir, f);
1778
+ if ((0, import_fs12.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
1779
+ } catch {
1780
+ }
1781
+ }
1782
+ return false;
1694
1783
  });
1784
+ var isTvEpisodeName2 = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
1785
+ var isSeasonDirName2 = (name) => !isTvEpisodeName2(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
1786
+ var expandWatchPath = (p) => {
1787
+ let isDir;
1788
+ try {
1789
+ isDir = (0, import_fs12.lstatSync)(p).isDirectory();
1790
+ } catch {
1791
+ return [p];
1792
+ }
1793
+ if (!isDir) return [p];
1794
+ const name = (0, import_path12.basename)(p);
1795
+ if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
1796
+ let children;
1797
+ try {
1798
+ children = (0, import_fs12.readdirSync)(p);
1799
+ } catch {
1800
+ return [p];
1801
+ }
1802
+ if (children.some((c) => isTvEpisodeName2(c))) {
1803
+ const entries = [];
1804
+ for (const child of children) {
1805
+ const cp = (0, import_path12.resolve)(p, child);
1806
+ let cd;
1807
+ try {
1808
+ cd = (0, import_fs12.lstatSync)(cp).isDirectory();
1809
+ } catch {
1810
+ continue;
1811
+ }
1812
+ const ext = child.match(/([^.]+$)/)?.[0];
1813
+ if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
1814
+ entries.push(cp);
1815
+ }
1816
+ return entries.length > 0 ? entries : [p];
1817
+ }
1818
+ const seasonDirs = children.filter((c) => {
1819
+ try {
1820
+ return isSeasonDirName2(c) && (0, import_fs12.lstatSync)((0, import_path12.resolve)(p, c)).isDirectory();
1821
+ } catch {
1822
+ return false;
1823
+ }
1824
+ });
1825
+ if (seasonDirs.length > 0) {
1826
+ const entries = [];
1827
+ for (const sd of seasonDirs) {
1828
+ const sp = (0, import_path12.resolve)(p, sd);
1829
+ let sc;
1830
+ try {
1831
+ sc = (0, import_fs12.readdirSync)(sp);
1832
+ } catch {
1833
+ continue;
1834
+ }
1835
+ for (const child of sc) {
1836
+ const cp = (0, import_path12.resolve)(sp, child);
1837
+ let cd;
1838
+ try {
1839
+ cd = (0, import_fs12.lstatSync)(cp).isDirectory();
1840
+ } catch {
1841
+ continue;
1842
+ }
1843
+ const ext = child.match(/([^.]+$)/)?.[0];
1844
+ if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
1845
+ entries.push(cp);
1846
+ }
1847
+ }
1848
+ return entries.length > 0 ? entries : [p];
1849
+ }
1850
+ return [p];
1851
+ };
1695
1852
  var findSeasonFolder2 = (showPath, season) => {
1696
1853
  if (!(0, import_fs12.existsSync)(showPath)) return null;
1697
1854
  const folders = (0, import_fs12.readdirSync)(showPath).filter((f) => {
@@ -1911,7 +2068,9 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
1911
2068
  setTimeout(async () => {
1912
2069
  pending.delete(path);
1913
2070
  try {
1914
- await processItem(path, hardlink, verbose, language, auto);
2071
+ for (const entry of expandWatchPath(path)) {
2072
+ await processItem(entry, hardlink, verbose, language, auto);
2073
+ }
1915
2074
  } catch (err) {
1916
2075
  spinner_default.fail(`error processing ${path}: ${err.message}`);
1917
2076
  }
package/dist/index.mjs CHANGED
@@ -1168,10 +1168,99 @@ var findVideo = (dir) => readdirSync7(dir).find((f) => {
1168
1168
  const ext = f.match(/([^.]+$)/)?.[0];
1169
1169
  return ext && videoExtensions_default.includes(ext);
1170
1170
  }) ?? null;
1171
- var containsBook = (dir) => readdirSync7(dir).some((f) => {
1171
+ var containsBook = (dir, depth = 2) => readdirSync7(dir).some((f) => {
1172
1172
  const ext = f.match(/([^.]+$)/)?.[0];
1173
- return ext && bookExtensions_default.includes(ext);
1173
+ if (ext && bookExtensions_default.includes(ext)) return true;
1174
+ if (depth > 1) {
1175
+ try {
1176
+ const sub = resolve8(dir, f);
1177
+ if (lstatSync4(sub).isDirectory()) return containsBook(sub, depth - 1);
1178
+ } catch {
1179
+ }
1180
+ }
1181
+ return false;
1174
1182
  });
1183
+ var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
1184
+ var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
1185
+ var gatherEntries = (source) => {
1186
+ const result = [];
1187
+ for (const name of readdirSync7(source)) {
1188
+ const fullPath = resolve8(source, name);
1189
+ let isDir;
1190
+ try {
1191
+ isDir = lstatSync4(fullPath).isDirectory();
1192
+ } catch {
1193
+ continue;
1194
+ }
1195
+ const ext = name.match(/([^.]+$)/)?.[0];
1196
+ const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
1197
+ const isBook = !isDir && ext && bookExtensions_default.includes(ext);
1198
+ if (!isDir && !isVideo && !isBook) continue;
1199
+ if (!isDir) {
1200
+ result.push({ entry: name, entryPath: fullPath, isDir: false });
1201
+ continue;
1202
+ }
1203
+ if (/(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name) || isTvEpisodeName(name)) {
1204
+ result.push({ entry: name, entryPath: fullPath, isDir: true });
1205
+ continue;
1206
+ }
1207
+ let children;
1208
+ try {
1209
+ children = readdirSync7(fullPath);
1210
+ } catch {
1211
+ result.push({ entry: name, entryPath: fullPath, isDir: true });
1212
+ continue;
1213
+ }
1214
+ if (children.some((c) => isTvEpisodeName(c))) {
1215
+ for (const child of children) {
1216
+ const childPath = resolve8(fullPath, child);
1217
+ let childIsDir;
1218
+ try {
1219
+ childIsDir = lstatSync4(childPath).isDirectory();
1220
+ } catch {
1221
+ continue;
1222
+ }
1223
+ const childExt = child.match(/([^.]+$)/)?.[0];
1224
+ if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
1225
+ result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
1226
+ }
1227
+ continue;
1228
+ }
1229
+ const seasonDirs = children.filter((c) => {
1230
+ try {
1231
+ return isSeasonDirName(c) && lstatSync4(resolve8(fullPath, c)).isDirectory();
1232
+ } catch {
1233
+ return false;
1234
+ }
1235
+ });
1236
+ if (seasonDirs.length > 0) {
1237
+ for (const seasonDir of seasonDirs) {
1238
+ const seasonPath = resolve8(fullPath, seasonDir);
1239
+ let seasonChildren;
1240
+ try {
1241
+ seasonChildren = readdirSync7(seasonPath);
1242
+ } catch {
1243
+ continue;
1244
+ }
1245
+ for (const child of seasonChildren) {
1246
+ const childPath = resolve8(seasonPath, child);
1247
+ let childIsDir;
1248
+ try {
1249
+ childIsDir = lstatSync4(childPath).isDirectory();
1250
+ } catch {
1251
+ continue;
1252
+ }
1253
+ const childExt = child.match(/([^.]+$)/)?.[0];
1254
+ if (!childIsDir && !(childExt && videoExtensions_default.includes(childExt))) continue;
1255
+ result.push({ entry: child, entryPath: childPath, isDir: childIsDir });
1256
+ }
1257
+ }
1258
+ continue;
1259
+ }
1260
+ result.push({ entry: name, entryPath: fullPath, isDir: true });
1261
+ }
1262
+ return result;
1263
+ };
1175
1264
  var findSeasonFolder = (showPath, season) => {
1176
1265
  if (!existsSync9(showPath)) return null;
1177
1266
  const folders = readdirSync7(showPath).filter((f) => {
@@ -1300,24 +1389,16 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
1300
1389
  continue;
1301
1390
  }
1302
1391
  spinner_default.text = `scanning ${Color9.white.encoder(source)}`;
1303
- for (const entry of readdirSync7(source)) {
1304
- const entryPath = resolve8(source, entry);
1305
- const isDir = lstatSync4(entryPath).isDirectory();
1392
+ for (const { entry, entryPath, isDir } of gatherEntries(source)) {
1306
1393
  const ext = entry.match(/([^.]+$)/)?.[0];
1307
- const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
1308
1394
  const isBook = !isDir && ext && bookExtensions_default.includes(ext);
1309
1395
  const isBookDir = isDir && containsBook(entryPath);
1310
- if (!isDir && !isVideo && !isBook) {
1311
- if (verbose) spinner_default.info(`skipped ${entry}`);
1312
- skipped++;
1313
- continue;
1314
- }
1315
1396
  let detectedType;
1316
1397
  if (type) {
1317
1398
  detectedType = type;
1318
1399
  } else if (isBook || isBookDir) {
1319
1400
  detectedType = "book";
1320
- } else if (isDir && /(?<=\[).+?(?=\])/.test(entry)) {
1401
+ } else if (isDir && /^[A-Z]{4}\d{5}/i.test(entry)) {
1321
1402
  detectedType = "ps3";
1322
1403
  } else if (/S\d{2,3}E\d{2,3}/i.test(entry) || /\d+x\d{2,3}/i.test(entry)) {
1323
1404
  detectedType = "tv";
@@ -1557,7 +1638,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, i
1557
1638
  }
1558
1639
  }
1559
1640
  }
1560
- spinner_default.succeed(`imported ${imported} items`);
1641
+ spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
1561
1642
  if (skipped) spinner_default.info(`skipped ${skipped} items`);
1562
1643
  spinner_default.stop();
1563
1644
  };
@@ -1612,10 +1693,86 @@ var findVideo2 = (dir) => readdirSync8(dir).find((f) => {
1612
1693
  const ext = f.match(/([^.]+$)/)?.[0];
1613
1694
  return ext && videoExtensions_default.includes(ext);
1614
1695
  }) ?? null;
1615
- var containsBook2 = (dir) => readdirSync8(dir).some((f) => {
1696
+ var containsBook2 = (dir, depth = 2) => readdirSync8(dir).some((f) => {
1616
1697
  const ext = f.match(/([^.]+$)/)?.[0];
1617
- return ext && bookExtensions_default.includes(ext);
1698
+ if (ext && bookExtensions_default.includes(ext)) return true;
1699
+ if (depth > 1) {
1700
+ try {
1701
+ const sub = resolve9(dir, f);
1702
+ if (lstatSync5(sub).isDirectory()) return containsBook2(sub, depth - 1);
1703
+ } catch {
1704
+ }
1705
+ }
1706
+ return false;
1618
1707
  });
1708
+ var isTvEpisodeName2 = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
1709
+ var isSeasonDirName2 = (name) => !isTvEpisodeName2(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
1710
+ var expandWatchPath = (p) => {
1711
+ let isDir;
1712
+ try {
1713
+ isDir = lstatSync5(p).isDirectory();
1714
+ } catch {
1715
+ return [p];
1716
+ }
1717
+ if (!isDir) return [p];
1718
+ const name = basename3(p);
1719
+ if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
1720
+ let children;
1721
+ try {
1722
+ children = readdirSync8(p);
1723
+ } catch {
1724
+ return [p];
1725
+ }
1726
+ if (children.some((c) => isTvEpisodeName2(c))) {
1727
+ const entries = [];
1728
+ for (const child of children) {
1729
+ const cp = resolve9(p, child);
1730
+ let cd;
1731
+ try {
1732
+ cd = lstatSync5(cp).isDirectory();
1733
+ } catch {
1734
+ continue;
1735
+ }
1736
+ const ext = child.match(/([^.]+$)/)?.[0];
1737
+ if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
1738
+ entries.push(cp);
1739
+ }
1740
+ return entries.length > 0 ? entries : [p];
1741
+ }
1742
+ const seasonDirs = children.filter((c) => {
1743
+ try {
1744
+ return isSeasonDirName2(c) && lstatSync5(resolve9(p, c)).isDirectory();
1745
+ } catch {
1746
+ return false;
1747
+ }
1748
+ });
1749
+ if (seasonDirs.length > 0) {
1750
+ const entries = [];
1751
+ for (const sd of seasonDirs) {
1752
+ const sp = resolve9(p, sd);
1753
+ let sc;
1754
+ try {
1755
+ sc = readdirSync8(sp);
1756
+ } catch {
1757
+ continue;
1758
+ }
1759
+ for (const child of sc) {
1760
+ const cp = resolve9(sp, child);
1761
+ let cd;
1762
+ try {
1763
+ cd = lstatSync5(cp).isDirectory();
1764
+ } catch {
1765
+ continue;
1766
+ }
1767
+ const ext = child.match(/([^.]+$)/)?.[0];
1768
+ if (!cd && !(ext && videoExtensions_default.includes(ext))) continue;
1769
+ entries.push(cp);
1770
+ }
1771
+ }
1772
+ return entries.length > 0 ? entries : [p];
1773
+ }
1774
+ return [p];
1775
+ };
1619
1776
  var findSeasonFolder2 = (showPath, season) => {
1620
1777
  if (!existsSync10(showPath)) return null;
1621
1778
  const folders = readdirSync8(showPath).filter((f) => {
@@ -1835,7 +1992,9 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
1835
1992
  setTimeout(async () => {
1836
1993
  pending.delete(path);
1837
1994
  try {
1838
- await processItem(path, hardlink, verbose, language, auto);
1995
+ for (const entry of expandWatchPath(path)) {
1996
+ await processItem(entry, hardlink, verbose, language, auto);
1997
+ }
1839
1998
  } catch (err) {
1840
1999
  spinner_default.fail(`error processing ${path}: ${err.message}`);
1841
2000
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reelsort",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "CLI to rename, organize, and manage your movie and TV library — Plex-compatible naming, hardlink support, and automated watch mode.",
5
5
  "keywords": [
6
6
  "media",