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/index.js CHANGED
@@ -1150,8 +1150,8 @@ var reset = async ({ dir: inputDir, double }) => {
1150
1150
  var reset_default = reset;
1151
1151
 
1152
1152
  // src/actions/scan.ts
1153
- var import_fs10 = require("fs");
1154
- var import_path11 = require("path");
1153
+ var import_fs11 = require("fs");
1154
+ var import_path12 = require("path");
1155
1155
  var import_termkit10 = require("termkit");
1156
1156
 
1157
1157
  // src/helpers/detectEdition.ts
@@ -1175,10 +1175,49 @@ var detectEdition = (filename) => {
1175
1175
  // src/helpers/hyperlink.ts
1176
1176
  var hyperlink = (url, text) => `\x1B]8;;${url}\x1B\\${text}\x1B]8;;\x1B\\`;
1177
1177
 
1178
+ // src/helpers/trash.ts
1179
+ var import_child_process2 = require("child_process");
1180
+ var import_fs10 = require("fs");
1181
+ var import_os3 = require("os");
1182
+ var import_path11 = require("path");
1183
+ var trashDir = () => {
1184
+ if (process.platform === "linux") return (0, import_path11.join)((0, import_os3.homedir)(), ".local", "share", "Trash", "files");
1185
+ return (0, import_path11.join)((0, import_os3.homedir)(), ".Trash");
1186
+ };
1187
+ var trashPath = (filePath, opts) => {
1188
+ if (process.platform === "darwin" || process.platform === "linux") {
1189
+ try {
1190
+ const dir = trashDir();
1191
+ if (!(0, import_fs10.existsSync)(dir)) (0, import_fs10.mkdirSync)(dir, { recursive: true });
1192
+ const name = (0, import_path11.basename)(filePath);
1193
+ let dest = (0, import_path11.join)(dir, name);
1194
+ let i = 1;
1195
+ while ((0, import_fs10.existsSync)(dest)) dest = (0, import_path11.join)(dir, `${name} ${i++}`);
1196
+ (0, import_fs10.renameSync)(filePath, dest);
1197
+ return;
1198
+ } catch {
1199
+ }
1200
+ } else if (process.platform === "win32") {
1201
+ try {
1202
+ const escaped = filePath.replace(/'/g, "''");
1203
+ const isDir = opts?.recursive ?? false;
1204
+ const method = isDir ? "DeleteDirectory" : "DeleteFile";
1205
+ (0, import_child_process2.execSync)(
1206
+ `powershell -NoProfile -NonInteractive -Command "Add-Type -AssemblyName Microsoft.VisualBasic; [Microsoft.VisualBasic.FileIO.FileSystem]::${method}('${escaped}', 'OnlyErrorDialogs', 'SendToRecycleBin')"`,
1207
+ { stdio: "ignore" }
1208
+ );
1209
+ return;
1210
+ } catch {
1211
+ }
1212
+ }
1213
+ (0, import_fs10.rmSync)(filePath, { recursive: opts?.recursive ?? false, force: true });
1214
+ };
1215
+
1178
1216
  // src/helpers/parseDownloadName.ts
1179
1217
  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"]);
1180
1218
  var TV_PATTERN = /^(.*?)[.\s_-]*(?:S(\d{2,3})E(\d{2,3})|(\d{1,2})x(\d{2,3})|Season[\s.](\d+))/i;
1181
- var parseDownloadName = (name) => {
1219
+ var parseDownloadName = (rawName) => {
1220
+ const name = rawName.replace(/^[\w.-]+\.\w{2,6}\s+-+\s+/i, "");
1182
1221
  const base = name.replace(/\.[a-z0-9]{2,4}$/i, "");
1183
1222
  const tvMatch = TV_PATTERN.exec(base);
1184
1223
  if (tvMatch) {
@@ -1277,45 +1316,63 @@ var bookExtensions_default = ["epub", "mobi", "azw3", "azw"];
1277
1316
  var sameDev = (a, b) => {
1278
1317
  try {
1279
1318
  let bExisting = b;
1280
- while (!(0, import_fs10.existsSync)(bExisting)) bExisting = (0, import_path11.dirname)(bExisting);
1281
- return (0, import_fs10.statSync)(a).dev === (0, import_fs10.statSync)(bExisting).dev;
1319
+ while (!(0, import_fs11.existsSync)(bExisting)) bExisting = (0, import_path12.dirname)(bExisting);
1320
+ return (0, import_fs11.statSync)(a).dev === (0, import_fs11.statSync)(bExisting).dev;
1282
1321
  } catch {
1283
1322
  return false;
1284
1323
  }
1285
1324
  };
1286
1325
  var moveFolder = (src, dest) => {
1287
1326
  if (sameDev(src, dest)) {
1288
- (0, import_fs10.renameSync)(src, dest);
1327
+ (0, import_fs11.renameSync)(src, dest);
1289
1328
  } else {
1290
- (0, import_fs10.cpSync)(src, dest, { recursive: true });
1291
- (0, import_fs10.rmSync)(src, { recursive: true, force: true });
1329
+ (0, import_fs11.cpSync)(src, dest, { recursive: true });
1330
+ trashPath(src, { recursive: true });
1292
1331
  }
1293
1332
  };
1294
- var findVideo = (dir) => (0, import_fs10.readdirSync)(dir).find((f) => {
1333
+ var findVideo = (dir) => (0, import_fs11.readdirSync)(dir).find((f) => {
1295
1334
  const ext = f.match(/([^.]+$)/)?.[0];
1296
1335
  return ext && videoExtensions_default.includes(ext);
1297
1336
  }) ?? null;
1298
- var containsBook = (dir, depth = 2) => (0, import_fs10.readdirSync)(dir).some((f) => {
1337
+ var containsBook = (dir, depth = 2) => (0, import_fs11.readdirSync)(dir).some((f) => {
1299
1338
  const ext = f.match(/([^.]+$)/)?.[0];
1300
1339
  if (ext && bookExtensions_default.includes(ext)) return true;
1301
1340
  if (depth > 1) {
1302
1341
  try {
1303
- const sub = (0, import_path11.resolve)(dir, f);
1304
- if ((0, import_fs10.lstatSync)(sub).isDirectory()) return containsBook(sub, depth - 1);
1342
+ const sub = (0, import_path12.resolve)(dir, f);
1343
+ if ((0, import_fs11.lstatSync)(sub).isDirectory()) return containsBook(sub, depth - 1);
1305
1344
  } catch {
1306
1345
  }
1307
1346
  }
1308
1347
  return false;
1309
1348
  });
1349
+ var containsPdf = (dir) => {
1350
+ try {
1351
+ return (0, import_fs11.readdirSync)(dir).some((f) => /\.pdf$/i.test(f));
1352
+ } catch {
1353
+ return false;
1354
+ }
1355
+ };
1356
+ var countVideos = (dir) => {
1357
+ try {
1358
+ return (0, import_fs11.readdirSync)(dir).filter((f) => {
1359
+ if (/\bsample\b/i.test(f)) return false;
1360
+ const ext = f.match(/([^.]+$)/)?.[0];
1361
+ return !!(ext && videoExtensions_default.includes(ext));
1362
+ }).length;
1363
+ } catch {
1364
+ return 0;
1365
+ }
1366
+ };
1310
1367
  var isTvEpisodeName = (name) => /S\d{2,3}E\d{2,3}/i.test(name) || /\d+x\d{2,3}/i.test(name);
1311
1368
  var isSeasonDirName = (name) => !isTvEpisodeName(name) && /(?:^|[.\s_-])(?:season|s)\s*0*\d+(?:[.\s_-]|$)/i.test(name);
1312
1369
  var gatherEntries = (source) => {
1313
1370
  const result = [];
1314
- for (const name of (0, import_fs10.readdirSync)(source)) {
1315
- const fullPath = (0, import_path11.resolve)(source, name);
1371
+ for (const name of (0, import_fs11.readdirSync)(source)) {
1372
+ const fullPath = (0, import_path12.resolve)(source, name);
1316
1373
  let isDir;
1317
1374
  try {
1318
- isDir = (0, import_fs10.lstatSync)(fullPath).isDirectory();
1375
+ isDir = (0, import_fs11.lstatSync)(fullPath).isDirectory();
1319
1376
  } catch {
1320
1377
  continue;
1321
1378
  }
@@ -1333,17 +1390,17 @@ var gatherEntries = (source) => {
1333
1390
  }
1334
1391
  let children;
1335
1392
  try {
1336
- children = (0, import_fs10.readdirSync)(fullPath);
1393
+ children = (0, import_fs11.readdirSync)(fullPath);
1337
1394
  } catch {
1338
1395
  result.push({ entry: name, entryPath: fullPath, isDir: true });
1339
1396
  continue;
1340
1397
  }
1341
1398
  if (children.some((c) => isTvEpisodeName(c))) {
1342
1399
  for (const child of children) {
1343
- const childPath = (0, import_path11.resolve)(fullPath, child);
1400
+ const childPath = (0, import_path12.resolve)(fullPath, child);
1344
1401
  let childIsDir;
1345
1402
  try {
1346
- childIsDir = (0, import_fs10.lstatSync)(childPath).isDirectory();
1403
+ childIsDir = (0, import_fs11.lstatSync)(childPath).isDirectory();
1347
1404
  } catch {
1348
1405
  continue;
1349
1406
  }
@@ -1355,25 +1412,25 @@ var gatherEntries = (source) => {
1355
1412
  }
1356
1413
  const seasonDirs = children.filter((c) => {
1357
1414
  try {
1358
- return isSeasonDirName(c) && (0, import_fs10.lstatSync)((0, import_path11.resolve)(fullPath, c)).isDirectory();
1415
+ return isSeasonDirName(c) && (0, import_fs11.lstatSync)((0, import_path12.resolve)(fullPath, c)).isDirectory();
1359
1416
  } catch {
1360
1417
  return false;
1361
1418
  }
1362
1419
  });
1363
1420
  if (seasonDirs.length > 0) {
1364
1421
  for (const seasonDir of seasonDirs) {
1365
- const seasonPath = (0, import_path11.resolve)(fullPath, seasonDir);
1422
+ const seasonPath = (0, import_path12.resolve)(fullPath, seasonDir);
1366
1423
  let seasonChildren;
1367
1424
  try {
1368
- seasonChildren = (0, import_fs10.readdirSync)(seasonPath);
1425
+ seasonChildren = (0, import_fs11.readdirSync)(seasonPath);
1369
1426
  } catch {
1370
1427
  continue;
1371
1428
  }
1372
1429
  for (const child of seasonChildren) {
1373
- const childPath = (0, import_path11.resolve)(seasonPath, child);
1430
+ const childPath = (0, import_path12.resolve)(seasonPath, child);
1374
1431
  let childIsDir;
1375
1432
  try {
1376
- childIsDir = (0, import_fs10.lstatSync)(childPath).isDirectory();
1433
+ childIsDir = (0, import_fs11.lstatSync)(childPath).isDirectory();
1377
1434
  } catch {
1378
1435
  continue;
1379
1436
  }
@@ -1389,19 +1446,19 @@ var gatherEntries = (source) => {
1389
1446
  return result;
1390
1447
  };
1391
1448
  var findShowFolder = (destRoot, title) => {
1392
- if (!(0, import_fs10.existsSync)(destRoot)) return null;
1449
+ if (!(0, import_fs11.existsSync)(destRoot)) return null;
1393
1450
  const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1394
1451
  const target = normalize(title);
1395
- return (0, import_fs10.readdirSync)(destRoot).filter((f) => {
1452
+ return (0, import_fs11.readdirSync)(destRoot).filter((f) => {
1396
1453
  try {
1397
- return (0, import_fs10.lstatSync)((0, import_path11.resolve)(destRoot, f)).isDirectory();
1454
+ return (0, import_fs11.lstatSync)((0, import_path12.resolve)(destRoot, f)).isDirectory();
1398
1455
  } catch {
1399
1456
  return false;
1400
1457
  }
1401
1458
  }).find((f) => normalize(f) === target) ?? null;
1402
1459
  };
1403
1460
  var findShowFolderByContent = (destRoot, title) => {
1404
- if (!(0, import_fs10.existsSync)(destRoot)) return null;
1461
+ if (!(0, import_fs11.existsSync)(destRoot)) return null;
1405
1462
  const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1406
1463
  const target = normalize(title);
1407
1464
  const matchesTitle = (name) => {
@@ -1409,18 +1466,18 @@ var findShowFolderByContent = (destRoot, title) => {
1409
1466
  const p = parseDownloadName(name);
1410
1467
  return !!p && normalize(p.title) === target;
1411
1468
  };
1412
- for (const folder of (0, import_fs10.readdirSync)(destRoot)) {
1469
+ for (const folder of (0, import_fs11.readdirSync)(destRoot)) {
1413
1470
  try {
1414
- const folderPath = (0, import_path11.resolve)(destRoot, folder);
1415
- if (!(0, import_fs10.lstatSync)(folderPath).isDirectory()) continue;
1416
- const children = (0, import_fs10.readdirSync)(folderPath);
1471
+ const folderPath = (0, import_path12.resolve)(destRoot, folder);
1472
+ if (!(0, import_fs11.lstatSync)(folderPath).isDirectory()) continue;
1473
+ const children = (0, import_fs11.readdirSync)(folderPath);
1417
1474
  if (children.some(matchesTitle)) return folder;
1418
1475
  for (const child of children) {
1419
1476
  if (!isSeasonDirName(child)) continue;
1420
1477
  try {
1421
- const seasonPath = (0, import_path11.resolve)(folderPath, child);
1422
- if (!(0, import_fs10.lstatSync)(seasonPath).isDirectory()) continue;
1423
- if ((0, import_fs10.readdirSync)(seasonPath).some(matchesTitle)) return folder;
1478
+ const seasonPath = (0, import_path12.resolve)(folderPath, child);
1479
+ if (!(0, import_fs11.lstatSync)(seasonPath).isDirectory()) continue;
1480
+ if ((0, import_fs11.readdirSync)(seasonPath).some(matchesTitle)) return folder;
1424
1481
  } catch {
1425
1482
  }
1426
1483
  }
@@ -1429,15 +1486,19 @@ var findShowFolderByContent = (destRoot, title) => {
1429
1486
  }
1430
1487
  return null;
1431
1488
  };
1432
- var findSeasonFolder = (showPath, season) => {
1433
- if (!(0, import_fs10.existsSync)(showPath)) return null;
1434
- const folders = (0, import_fs10.readdirSync)(showPath).filter((f) => {
1489
+ var findSeasonFolder = (showPath, season, specialsFolder) => {
1490
+ if (!(0, import_fs11.existsSync)(showPath)) return null;
1491
+ const folders = (0, import_fs11.readdirSync)(showPath).filter((f) => {
1435
1492
  try {
1436
- return (0, import_fs10.lstatSync)((0, import_path11.resolve)(showPath, f)).isDirectory();
1493
+ return (0, import_fs11.lstatSync)((0, import_path12.resolve)(showPath, f)).isDirectory();
1437
1494
  } catch {
1438
1495
  return false;
1439
1496
  }
1440
1497
  });
1498
+ if (season === 0 && specialsFolder) {
1499
+ const existing = folders.find((f) => f.toLowerCase() === specialsFolder.toLowerCase());
1500
+ if (existing) return existing;
1501
+ }
1441
1502
  return folders.find((f) => {
1442
1503
  const match = f.match(/(?:season|s)\s*0*(\d+)/i);
1443
1504
  return match && parseInt(match[1]) === season;
@@ -1466,11 +1527,13 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1466
1527
  const language = config.language ?? "eng";
1467
1528
  const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
1468
1529
  const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
1530
+ const specialsFolder = config.specialsFolder ?? "Specials";
1469
1531
  const lookupMovie = async (parsed) => {
1470
1532
  let tmdbId;
1471
1533
  let resolvedTitle = parsed.title;
1472
1534
  let resolvedYear = parsed.year;
1473
1535
  if (config.tmdbApiKey) {
1536
+ spinner_default.text = `TMDb: ${parsed.title}`;
1474
1537
  const results = await searchMovie(parsed.title, parsed.year, config.tmdbApiKey);
1475
1538
  if (results.length === 1) {
1476
1539
  tmdbId = results[0].id;
@@ -1498,8 +1561,8 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1498
1561
  const importMovie = async (entry, entryPath, isDir, resolvedTitle, resolvedYear, tmdbId, destRoot) => {
1499
1562
  const edition = detectEdition(entry);
1500
1563
  const folderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear, edition);
1501
- const destFolder = (0, import_path11.resolve)(destRoot, folderName);
1502
- if ((0, import_fs10.existsSync)(destFolder)) {
1564
+ const destFolder = (0, import_path12.resolve)(destRoot, folderName);
1565
+ if ((0, import_fs11.existsSync)(destFolder)) {
1503
1566
  spinner_default.warn(`already exists: ${folderName}`);
1504
1567
  return false;
1505
1568
  }
@@ -1510,43 +1573,43 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1510
1573
  }
1511
1574
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
1512
1575
  const destVideoName = `${folderName}.${videoExt}`;
1513
- const videoSourcePath = isDir ? (0, import_path11.resolve)(entryPath, videoFile) : entryPath;
1514
- const dirFiles = isDir ? (0, import_fs10.readdirSync)(entryPath) : [];
1576
+ const videoSourcePath = isDir ? (0, import_path12.resolve)(entryPath, videoFile) : entryPath;
1577
+ const dirFiles = isDir ? (0, import_fs11.readdirSync)(entryPath) : [];
1515
1578
  const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
1516
1579
  const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
1517
- const subtitleSourcePath = subtitle ? (0, import_path11.resolve)(entryPath, subtitle) : null;
1580
+ const subtitleSourcePath = subtitle ? (0, import_path12.resolve)(entryPath, subtitle) : null;
1518
1581
  const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
1519
1582
  if (!dryRun) {
1520
1583
  if (useHardlink) {
1521
- (0, import_fs10.mkdirSync)(destFolder, { recursive: true });
1522
- const destVideoPath = (0, import_path11.resolve)(destFolder, destVideoName);
1584
+ (0, import_fs11.mkdirSync)(destFolder, { recursive: true });
1585
+ const destVideoPath = (0, import_path12.resolve)(destFolder, destVideoName);
1523
1586
  let mode;
1524
1587
  try {
1525
1588
  if (!sameDev(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
1526
- (0, import_fs10.linkSync)(videoSourcePath, destVideoPath);
1589
+ (0, import_fs11.linkSync)(videoSourcePath, destVideoPath);
1527
1590
  mode = "hardlink";
1528
1591
  } catch {
1529
1592
  spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
1530
- (0, import_fs10.cpSync)(videoSourcePath, destVideoPath);
1593
+ (0, import_fs11.cpSync)(videoSourcePath, destVideoPath);
1531
1594
  mode = "copy";
1532
1595
  }
1533
- if (subtitleSourcePath && destSubtitleName) (0, import_fs10.cpSync)(subtitleSourcePath, (0, import_path11.resolve)(destFolder, destSubtitleName));
1596
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs11.cpSync)(subtitleSourcePath, (0, import_path12.resolve)(destFolder, destSubtitleName));
1534
1597
  recordImport(sessionId, entryPath, destFolder, mode, tmdbId, "movie");
1535
1598
  } else {
1536
1599
  if (isDir) {
1537
1600
  const keep = new Set([videoFile, subtitle].filter(Boolean));
1538
- for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs10.rmSync)((0, import_path11.resolve)(entryPath, f), { recursive: true, force: true });
1539
- (0, import_fs10.renameSync)(videoSourcePath, (0, import_path11.resolve)(entryPath, destVideoName));
1540
- if (subtitleSourcePath && destSubtitleName) (0, import_fs10.renameSync)(subtitleSourcePath, (0, import_path11.resolve)(entryPath, destSubtitleName));
1601
+ for (const f of dirFiles.filter((f2) => !keep.has(f2))) trashPath((0, import_path12.resolve)(entryPath, f), { recursive: true });
1602
+ (0, import_fs11.renameSync)(videoSourcePath, (0, import_path12.resolve)(entryPath, destVideoName));
1603
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs11.renameSync)(subtitleSourcePath, (0, import_path12.resolve)(entryPath, destSubtitleName));
1541
1604
  moveFolder(entryPath, destFolder);
1542
1605
  } else {
1543
- (0, import_fs10.mkdirSync)(destFolder, { recursive: true });
1544
- const destVideoPath = (0, import_path11.resolve)(destFolder, destVideoName);
1606
+ (0, import_fs11.mkdirSync)(destFolder, { recursive: true });
1607
+ const destVideoPath = (0, import_path12.resolve)(destFolder, destVideoName);
1545
1608
  if (sameDev(videoSourcePath, destRoot)) {
1546
- (0, import_fs10.renameSync)(videoSourcePath, destVideoPath);
1609
+ (0, import_fs11.renameSync)(videoSourcePath, destVideoPath);
1547
1610
  } else {
1548
- (0, import_fs10.cpSync)(videoSourcePath, destVideoPath);
1549
- (0, import_fs10.rmSync)(videoSourcePath);
1611
+ (0, import_fs11.cpSync)(videoSourcePath, destVideoPath);
1612
+ trashPath(videoSourcePath);
1550
1613
  }
1551
1614
  }
1552
1615
  recordImport(sessionId, entryPath, destFolder, "move", tmdbId, "movie");
@@ -1560,15 +1623,18 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1560
1623
  let imported = 0, skipped = 0;
1561
1624
  const pendingMovies = [];
1562
1625
  const pendingTv = [];
1626
+ const pendingBooks = [];
1627
+ const pendingAnime = [];
1563
1628
  const ignoreSet = new Set(config.ignore ?? []);
1564
1629
  const seenIgnored = /* @__PURE__ */ new Set();
1565
1630
  for (const source of config.sources) {
1566
- if (!(0, import_fs10.existsSync)(source)) {
1631
+ if (!(0, import_fs11.existsSync)(source)) {
1567
1632
  spinner_default.warn(`source not found: ${import_termkit10.Color.white.encoder(source)}`);
1568
1633
  continue;
1569
1634
  }
1570
1635
  spinner_default.text = `scanning ${import_termkit10.Color.white.encoder(source)}`;
1571
1636
  for (const { entry, entryPath, isDir } of gatherEntries(source)) {
1637
+ spinner_default.text = `scanning: ${entry}`;
1572
1638
  if (ignoreSet.has(entry)) {
1573
1639
  seenIgnored.add(entry);
1574
1640
  if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
@@ -1603,8 +1669,8 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1603
1669
  continue;
1604
1670
  }
1605
1671
  const destName = `${nameMatch[0]} [${id}]`;
1606
- const destPath = (0, import_path11.resolve)(destRoot, destName);
1607
- if ((0, import_fs10.existsSync)(destPath)) {
1672
+ const destPath = (0, import_path12.resolve)(destRoot, destName);
1673
+ if ((0, import_fs11.existsSync)(destPath)) {
1608
1674
  spinner_default.warn(`already exists: ${destName}`);
1609
1675
  skipped++;
1610
1676
  continue;
@@ -1618,8 +1684,8 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1618
1684
  continue;
1619
1685
  }
1620
1686
  if (detectedType === "book") {
1621
- const destPath = (0, import_path11.resolve)(destRoot, entry);
1622
- if ((0, import_fs10.existsSync)(destPath)) {
1687
+ const destPath = (0, import_path12.resolve)(destRoot, entry);
1688
+ if ((0, import_fs11.existsSync)(destPath)) {
1623
1689
  spinner_default.warn(`already exists: ${entry}`);
1624
1690
  skipped++;
1625
1691
  continue;
@@ -1628,12 +1694,12 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1628
1694
  if (isDir || isBookDir) {
1629
1695
  moveFolder(entryPath, destPath);
1630
1696
  } else {
1631
- (0, import_fs10.mkdirSync)(destRoot, { recursive: true });
1697
+ (0, import_fs11.mkdirSync)(destRoot, { recursive: true });
1632
1698
  if (sameDev(entryPath, destRoot)) {
1633
- (0, import_fs10.renameSync)(entryPath, destPath);
1699
+ (0, import_fs11.renameSync)(entryPath, destPath);
1634
1700
  } else {
1635
- (0, import_fs10.cpSync)(entryPath, destPath);
1636
- (0, import_fs10.rmSync)(entryPath);
1701
+ (0, import_fs11.cpSync)(entryPath, destPath);
1702
+ (0, import_fs11.rmSync)(entryPath);
1637
1703
  }
1638
1704
  }
1639
1705
  recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
@@ -1642,6 +1708,22 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1642
1708
  imported++;
1643
1709
  continue;
1644
1710
  }
1711
+ if (detectedType === "movie" && isDir) {
1712
+ const videoCount = countVideos(entryPath);
1713
+ if (videoCount === 0) {
1714
+ if (containsPdf(entryPath)) {
1715
+ pendingBooks.push({ entry, entryPath });
1716
+ } else if (isVerbose()) {
1717
+ spinner_default.info(`no media found, skipped: ${entry}`);
1718
+ }
1719
+ skipped++;
1720
+ continue;
1721
+ }
1722
+ if (videoCount >= 2) {
1723
+ pendingAnime.push({ entry, entryPath, videoCount });
1724
+ continue;
1725
+ }
1726
+ }
1645
1727
  const parsed = parseDownloadName(entry);
1646
1728
  if (!parsed) {
1647
1729
  if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
@@ -1665,6 +1747,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1665
1747
  let resolvedYear = parsed.year;
1666
1748
  if (config.tmdbApiKey) {
1667
1749
  if (detectedType === "tv") {
1750
+ spinner_default.text = `TMDb: ${parsed.title}`;
1668
1751
  const results = await searchTv(parsed.title, config.tmdbApiKey);
1669
1752
  if (results.length === 1) {
1670
1753
  tmdbId = results[0].id;
@@ -1709,18 +1792,18 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1709
1792
  const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
1710
1793
  if (existingFolder) {
1711
1794
  showFolderName = existingFolder;
1712
- showPath = (0, import_path11.resolve)(destRoot, existingFolder);
1795
+ showPath = (0, import_path12.resolve)(destRoot, existingFolder);
1713
1796
  } else if (auto) {
1714
1797
  showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
1715
- showPath = (0, import_path11.resolve)(destRoot, showFolderName);
1798
+ showPath = (0, import_path12.resolve)(destRoot, showFolderName);
1716
1799
  if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
1717
1800
  } else {
1718
1801
  pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
1719
1802
  continue;
1720
1803
  }
1721
1804
  }
1722
- const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
1723
- const seasonPath = (0, import_path11.resolve)(showPath, seasonFolderName);
1805
+ const seasonFolderName = parsed.season === 0 ? findSeasonFolder(showPath, 0, specialsFolder) ?? specialsFolder : findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
1806
+ const seasonPath = (0, import_path12.resolve)(showPath, seasonFolderName);
1724
1807
  const videoFile = isDir ? findVideo(entryPath) : entry;
1725
1808
  if (!videoFile) {
1726
1809
  if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
@@ -1728,13 +1811,15 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1728
1811
  continue;
1729
1812
  }
1730
1813
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
1814
+ if (tmdbId && config.tmdbApiKey) spinner_default.text = `TMDb: episode name for ${resolvedTitle}`;
1731
1815
  const tmdbEpisodeName = tmdbId && config.tmdbApiKey ? await getEpisodeName(tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
1732
1816
  const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, resolvedTitle, tmdbEpisodeName ?? void 0);
1733
1817
  const destVideoName = `${episodeName}.${videoExt}`;
1734
- const destVideoPath = (0, import_path11.resolve)(seasonPath, destVideoName);
1735
- const videoSourcePath = isDir ? (0, import_path11.resolve)(entryPath, videoFile) : entryPath;
1736
- if ((0, import_fs10.existsSync)(destVideoPath)) {
1737
- let shouldReplace = force;
1818
+ const destVideoPath = (0, import_path12.resolve)(seasonPath, destVideoName);
1819
+ const videoSourcePath = isDir ? (0, import_path12.resolve)(entryPath, videoFile) : entryPath;
1820
+ if ((0, import_fs11.existsSync)(destVideoPath)) {
1821
+ const isRepack = /\brepack\d*\b|\bproper\b/i.test(entry);
1822
+ let shouldReplace = force || isRepack;
1738
1823
  if (!shouldReplace && interactive) {
1739
1824
  spinner_default.stop();
1740
1825
  const select = new import_termkit10.Select();
@@ -1751,39 +1836,39 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1751
1836
  continue;
1752
1837
  }
1753
1838
  if (!dryRun) {
1754
- for (const f of (0, import_fs10.readdirSync)(seasonPath)) {
1755
- if (f.startsWith(`${episodeName}.`)) (0, import_fs10.rmSync)((0, import_path11.resolve)(seasonPath, f));
1839
+ for (const f of (0, import_fs11.readdirSync)(seasonPath)) {
1840
+ if (f.startsWith(`${episodeName}.`)) trashPath((0, import_path12.resolve)(seasonPath, f));
1756
1841
  }
1757
1842
  }
1758
1843
  }
1759
- const dirFiles = isDir ? (0, import_fs10.readdirSync)(entryPath) : [];
1844
+ const dirFiles = isDir ? (0, import_fs11.readdirSync)(entryPath) : [];
1760
1845
  const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
1761
1846
  const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
1762
- const subtitleSourcePath = subtitle ? (0, import_path11.resolve)(entryPath, subtitle) : null;
1847
+ const subtitleSourcePath = subtitle ? (0, import_path12.resolve)(entryPath, subtitle) : null;
1763
1848
  const destSubtitleName = subtitle && subtitleExt ? `${episodeName}.${subtitleExt}` : null;
1764
1849
  if (!dryRun) {
1765
- (0, import_fs10.mkdirSync)(seasonPath, { recursive: true });
1850
+ (0, import_fs11.mkdirSync)(seasonPath, { recursive: true });
1766
1851
  let mode = "move";
1767
1852
  if (useHardlink) {
1768
1853
  try {
1769
1854
  if (!sameDev(videoSourcePath, seasonPath)) throw new Error("cross-filesystem");
1770
- (0, import_fs10.linkSync)(videoSourcePath, destVideoPath);
1855
+ (0, import_fs11.linkSync)(videoSourcePath, destVideoPath);
1771
1856
  mode = "hardlink";
1772
1857
  } catch {
1773
1858
  spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
1774
- (0, import_fs10.cpSync)(videoSourcePath, destVideoPath);
1859
+ (0, import_fs11.cpSync)(videoSourcePath, destVideoPath);
1775
1860
  mode = "copy";
1776
1861
  }
1777
- if (subtitleSourcePath && destSubtitleName) (0, import_fs10.cpSync)(subtitleSourcePath, (0, import_path11.resolve)(seasonPath, destSubtitleName));
1862
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs11.cpSync)(subtitleSourcePath, (0, import_path12.resolve)(seasonPath, destSubtitleName));
1778
1863
  } else {
1779
1864
  if (sameDev(videoSourcePath, seasonPath)) {
1780
- (0, import_fs10.renameSync)(videoSourcePath, destVideoPath);
1865
+ (0, import_fs11.renameSync)(videoSourcePath, destVideoPath);
1781
1866
  } else {
1782
- (0, import_fs10.cpSync)(videoSourcePath, destVideoPath);
1783
- (0, import_fs10.rmSync)(videoSourcePath);
1867
+ (0, import_fs11.cpSync)(videoSourcePath, destVideoPath);
1868
+ trashPath(videoSourcePath);
1784
1869
  }
1785
- if (subtitleSourcePath && destSubtitleName) (0, import_fs10.renameSync)(subtitleSourcePath, (0, import_path11.resolve)(seasonPath, destSubtitleName));
1786
- if (isDir) (0, import_fs10.rmSync)(entryPath, { recursive: true, force: true });
1870
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs11.renameSync)(subtitleSourcePath, (0, import_path12.resolve)(seasonPath, destSubtitleName));
1871
+ if (isDir) trashPath(entryPath, { recursive: true });
1787
1872
  }
1788
1873
  recordImport(sessionId, entryPath, seasonPath, mode, tmdbId, "tv");
1789
1874
  }
@@ -1832,6 +1917,15 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1832
1917
  for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
1833
1918
  skipped += pendingTv.length;
1834
1919
  }
1920
+ if (pendingBooks.length > 0) {
1921
+ spinner_default.warn(`${pendingBooks.length} uncertain book${pendingBooks.length > 1 ? "s" : ""} skipped \u2014 contains PDFs, review manually`);
1922
+ for (const p of pendingBooks) spinner_default.info(` ${typeGlyph("book")} ${p.entry}${typeTag("book")}`);
1923
+ }
1924
+ if (pendingAnime.length > 0) {
1925
+ spinner_default.warn(`${pendingAnime.length} uncertain anime/TV director${pendingAnime.length > 1 ? "ies" : "y"} skipped \u2014 multiple videos with no episode naming`);
1926
+ for (const p of pendingAnime) spinner_default.info(` ${typeGlyph("tv")} ${p.entry} (${p.videoCount} video${p.videoCount > 1 ? "s" : ""})${typeTag("tv")}`);
1927
+ skipped += pendingAnime.length;
1928
+ }
1835
1929
  if (ignoreSet.size > 0) {
1836
1930
  const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
1837
1931
  if (stale.length > 0 && !dryRun) {
@@ -1848,7 +1942,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1848
1942
  var scan_default = scan;
1849
1943
 
1850
1944
  // src/actions/undo.ts
1851
- var import_fs11 = require("fs");
1945
+ var import_fs12 = require("fs");
1852
1946
  var import_termkit11 = require("termkit");
1853
1947
  var undo = async () => {
1854
1948
  spinner_default.start();
@@ -1863,7 +1957,7 @@ var undo = async () => {
1863
1957
  if (!useImports) {
1864
1958
  let undone2 = 0;
1865
1959
  for (const record of renameRecords) {
1866
- (0, import_fs11.renameSync)(record.newPath, record.oldPath);
1960
+ (0, import_fs12.renameSync)(record.newPath, record.oldPath);
1867
1961
  spinner_default.succeed(`${import_termkit11.Color.green.encoder(record.newPath)} \u2192 ${import_termkit11.Color.white.encoder(record.oldPath)}`);
1868
1962
  undone2++;
1869
1963
  }
@@ -1885,12 +1979,12 @@ var undo = async () => {
1885
1979
  skipped++;
1886
1980
  continue;
1887
1981
  }
1888
- if (!(0, import_fs11.existsSync)(record.destinationPath)) {
1982
+ if (!(0, import_fs12.existsSync)(record.destinationPath)) {
1889
1983
  spinner_default.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
1890
1984
  skipped++;
1891
1985
  continue;
1892
1986
  }
1893
- (0, import_fs11.renameSync)(record.destinationPath, record.sourcePath);
1987
+ (0, import_fs12.renameSync)(record.destinationPath, record.sourcePath);
1894
1988
  spinner_default.succeed(`${import_termkit11.Color.green.encoder(record.destinationPath)} \u2192 ${import_termkit11.Color.white.encoder(record.sourcePath)}`);
1895
1989
  undone++;
1896
1990
  }
@@ -1903,37 +1997,37 @@ var undo_default = undo;
1903
1997
 
1904
1998
  // src/actions/watch.ts
1905
1999
  var import_chokidar = __toESM(require("chokidar"));
1906
- var import_fs12 = require("fs");
1907
- var import_path12 = require("path");
2000
+ var import_fs13 = require("fs");
2001
+ var import_path13 = require("path");
1908
2002
  var import_termkit12 = require("termkit");
1909
2003
  var sameDev2 = (a, b) => {
1910
2004
  try {
1911
2005
  let bExisting = b;
1912
- while (!(0, import_fs12.existsSync)(bExisting)) bExisting = (0, import_path12.dirname)(bExisting);
1913
- return (0, import_fs12.statSync)(a).dev === (0, import_fs12.statSync)(bExisting).dev;
2006
+ while (!(0, import_fs13.existsSync)(bExisting)) bExisting = (0, import_path13.dirname)(bExisting);
2007
+ return (0, import_fs13.statSync)(a).dev === (0, import_fs13.statSync)(bExisting).dev;
1914
2008
  } catch {
1915
2009
  return false;
1916
2010
  }
1917
2011
  };
1918
2012
  var moveItem = (src, dest) => {
1919
2013
  if (sameDev2(src, dest)) {
1920
- (0, import_fs12.renameSync)(src, dest);
2014
+ (0, import_fs13.renameSync)(src, dest);
1921
2015
  } else {
1922
- (0, import_fs12.cpSync)(src, dest, { recursive: true });
1923
- (0, import_fs12.rmSync)(src, { recursive: true, force: true });
2016
+ (0, import_fs13.cpSync)(src, dest, { recursive: true });
2017
+ (0, import_fs13.rmSync)(src, { recursive: true, force: true });
1924
2018
  }
1925
2019
  };
1926
- var findVideo2 = (dir) => (0, import_fs12.readdirSync)(dir).find((f) => {
2020
+ var findVideo2 = (dir) => (0, import_fs13.readdirSync)(dir).find((f) => {
1927
2021
  const ext = f.match(/([^.]+$)/)?.[0];
1928
2022
  return ext && videoExtensions_default.includes(ext);
1929
2023
  }) ?? null;
1930
- var containsBook2 = (dir, depth = 2) => (0, import_fs12.readdirSync)(dir).some((f) => {
2024
+ var containsBook2 = (dir, depth = 2) => (0, import_fs13.readdirSync)(dir).some((f) => {
1931
2025
  const ext = f.match(/([^.]+$)/)?.[0];
1932
2026
  if (ext && bookExtensions_default.includes(ext)) return true;
1933
2027
  if (depth > 1) {
1934
2028
  try {
1935
- const sub = (0, import_path12.resolve)(dir, f);
1936
- if ((0, import_fs12.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
2029
+ const sub = (0, import_path13.resolve)(dir, f);
2030
+ if ((0, import_fs13.lstatSync)(sub).isDirectory()) return containsBook2(sub, depth - 1);
1937
2031
  } catch {
1938
2032
  }
1939
2033
  }
@@ -1944,26 +2038,26 @@ var isSeasonDirName2 = (name) => !isTvEpisodeName2(name) && /(?:^|[.\s_-])(?:sea
1944
2038
  var expandWatchPath = (p) => {
1945
2039
  let isDir;
1946
2040
  try {
1947
- isDir = (0, import_fs12.lstatSync)(p).isDirectory();
2041
+ isDir = (0, import_fs13.lstatSync)(p).isDirectory();
1948
2042
  } catch {
1949
2043
  return [p];
1950
2044
  }
1951
2045
  if (!isDir) return [p];
1952
- const name = (0, import_path12.basename)(p);
2046
+ const name = (0, import_path13.basename)(p);
1953
2047
  if (isTvEpisodeName2(name) || /(?<=\[).+?(?=\])/.test(name) || /^[A-Z]{4}\d{5}/i.test(name)) return [p];
1954
2048
  let children;
1955
2049
  try {
1956
- children = (0, import_fs12.readdirSync)(p);
2050
+ children = (0, import_fs13.readdirSync)(p);
1957
2051
  } catch {
1958
2052
  return [p];
1959
2053
  }
1960
2054
  if (children.some((c) => isTvEpisodeName2(c))) {
1961
2055
  const entries = [];
1962
2056
  for (const child of children) {
1963
- const cp = (0, import_path12.resolve)(p, child);
2057
+ const cp = (0, import_path13.resolve)(p, child);
1964
2058
  let cd;
1965
2059
  try {
1966
- cd = (0, import_fs12.lstatSync)(cp).isDirectory();
2060
+ cd = (0, import_fs13.lstatSync)(cp).isDirectory();
1967
2061
  } catch {
1968
2062
  continue;
1969
2063
  }
@@ -1975,7 +2069,7 @@ var expandWatchPath = (p) => {
1975
2069
  }
1976
2070
  const seasonDirs = children.filter((c) => {
1977
2071
  try {
1978
- return isSeasonDirName2(c) && (0, import_fs12.lstatSync)((0, import_path12.resolve)(p, c)).isDirectory();
2072
+ return isSeasonDirName2(c) && (0, import_fs13.lstatSync)((0, import_path13.resolve)(p, c)).isDirectory();
1979
2073
  } catch {
1980
2074
  return false;
1981
2075
  }
@@ -1983,18 +2077,18 @@ var expandWatchPath = (p) => {
1983
2077
  if (seasonDirs.length > 0) {
1984
2078
  const entries = [];
1985
2079
  for (const sd of seasonDirs) {
1986
- const sp = (0, import_path12.resolve)(p, sd);
2080
+ const sp = (0, import_path13.resolve)(p, sd);
1987
2081
  let sc;
1988
2082
  try {
1989
- sc = (0, import_fs12.readdirSync)(sp);
2083
+ sc = (0, import_fs13.readdirSync)(sp);
1990
2084
  } catch {
1991
2085
  continue;
1992
2086
  }
1993
2087
  for (const child of sc) {
1994
- const cp = (0, import_path12.resolve)(sp, child);
2088
+ const cp = (0, import_path13.resolve)(sp, child);
1995
2089
  let cd;
1996
2090
  try {
1997
- cd = (0, import_fs12.lstatSync)(cp).isDirectory();
2091
+ cd = (0, import_fs13.lstatSync)(cp).isDirectory();
1998
2092
  } catch {
1999
2093
  continue;
2000
2094
  }
@@ -2008,10 +2102,10 @@ var expandWatchPath = (p) => {
2008
2102
  return [p];
2009
2103
  };
2010
2104
  var findSeasonFolder2 = (showPath, season) => {
2011
- if (!(0, import_fs12.existsSync)(showPath)) return null;
2012
- const folders = (0, import_fs12.readdirSync)(showPath).filter((f) => {
2105
+ if (!(0, import_fs13.existsSync)(showPath)) return null;
2106
+ const folders = (0, import_fs13.readdirSync)(showPath).filter((f) => {
2013
2107
  try {
2014
- return (0, import_fs12.lstatSync)((0, import_path12.resolve)(showPath, f)).isDirectory();
2108
+ return (0, import_fs13.lstatSync)((0, import_path13.resolve)(showPath, f)).isDirectory();
2015
2109
  } catch {
2016
2110
  return false;
2017
2111
  }
@@ -2024,10 +2118,10 @@ var findSeasonFolder2 = (showPath, season) => {
2024
2118
  var processItem = async (entryPath, useHardlink, language, auto) => {
2025
2119
  const config = getConfig();
2026
2120
  const sessionId = (/* @__PURE__ */ new Date()).toISOString();
2027
- const entry = (0, import_path12.basename)(entryPath);
2121
+ const entry = (0, import_path13.basename)(entryPath);
2028
2122
  const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
2029
2123
  const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
2030
- const isDir = (0, import_fs12.lstatSync)(entryPath).isDirectory();
2124
+ const isDir = (0, import_fs13.lstatSync)(entryPath).isDirectory();
2031
2125
  const ext = entry.match(/([^.]+$)/)?.[0];
2032
2126
  const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
2033
2127
  const isBook = !isDir && ext && bookExtensions_default.includes(ext);
@@ -2053,8 +2147,8 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2053
2147
  const id = entry.split("-")[0];
2054
2148
  if (!nameMatch || !id) return;
2055
2149
  const destName = `${nameMatch[0]} [${id}]`;
2056
- const destPath = (0, import_path12.resolve)(destRoot, destName);
2057
- if ((0, import_fs12.existsSync)(destPath)) {
2150
+ const destPath = (0, import_path13.resolve)(destRoot, destName);
2151
+ if ((0, import_fs13.existsSync)(destPath)) {
2058
2152
  spinner_default.warn(`already exists: ${destName}`);
2059
2153
  return;
2060
2154
  }
@@ -2064,20 +2158,20 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2064
2158
  return;
2065
2159
  }
2066
2160
  if (detectedType === "book") {
2067
- const destPath = (0, import_path12.resolve)(destRoot, entry);
2068
- if ((0, import_fs12.existsSync)(destPath)) {
2161
+ const destPath = (0, import_path13.resolve)(destRoot, entry);
2162
+ if ((0, import_fs13.existsSync)(destPath)) {
2069
2163
  spinner_default.warn(`already exists: ${entry}`);
2070
2164
  return;
2071
2165
  }
2072
2166
  if (isDir || isBookDir) {
2073
2167
  moveItem(entryPath, destPath);
2074
2168
  } else {
2075
- (0, import_fs12.mkdirSync)(destRoot, { recursive: true });
2169
+ (0, import_fs13.mkdirSync)(destRoot, { recursive: true });
2076
2170
  if (sameDev2(entryPath, destRoot)) {
2077
- (0, import_fs12.renameSync)(entryPath, destPath);
2171
+ (0, import_fs13.renameSync)(entryPath, destPath);
2078
2172
  } else {
2079
- (0, import_fs12.cpSync)(entryPath, destPath);
2080
- (0, import_fs12.rmSync)(entryPath);
2173
+ (0, import_fs13.cpSync)(entryPath, destPath);
2174
+ (0, import_fs13.rmSync)(entryPath);
2081
2175
  }
2082
2176
  }
2083
2177
  recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
@@ -2102,14 +2196,14 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2102
2196
  showFolderName = showPath.split("/").pop() ?? registeredShow.path;
2103
2197
  } else if (auto) {
2104
2198
  showFolderName = formatMovieName(movieFormat, parsed.title, parsed.year);
2105
- showPath = (0, import_path12.resolve)(destRoot, showFolderName);
2199
+ showPath = (0, import_path13.resolve)(destRoot, showFolderName);
2106
2200
  upsertShow(showPath, null, parsed.title);
2107
2201
  } else {
2108
2202
  if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
2109
2203
  return;
2110
2204
  }
2111
2205
  const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
2112
- const seasonPath = (0, import_path12.resolve)(showPath, seasonFolderName);
2206
+ const seasonPath = (0, import_path13.resolve)(showPath, seasonFolderName);
2113
2207
  const videoFile2 = isDir ? findVideo2(entryPath) : entry;
2114
2208
  if (!videoFile2) {
2115
2209
  if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
@@ -2119,39 +2213,39 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2119
2213
  const tmdbEpisodeName = registeredShow?.tmdbId && config.tmdbApiKey ? await getEpisodeName(registeredShow.tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
2120
2214
  const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, parsed.title, tmdbEpisodeName ?? void 0);
2121
2215
  const destVideoName2 = `${episodeName}.${videoExt2}`;
2122
- const destVideoPath = (0, import_path12.resolve)(seasonPath, destVideoName2);
2123
- const videoSourcePath2 = isDir ? (0, import_path12.resolve)(entryPath, videoFile2) : entryPath;
2124
- if ((0, import_fs12.existsSync)(destVideoPath)) {
2216
+ const destVideoPath = (0, import_path13.resolve)(seasonPath, destVideoName2);
2217
+ const videoSourcePath2 = isDir ? (0, import_path13.resolve)(entryPath, videoFile2) : entryPath;
2218
+ if ((0, import_fs13.existsSync)(destVideoPath)) {
2125
2219
  spinner_default.warn(`already exists: ${episodeName}`);
2126
2220
  return;
2127
2221
  }
2128
- const dirFiles2 = isDir ? (0, import_fs12.readdirSync)(entryPath) : [];
2222
+ const dirFiles2 = isDir ? (0, import_fs13.readdirSync)(entryPath) : [];
2129
2223
  const subtitle2 = isDir ? findSubtitle(dirFiles2, language) : null;
2130
2224
  const subtitleExt2 = subtitle2?.match(/([^.]+$)/)?.[0];
2131
- const subtitleSourcePath2 = subtitle2 ? (0, import_path12.resolve)(entryPath, subtitle2) : null;
2225
+ const subtitleSourcePath2 = subtitle2 ? (0, import_path13.resolve)(entryPath, subtitle2) : null;
2132
2226
  const destSubtitleName2 = subtitle2 && subtitleExt2 ? `${episodeName}.${subtitleExt2}` : null;
2133
- (0, import_fs12.mkdirSync)(seasonPath, { recursive: true });
2227
+ (0, import_fs13.mkdirSync)(seasonPath, { recursive: true });
2134
2228
  let mode = "move";
2135
2229
  if (useHardlink) {
2136
2230
  try {
2137
2231
  if (!sameDev2(videoSourcePath2, seasonPath)) throw new Error("cross-filesystem");
2138
- (0, import_fs12.linkSync)(videoSourcePath2, destVideoPath);
2232
+ (0, import_fs13.linkSync)(videoSourcePath2, destVideoPath);
2139
2233
  mode = "hardlink";
2140
2234
  } catch {
2141
2235
  spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
2142
- (0, import_fs12.cpSync)(videoSourcePath2, destVideoPath);
2236
+ (0, import_fs13.cpSync)(videoSourcePath2, destVideoPath);
2143
2237
  mode = "copy";
2144
2238
  }
2145
- if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs12.cpSync)(subtitleSourcePath2, (0, import_path12.resolve)(seasonPath, destSubtitleName2));
2239
+ if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs13.cpSync)(subtitleSourcePath2, (0, import_path13.resolve)(seasonPath, destSubtitleName2));
2146
2240
  } else {
2147
2241
  if (sameDev2(videoSourcePath2, seasonPath)) {
2148
- (0, import_fs12.renameSync)(videoSourcePath2, destVideoPath);
2242
+ (0, import_fs13.renameSync)(videoSourcePath2, destVideoPath);
2149
2243
  } else {
2150
- (0, import_fs12.cpSync)(videoSourcePath2, destVideoPath);
2151
- (0, import_fs12.rmSync)(videoSourcePath2);
2244
+ (0, import_fs13.cpSync)(videoSourcePath2, destVideoPath);
2245
+ (0, import_fs13.rmSync)(videoSourcePath2);
2152
2246
  }
2153
- if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs12.renameSync)(subtitleSourcePath2, (0, import_path12.resolve)(seasonPath, destSubtitleName2));
2154
- if (isDir) (0, import_fs12.rmSync)(entryPath, { recursive: true, force: true });
2247
+ if (subtitleSourcePath2 && destSubtitleName2) (0, import_fs13.renameSync)(subtitleSourcePath2, (0, import_path13.resolve)(seasonPath, destSubtitleName2));
2248
+ if (isDir) (0, import_fs13.rmSync)(entryPath, { recursive: true, force: true });
2155
2249
  }
2156
2250
  recordImport(sessionId, entryPath, seasonPath, mode, void 0, "tv");
2157
2251
  spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
@@ -2159,8 +2253,8 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2159
2253
  }
2160
2254
  const edition = detectEdition(entry);
2161
2255
  const folderName = formatMovieName(movieFormat, parsed.title, parsed.year, edition);
2162
- const destFolder = (0, import_path12.resolve)(destRoot, folderName);
2163
- if ((0, import_fs12.existsSync)(destFolder)) {
2256
+ const destFolder = (0, import_path13.resolve)(destRoot, folderName);
2257
+ if ((0, import_fs13.existsSync)(destFolder)) {
2164
2258
  spinner_default.warn(`already exists: ${folderName}`);
2165
2259
  return;
2166
2260
  }
@@ -2171,42 +2265,42 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2171
2265
  }
2172
2266
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
2173
2267
  const destVideoName = `${folderName}.${videoExt}`;
2174
- const videoSourcePath = isDir ? (0, import_path12.resolve)(entryPath, videoFile) : entryPath;
2175
- const dirFiles = isDir ? (0, import_fs12.readdirSync)(entryPath) : [];
2268
+ const videoSourcePath = isDir ? (0, import_path13.resolve)(entryPath, videoFile) : entryPath;
2269
+ const dirFiles = isDir ? (0, import_fs13.readdirSync)(entryPath) : [];
2176
2270
  const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
2177
2271
  const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
2178
- const subtitleSourcePath = subtitle ? (0, import_path12.resolve)(entryPath, subtitle) : null;
2272
+ const subtitleSourcePath = subtitle ? (0, import_path13.resolve)(entryPath, subtitle) : null;
2179
2273
  const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
2180
2274
  if (useHardlink) {
2181
- (0, import_fs12.mkdirSync)(destFolder, { recursive: true });
2182
- const destVideoPath = (0, import_path12.resolve)(destFolder, destVideoName);
2275
+ (0, import_fs13.mkdirSync)(destFolder, { recursive: true });
2276
+ const destVideoPath = (0, import_path13.resolve)(destFolder, destVideoName);
2183
2277
  let mode;
2184
2278
  try {
2185
2279
  if (!sameDev2(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
2186
- (0, import_fs12.linkSync)(videoSourcePath, destVideoPath);
2280
+ (0, import_fs13.linkSync)(videoSourcePath, destVideoPath);
2187
2281
  mode = "hardlink";
2188
2282
  } catch {
2189
2283
  spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
2190
- (0, import_fs12.cpSync)(videoSourcePath, destVideoPath);
2284
+ (0, import_fs13.cpSync)(videoSourcePath, destVideoPath);
2191
2285
  mode = "copy";
2192
2286
  }
2193
- if (subtitleSourcePath && destSubtitleName) (0, import_fs12.cpSync)(subtitleSourcePath, (0, import_path12.resolve)(destFolder, destSubtitleName));
2287
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs13.cpSync)(subtitleSourcePath, (0, import_path13.resolve)(destFolder, destSubtitleName));
2194
2288
  recordImport(sessionId, entryPath, destFolder, mode, void 0, "movie");
2195
2289
  } else {
2196
2290
  if (isDir) {
2197
2291
  const keep = new Set([videoFile, subtitle].filter(Boolean));
2198
- for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs12.rmSync)((0, import_path12.resolve)(entryPath, f), { recursive: true, force: true });
2199
- (0, import_fs12.renameSync)(videoSourcePath, (0, import_path12.resolve)(entryPath, destVideoName));
2200
- if (subtitleSourcePath && destSubtitleName) (0, import_fs12.renameSync)(subtitleSourcePath, (0, import_path12.resolve)(entryPath, destSubtitleName));
2292
+ for (const f of dirFiles.filter((f2) => !keep.has(f2))) (0, import_fs13.rmSync)((0, import_path13.resolve)(entryPath, f), { recursive: true, force: true });
2293
+ (0, import_fs13.renameSync)(videoSourcePath, (0, import_path13.resolve)(entryPath, destVideoName));
2294
+ if (subtitleSourcePath && destSubtitleName) (0, import_fs13.renameSync)(subtitleSourcePath, (0, import_path13.resolve)(entryPath, destSubtitleName));
2201
2295
  moveItem(entryPath, destFolder);
2202
2296
  } else {
2203
- (0, import_fs12.mkdirSync)(destFolder, { recursive: true });
2204
- const destVideoPath = (0, import_path12.resolve)(destFolder, destVideoName);
2297
+ (0, import_fs13.mkdirSync)(destFolder, { recursive: true });
2298
+ const destVideoPath = (0, import_path13.resolve)(destFolder, destVideoName);
2205
2299
  if (sameDev2(videoSourcePath, destRoot)) {
2206
- (0, import_fs12.renameSync)(videoSourcePath, destVideoPath);
2300
+ (0, import_fs13.renameSync)(videoSourcePath, destVideoPath);
2207
2301
  } else {
2208
- (0, import_fs12.cpSync)(videoSourcePath, destVideoPath);
2209
- (0, import_fs12.rmSync)(videoSourcePath);
2302
+ (0, import_fs13.cpSync)(videoSourcePath, destVideoPath);
2303
+ (0, import_fs13.rmSync)(videoSourcePath);
2210
2304
  }
2211
2305
  }
2212
2306
  recordImport(sessionId, entryPath, destFolder, "move", void 0, "movie");