reelsort 0.2.7 → 0.2.9

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.
Files changed (4) hide show
  1. package/dist/cli.js +358 -359
  2. package/dist/index.js +282 -290
  3. package/dist/index.mjs +252 -260
  4. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -78,7 +78,7 @@ module.exports = __toCommonJS(index_exports);
78
78
 
79
79
  // src/actions/clean.ts
80
80
  var import_fs2 = require("fs");
81
- var import_termkit2 = require("termkit");
81
+ var import_termkit = require("termkit");
82
82
 
83
83
  // src/db.ts
84
84
  var import_better_sqlite3 = __toESM(require("better-sqlite3"));
@@ -220,48 +220,8 @@ var getHistory = (limit = 10) => {
220
220
  }));
221
221
  };
222
222
 
223
- // src/refs/spinner.ts
224
- var import_termkit = require("termkit");
225
- var Spinner = class {
226
- spinner;
227
- constructor() {
228
- this.spinner = new import_termkit.Spinner();
229
- }
230
- get text() {
231
- return this.spinner.text;
232
- }
233
- set text(t) {
234
- this.spinner.text = t;
235
- }
236
- start(s) {
237
- if (s) this.spinner.text = s;
238
- this.spinner.start();
239
- return this;
240
- }
241
- info(s) {
242
- this.spinner.info(s);
243
- return this;
244
- }
245
- warn(s) {
246
- this.spinner.warn(s);
247
- return this;
248
- }
249
- fail(s) {
250
- this.spinner.fail(s);
251
- return this;
252
- }
253
- succeed(s) {
254
- this.spinner.succeed(s);
255
- return this;
256
- }
257
- stop() {
258
- this.spinner.stop();
259
- return this;
260
- }
261
- };
262
- var spinner_default = new Spinner();
263
-
264
223
  // src/actions/clean.ts
224
+ var spinner = new import_termkit.Spinner();
265
225
  var parseOlderThan = (s) => {
266
226
  const match = s.match(/^(\d+)([dhm])$/);
267
227
  if (!match) return null;
@@ -271,11 +231,11 @@ var parseOlderThan = (s) => {
271
231
  return ms;
272
232
  };
273
233
  var clean = async ({ dryRun, olderThan }) => {
274
- spinner_default.start();
234
+ spinner.start();
275
235
  const imports = getCleanableImports();
276
236
  if (imports.length === 0) {
277
- spinner_default.info("nothing to clean");
278
- spinner_default.stop();
237
+ spinner.info("nothing to clean");
238
+ spinner.stop();
279
239
  return;
280
240
  }
281
241
  const cutoffMs = olderThan ? parseOlderThan(olderThan) : null;
@@ -294,29 +254,29 @@ var clean = async ({ dryRun, olderThan }) => {
294
254
  continue;
295
255
  }
296
256
  if (dryRun) {
297
- spinner_default.succeed(`[dry] would remove ${import_termkit2.Color.white.encoder(imp.sourcePath)}`);
257
+ spinner.succeed(`[dry] would remove ${import_termkit.Color.white.encoder(imp.sourcePath)}`);
298
258
  cleaned++;
299
259
  continue;
300
260
  }
301
261
  try {
302
262
  (0, import_fs2.rmSync)(imp.sourcePath, { recursive: true, force: true });
303
263
  deleteImport(imp.id);
304
- spinner_default.succeed(`removed ${import_termkit2.Color.white.encoder(imp.sourcePath)}`);
264
+ spinner.succeed(`removed ${import_termkit.Color.white.encoder(imp.sourcePath)}`);
305
265
  cleaned++;
306
266
  } catch {
307
- spinner_default.warn(`locked or inaccessible, skipped: ${import_termkit2.Color.white.encoder(imp.sourcePath)}`);
267
+ spinner.warn(`locked or inaccessible, skipped: ${import_termkit.Color.white.encoder(imp.sourcePath)}`);
308
268
  skipped++;
309
269
  }
310
270
  }
311
- spinner_default.succeed(`cleaned ${cleaned} items`);
312
- if (skipped) spinner_default.info(`skipped ${skipped} items`);
313
- spinner_default.stop();
271
+ spinner.succeed(`cleaned ${cleaned} items`);
272
+ if (skipped) spinner.info(`skipped ${skipped} items`);
273
+ spinner.stop();
314
274
  };
315
275
  var clean_default = clean;
316
276
 
317
277
  // src/actions/config.ts
318
278
  var import_path3 = require("path");
319
- var import_termkit3 = require("termkit");
279
+ var import_termkit2 = require("termkit");
320
280
 
321
281
  // src/config.ts
322
282
  var import_fs3 = require("fs");
@@ -363,32 +323,33 @@ var formatMovieName = (template, title, year, edition) => {
363
323
  };
364
324
 
365
325
  // src/actions/config.ts
326
+ var spinner2 = new import_termkit2.Spinner();
366
327
  var DEST_TYPES = ["movie", "tv", "ps3", "book"];
367
328
  var sourceAdd = async ({ dir }) => {
368
329
  const resolved = (0, import_path3.resolve)(dir);
369
330
  const config = getConfig();
370
331
  if (config.sources.includes(resolved)) {
371
- spinner_default.start();
372
- spinner_default.info(`source already configured: ${import_termkit3.Color.white.encoder(resolved)}`);
373
- spinner_default.stop();
332
+ spinner2.start();
333
+ spinner2.info(`source already configured: ${import_termkit2.Color.white.encoder(resolved)}`);
334
+ spinner2.stop();
374
335
  return;
375
336
  }
376
337
  config.sources.push(resolved);
377
338
  saveConfig(config);
378
- spinner_default.start();
379
- spinner_default.succeed(`added source: ${import_termkit3.Color.white.encoder(resolved)}`);
380
- spinner_default.stop();
339
+ spinner2.start();
340
+ spinner2.succeed(`added source: ${import_termkit2.Color.white.encoder(resolved)}`);
341
+ spinner2.stop();
381
342
  };
382
343
  var sourceRemove = async ({ dir }) => {
383
344
  const config = getConfig();
384
345
  if (!dir) {
385
346
  if (config.sources.length === 0) {
386
- spinner_default.start();
387
- spinner_default.warn("no sources configured");
388
- spinner_default.stop();
347
+ spinner2.start();
348
+ spinner2.warn("no sources configured");
349
+ spinner2.stop();
389
350
  return;
390
351
  }
391
- const select = new import_termkit3.Select();
352
+ const select = new import_termkit2.Select();
392
353
  const picked = await select.ask("Which source do you want to remove?", config.sources.map((s) => ({ label: s, value: s })));
393
354
  if (!picked) return;
394
355
  dir = picked.value;
@@ -396,16 +357,16 @@ var sourceRemove = async ({ dir }) => {
396
357
  const resolved = (0, import_path3.resolve)(dir);
397
358
  const index = config.sources.indexOf(resolved);
398
359
  if (index === -1) {
399
- spinner_default.start();
400
- spinner_default.warn(`source not found: ${import_termkit3.Color.white.encoder(resolved)}`);
401
- spinner_default.stop();
360
+ spinner2.start();
361
+ spinner2.warn(`source not found: ${import_termkit2.Color.white.encoder(resolved)}`);
362
+ spinner2.stop();
402
363
  return;
403
364
  }
404
365
  config.sources.splice(index, 1);
405
366
  saveConfig(config);
406
- spinner_default.start();
407
- spinner_default.succeed(`removed source: ${import_termkit3.Color.white.encoder(resolved)}`);
408
- spinner_default.stop();
367
+ spinner2.start();
368
+ spinner2.succeed(`removed source: ${import_termkit2.Color.white.encoder(resolved)}`);
369
+ spinner2.stop();
409
370
  };
410
371
  var destAdd = async ({ type, dir }) => {
411
372
  if (!DEST_TYPES.includes(type)) {
@@ -415,21 +376,21 @@ var destAdd = async ({ type, dir }) => {
415
376
  const config = getConfig();
416
377
  config.dest[type] = resolved;
417
378
  saveConfig(config);
418
- spinner_default.start();
419
- spinner_default.succeed(`set ${type} destination: ${import_termkit3.Color.green.encoder(resolved)}`);
420
- spinner_default.stop();
379
+ spinner2.start();
380
+ spinner2.succeed(`set ${type} destination: ${import_termkit2.Color.green.encoder(resolved)}`);
381
+ spinner2.stop();
421
382
  };
422
383
  var destRemove = async ({ type }) => {
423
384
  const config = getConfig();
424
385
  if (!type) {
425
386
  const configured = DEST_TYPES.filter((t) => config.dest[t]);
426
387
  if (configured.length === 0) {
427
- spinner_default.start();
428
- spinner_default.warn("no destinations configured");
429
- spinner_default.stop();
388
+ spinner2.start();
389
+ spinner2.warn("no destinations configured");
390
+ spinner2.stop();
430
391
  return;
431
392
  }
432
- const select = new import_termkit3.Select();
393
+ const select = new import_termkit2.Select();
433
394
  const picked = await select.ask("Which destination do you want to remove?", configured.map((t) => ({ label: `${t.padEnd(6)} ${config.dest[t]}`, value: t })));
434
395
  if (!picked) return;
435
396
  type = picked.value;
@@ -438,33 +399,33 @@ var destRemove = async ({ type }) => {
438
399
  throw new Error(`unknown type '${type}', expected: ${DEST_TYPES.join(", ")}`);
439
400
  }
440
401
  if (!config.dest[type]) {
441
- spinner_default.start();
442
- spinner_default.warn(`no ${type} destination configured`);
443
- spinner_default.stop();
402
+ spinner2.start();
403
+ spinner2.warn(`no ${type} destination configured`);
404
+ spinner2.stop();
444
405
  return;
445
406
  }
446
407
  delete config.dest[type];
447
408
  saveConfig(config);
448
- spinner_default.start();
449
- spinner_default.succeed(`removed ${type} destination`);
450
- spinner_default.stop();
409
+ spinner2.start();
410
+ spinner2.succeed(`removed ${type} destination`);
411
+ spinner2.stop();
451
412
  };
452
413
  var configSet = async ({ key, subkey, value }) => {
453
414
  const config = getConfig();
454
415
  if (key === "language") {
455
416
  config.language = subkey;
456
417
  saveConfig(config);
457
- spinner_default.start();
458
- spinner_default.succeed(`set subtitle language: ${import_termkit3.Color.green.encoder(subkey)}`);
459
- spinner_default.stop();
418
+ spinner2.start();
419
+ spinner2.succeed(`set subtitle language: ${import_termkit2.Color.green.encoder(subkey)}`);
420
+ spinner2.stop();
460
421
  return;
461
422
  }
462
423
  if (key === "tmdb-key") {
463
424
  config.tmdbApiKey = subkey;
464
425
  saveConfig(config);
465
- spinner_default.start();
466
- spinner_default.succeed(`set TMDb API key`);
467
- spinner_default.stop();
426
+ spinner2.start();
427
+ spinner2.succeed(`set TMDb API key`);
428
+ spinner2.stop();
468
429
  return;
469
430
  }
470
431
  if (key === "format") {
@@ -484,9 +445,9 @@ var configSet = async ({ key, subkey, value }) => {
484
445
  throw new Error(`unknown format key '${subkey}', expected: movie, episode, season`);
485
446
  }
486
447
  saveConfig(config);
487
- spinner_default.start();
488
- spinner_default.succeed(`set ${subkey} format: ${import_termkit3.Color.green.encoder(value ?? subkey)}`);
489
- spinner_default.stop();
448
+ spinner2.start();
449
+ spinner2.succeed(`set ${subkey} format: ${import_termkit2.Color.green.encoder(value ?? subkey)}`);
450
+ spinner2.stop();
490
451
  return;
491
452
  }
492
453
  throw new Error(`unknown key '${key}', expected: language, tmdb-key, format`);
@@ -497,7 +458,7 @@ var configShow = async () => {
497
458
  if (config.sources.length === 0) {
498
459
  console.log(" (none)");
499
460
  } else {
500
- for (const s of config.sources) console.log(` ${import_termkit3.Color.white.encoder(s)}`);
461
+ for (const s of config.sources) console.log(` ${import_termkit2.Color.white.encoder(s)}`);
501
462
  }
502
463
  console.log("\nDestinations:");
503
464
  const entries = DEST_TYPES.map((t) => ({ type: t, path: config.dest[t] })).filter((e) => e.path);
@@ -505,20 +466,20 @@ var configShow = async () => {
505
466
  console.log(" (none)");
506
467
  } else {
507
468
  for (const { type, path } of entries) {
508
- console.log(` ${type.padEnd(6)} ${import_termkit3.Color.green.encoder(path)}`);
469
+ console.log(` ${type.padEnd(6)} ${import_termkit2.Color.green.encoder(path)}`);
509
470
  }
510
471
  }
511
472
  console.log(`
512
- Subtitle language: ${import_termkit3.Color.green.encoder(config.language ?? "eng (default)")}`);
513
- console.log(`TMDb API key: ${config.tmdbApiKey ? import_termkit3.Color.green.encoder("configured") : import_termkit3.Color.red.encoder("not set")}`);
514
- console.log(`Movie format: ${import_termkit3.Color.green.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
515
- console.log(`Episode format: ${import_termkit3.Color.green.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
516
- console.log(`Season folder: ${import_termkit3.Color.green.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
473
+ Subtitle language: ${import_termkit2.Color.green.encoder(config.language ?? "eng (default)")}`);
474
+ console.log(`TMDb API key: ${config.tmdbApiKey ? import_termkit2.Color.green.encoder("configured") : import_termkit2.Color.red.encoder("not set")}`);
475
+ console.log(`Movie format: ${import_termkit2.Color.green.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
476
+ console.log(`Episode format: ${import_termkit2.Color.green.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
477
+ console.log(`Season folder: ${import_termkit2.Color.green.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
517
478
  console.log("\nIgnored files:");
518
479
  if (!config.ignore || config.ignore.length === 0) {
519
480
  console.log(" (none)");
520
481
  } else {
521
- for (const name of config.ignore) console.log(` ${import_termkit3.Color.white.encoder(name)}`);
482
+ for (const name of config.ignore) console.log(` ${import_termkit2.Color.white.encoder(name)}`);
522
483
  }
523
484
  console.log();
524
485
  };
@@ -526,12 +487,13 @@ Subtitle language: ${import_termkit3.Color.green.encoder(config.language ?? "eng
526
487
  // src/actions/differences.ts
527
488
  var import_fs4 = require("fs");
528
489
  var import_path4 = require("path");
529
- var import_termkit4 = require("termkit");
490
+ var import_termkit3 = require("termkit");
491
+ var spinner3 = new import_termkit3.Spinner();
530
492
  var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore }) => {
531
493
  let dir1 = rawDir1;
532
494
  let dir2 = rawDir2;
533
- spinner_default.text = `checking differences between ${import_termkit4.Color.white.encoder(dir1)} and ${import_termkit4.Color.white.encoder(dir2)}`;
534
- spinner_default.start();
495
+ spinner3.update(`checking differences between ${import_termkit3.Color.white.encoder(dir1)} and ${import_termkit3.Color.white.encoder(dir2)}`);
496
+ spinner3.start();
535
497
  dir1 = (0, import_path4.resolve)(dir1);
536
498
  dir2 = (0, import_path4.resolve)(dir2);
537
499
  if (!(0, import_fs4.existsSync)(dir1)) throw new Error(`dir1 ${dir1} does not exist`);
@@ -566,18 +528,18 @@ var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore }) => {
566
528
  removed.push(l);
567
529
  }
568
530
  }
569
- spinner_default.succeed(`checked differences between ${import_termkit4.Color.white.encoder(dir1)} and ${import_termkit4.Color.white.encoder(dir2)}`);
570
- spinner_default.succeed(`found ${added.length} added files`);
571
- spinner_default.succeed(`found ${removed.length} removed files`);
572
- spinner_default.stop();
573
- for (const i of added) console.log(`${import_termkit4.Color.green.encoder("added")} ${i}`);
574
- for (const i of removed) console.log(`${import_termkit4.Color.red.encoder("removed")} ${i}`);
531
+ spinner3.succeed(`checked differences between ${import_termkit3.Color.white.encoder(dir1)} and ${import_termkit3.Color.white.encoder(dir2)}`);
532
+ spinner3.succeed(`found ${added.length} added files`);
533
+ spinner3.succeed(`found ${removed.length} removed files`);
534
+ spinner3.stop();
535
+ for (const i of added) console.log(`${import_termkit3.Color.green.encoder("added")} ${i}`);
536
+ for (const i of removed) console.log(`${import_termkit3.Color.red.encoder("removed")} ${i}`);
575
537
  };
576
538
  var differences_default = differences;
577
539
 
578
540
  // src/actions/history.ts
579
541
  var import_path5 = require("path");
580
- var import_termkit5 = require("termkit");
542
+ var import_termkit4 = require("termkit");
581
543
  var history = async ({ limit, imports }) => {
582
544
  if (imports) {
583
545
  const sessions = getImportHistory(limit ?? 10);
@@ -589,11 +551,11 @@ var history = async ({ limit, imports }) => {
589
551
  const date = new Date(session.sessionId);
590
552
  const label = isNaN(date.getTime()) ? session.sessionId : date.toLocaleString();
591
553
  console.log(`
592
- ${import_termkit5.Color.yellow.encoder(label)} (${session.records.length} item${session.records.length !== 1 ? "s" : ""})`);
554
+ ${import_termkit4.Color.yellow.encoder(label)} (${session.records.length} item${session.records.length !== 1 ? "s" : ""})`);
593
555
  for (const r of session.records) {
594
556
  const src = (0, import_path5.basename)(r.sourcePath);
595
- const dest = import_termkit5.Color.green.encoder(r.destinationPath);
596
- const mode = r.mode !== "move" ? ` ${import_termkit5.Color.white.encoder(`[${r.mode}]`)}` : "";
557
+ const dest = import_termkit4.Color.green.encoder(r.destinationPath);
558
+ const mode = r.mode !== "move" ? ` ${import_termkit4.Color.white.encoder(`[${r.mode}]`)}` : "";
597
559
  console.log(` ${src} \u2192 ${dest}${mode}`);
598
560
  }
599
561
  }
@@ -608,11 +570,11 @@ ${import_termkit5.Color.yellow.encoder(label)} (${session.records.length} item$
608
570
  const label = isNaN(date.getTime()) ? session.sessionId : date.toLocaleString();
609
571
  const folders = session.records.filter((r) => (0, import_path5.extname)(r.newPath) === "");
610
572
  console.log(`
611
- ${import_termkit5.Color.yellow.encoder(label)} (${folders.length} item${folders.length !== 1 ? "s" : ""})`);
573
+ ${import_termkit4.Color.yellow.encoder(label)} (${folders.length} item${folders.length !== 1 ? "s" : ""})`);
612
574
  for (const r of folders) {
613
575
  const oldName = (0, import_path5.basename)(r.oldPath);
614
576
  const newName = (0, import_path5.basename)(r.newPath);
615
- console.log(` ${import_termkit5.Color.white.encoder(oldName)} \u2192 ${import_termkit5.Color.green.encoder(newName)}`);
577
+ console.log(` ${import_termkit4.Color.white.encoder(oldName)} \u2192 ${import_termkit4.Color.green.encoder(newName)}`);
616
578
  }
617
579
  }
618
580
  }
@@ -623,7 +585,7 @@ var history_default = history;
623
585
  // src/actions/list.ts
624
586
  var import_fs6 = require("fs");
625
587
  var import_path7 = require("path");
626
- var import_termkit6 = require("termkit");
588
+ var import_termkit5 = require("termkit");
627
589
 
628
590
  // src/helpers/dirSize.ts
629
591
  var import_fs5 = require("fs");
@@ -763,7 +725,7 @@ var list = async ({ type, missingSubs, codec: codecFilter, resolution: resFilter
763
725
  const destRoot = config.dest[t];
764
726
  if (!(0, import_fs6.existsSync)(destRoot)) {
765
727
  console.log(`
766
- ${t.toUpperCase()} ${import_termkit6.Color.white.encoder(destRoot)} (not found)`);
728
+ ${t.toUpperCase()} ${import_termkit5.Color.white.encoder(destRoot)} (not found)`);
767
729
  continue;
768
730
  }
769
731
  const folders = (0, import_fs6.readdirSync)(destRoot).filter((f) => {
@@ -794,8 +756,8 @@ ${t.toUpperCase()} ${import_termkit6.Color.white.encoder(destRoot)} (not found
794
756
  return yearDiff !== 0 ? yearDiff : a.title.localeCompare(b.title);
795
757
  });
796
758
  console.log(`
797
- ${import_termkit6.Color.yellow.encoder(t.toUpperCase())} ${import_termkit6.Color.white.encoder(destRoot)}`);
798
- new import_termkit6.Table(
759
+ ${import_termkit5.Color.yellow.encoder(t.toUpperCase())} ${import_termkit5.Color.white.encoder(destRoot)}`);
760
+ new import_termkit5.Table(
799
761
  filtered.map((e) => ({
800
762
  title: e.title,
801
763
  year: e.year,
@@ -812,7 +774,7 @@ ${import_termkit6.Color.yellow.encoder(t.toUpperCase())} ${import_termkit6.Colo
812
774
  { key: "resolution", title: "Res", value: (v) => v ?? "\u2014" },
813
775
  { key: "codec", title: "Codec", value: (v) => v ?? "\u2014" },
814
776
  { key: "size", title: "Size" },
815
- { key: "sub", title: "Sub", value: (v) => v ? import_termkit6.Color.green.encoder("\u2713") : import_termkit6.Color.red.encoder("\u2717") }
777
+ { key: "sub", title: "Sub", value: (v) => v ? import_termkit5.Color.green.encoder("\u2713") : import_termkit5.Color.red.encoder("\u2717") }
816
778
  ]
817
779
  }
818
780
  ).print();
@@ -826,13 +788,14 @@ var list_default = list;
826
788
  var import_child_process = require("child_process");
827
789
  var import_fs7 = require("fs");
828
790
  var import_path8 = require("path");
829
- var import_termkit7 = require("termkit");
791
+ var import_termkit6 = require("termkit");
830
792
 
831
793
  // src/refs/verbose.ts
832
794
  var _verbose = false;
833
795
  var isVerbose = () => _verbose;
834
796
 
835
797
  // src/actions/probe.ts
798
+ var spinner4 = new import_termkit6.Spinner();
836
799
  var DEST_TYPES3 = ["movie", "tv", "ps3"];
837
800
  var CODEC_MAP2 = {
838
801
  hevc: "x265",
@@ -895,10 +858,10 @@ var walkVideoFiles = (dir, depth = 0, maxDepth = 3) => {
895
858
  return results;
896
859
  };
897
860
  var probe = async ({ type, force }) => {
898
- spinner_default.start();
861
+ spinner4.start();
899
862
  if (!isFfprobeAvailable()) {
900
- spinner_default.fail("ffprobe not found \u2014 install ffmpeg to use this command");
901
- spinner_default.stop();
863
+ spinner4.fail("ffprobe not found \u2014 install ffmpeg to use this command");
864
+ spinner4.stop();
902
865
  return;
903
866
  }
904
867
  const config = getConfig();
@@ -908,30 +871,30 @@ var probe = async ({ type, force }) => {
908
871
  for (const t of types) {
909
872
  const destRoot = config.dest[t];
910
873
  if (!(0, import_fs7.existsSync)(destRoot)) continue;
911
- spinner_default.text = `scanning ${import_termkit7.Color.white.encoder(destRoot)}`;
874
+ spinner4.update(`scanning ${import_termkit6.Color.white.encoder(destRoot)}`);
912
875
  const files = walkVideoFiles(destRoot);
913
876
  for (const filePath of files) {
914
877
  if (!force && getMediaInfo(filePath)) {
915
- if (isVerbose()) spinner_default.info(`already probed: ${filePath}`);
878
+ if (isVerbose()) spinner4.info(`already probed: ${filePath}`);
916
879
  skipped++;
917
880
  continue;
918
881
  }
919
- spinner_default.text = `probing ${import_termkit7.Color.white.encoder(filePath)}`;
882
+ spinner4.update(`probing ${import_termkit6.Color.white.encoder(filePath)}`);
920
883
  const result = runFfprobe(filePath);
921
884
  if (!result) {
922
- if (isVerbose()) spinner_default.warn(`ffprobe failed: ${filePath}`);
885
+ if (isVerbose()) spinner4.warn(`ffprobe failed: ${filePath}`);
923
886
  failed++;
924
887
  continue;
925
888
  }
926
889
  upsertMediaInfo(filePath, result.codec, result.resolution, result.width, result.height, result.duration);
927
- if (isVerbose()) spinner_default.succeed(`${result.resolution ?? "?"} ${result.codec ?? "?"} ${filePath}`);
890
+ if (isVerbose()) spinner4.succeed(`${result.resolution ?? "?"} ${result.codec ?? "?"} ${filePath}`);
928
891
  probed++;
929
892
  }
930
893
  }
931
- spinner_default.succeed(`probed ${probed} files`);
932
- if (skipped) spinner_default.info(`skipped ${skipped} already indexed`);
933
- if (failed) spinner_default.warn(`failed ${failed} files`);
934
- spinner_default.stop();
894
+ spinner4.succeed(`probed ${probed} files`);
895
+ if (skipped) spinner4.info(`skipped ${skipped} already indexed`);
896
+ if (failed) spinner4.warn(`failed ${failed} files`);
897
+ spinner4.stop();
935
898
  };
936
899
  var probe_default = probe;
937
900
 
@@ -939,7 +902,7 @@ var probe_default = probe;
939
902
  var import_fs8 = require("fs");
940
903
  var import_path9 = require("path");
941
904
  var import_rimraf = require("rimraf");
942
- var import_termkit8 = require("termkit");
905
+ var import_termkit7 = require("termkit");
943
906
 
944
907
  // src/helpers/findSubtitle.ts
945
908
  var LANGUAGE_ALIASES = {
@@ -994,21 +957,22 @@ var titleCase_default = (s) => {
994
957
  };
995
958
 
996
959
  // src/actions/rename.ts
960
+ var spinner5 = new import_termkit7.Spinner();
997
961
  var rename = async ({ dir: inputDir, type }) => {
998
962
  const dir = (0, import_path9.resolve)(inputDir);
999
963
  const sessionId = (/* @__PURE__ */ new Date()).toISOString();
1000
964
  const config = getConfig();
1001
965
  const language = config.language ?? "eng";
1002
966
  const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
1003
- spinner_default.text = `renaming in ${import_termkit8.Color.white.encoder(dir)}`;
1004
- spinner_default.start();
967
+ spinner5.update(`renaming in ${import_termkit7.Color.white.encoder(dir)}`);
968
+ spinner5.start();
1005
969
  if (!(0, import_fs8.existsSync)(dir)) throw new Error(`dir ${dir} does not exist`);
1006
970
  const list2 = (0, import_fs8.readdirSync)(dir);
1007
971
  let renamed = 0, removed = 0, skipped = 0;
1008
972
  for (const [index, entry] of list2.entries()) {
1009
- spinner_default.text = `renaming in ${import_termkit8.Color.white.encoder(dir)} ${index + 1}/${list2.length}`;
973
+ spinner5.update(`renaming in ${import_termkit7.Color.white.encoder(dir)} ${index + 1}/${list2.length}`);
1010
974
  if (!(0, import_fs8.lstatSync)((0, import_path9.resolve)(dir, entry)).isDirectory()) {
1011
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
975
+ if (isVerbose()) spinner5.info(`skipped ${entry}`);
1012
976
  skipped++;
1013
977
  continue;
1014
978
  }
@@ -1018,7 +982,7 @@ var rename = async ({ dir: inputDir, type }) => {
1018
982
  const nameMatch = entry.match(/(?<=\[).+?(?=\])/);
1019
983
  const id = entry.split("-")[0];
1020
984
  if (!nameMatch || !id) {
1021
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
985
+ if (isVerbose()) spinner5.info(`skipped ${entry}`);
1022
986
  skipped++;
1023
987
  continue;
1024
988
  }
@@ -1026,19 +990,19 @@ var rename = async ({ dir: inputDir, type }) => {
1026
990
  const ps3New = (0, import_path9.resolve)(dir, `${nameMatch[0]} [${id}]`);
1027
991
  (0, import_fs8.renameSync)(ps3Old, ps3New);
1028
992
  recordRename(sessionId, ps3Old, ps3New);
1029
- spinner_default.succeed(`${nameMatch[0]} [${id}]`);
993
+ spinner5.succeed(`${nameMatch[0]} [${id}]`);
1030
994
  renamed++;
1031
995
  continue;
1032
996
  }
1033
997
  const yearMatch = entry.match(/\([^\d]*(\d+)[^\d]*\)/);
1034
998
  if (!yearMatch) {
1035
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
999
+ if (isVerbose()) spinner5.info(`skipped ${entry}`);
1036
1000
  skipped++;
1037
1001
  continue;
1038
1002
  }
1039
1003
  const year = yearMatch[0];
1040
1004
  if (year.length !== 6) {
1041
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1005
+ if (isVerbose()) spinner5.info(`skipped ${entry}`);
1042
1006
  skipped++;
1043
1007
  continue;
1044
1008
  }
@@ -1049,20 +1013,20 @@ var rename = async ({ dir: inputDir, type }) => {
1049
1013
  return videoExtensions_default.includes(ext2) && title.split(" ").reduce((a, w) => f.toLowerCase().includes(w.toLowerCase()) ? a : false, true);
1050
1014
  });
1051
1015
  if (!video) {
1052
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1016
+ if (isVerbose()) spinner5.info(`skipped ${entry}`);
1053
1017
  skipped++;
1054
1018
  continue;
1055
1019
  }
1056
1020
  const ext = video.match(/([^.]+$)/)?.[0];
1057
1021
  if (!ext) {
1058
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1022
+ if (isVerbose()) spinner5.info(`skipped ${entry}`);
1059
1023
  skipped++;
1060
1024
  continue;
1061
1025
  }
1062
1026
  const yearNum = parseInt(year.replace(/\D/g, ""));
1063
1027
  const formatted = formatMovieName(movieFormat, title, yearNum);
1064
1028
  if (entry === formatted && video === `${formatted}.${ext}`) {
1065
- if (isVerbose()) spinner_default.info(`skipped ${entry}`);
1029
+ if (isVerbose()) spinner5.info(`skipped ${entry}`);
1066
1030
  skipped++;
1067
1031
  continue;
1068
1032
  }
@@ -1085,25 +1049,26 @@ var rename = async ({ dir: inputDir, type }) => {
1085
1049
  (0, import_fs8.renameSync)(folderOld, folderNew);
1086
1050
  recordRename(sessionId, fileOld, fileNew);
1087
1051
  recordRename(sessionId, folderOld, folderNew);
1088
- spinner_default.succeed(formatted);
1052
+ spinner5.succeed(formatted);
1089
1053
  renamed++;
1090
1054
  }
1091
- spinner_default.succeed(`renamed ${renamed} files`);
1092
- if (removed) spinner_default.info(`removed ${removed} files`);
1093
- spinner_default.info(`skipped ${skipped} files`);
1094
- spinner_default.succeed(`done in ${import_termkit8.Color.green.encoder(dir)}`);
1095
- spinner_default.stop();
1055
+ spinner5.succeed(`renamed ${renamed} files`);
1056
+ if (removed) spinner5.info(`removed ${removed} files`);
1057
+ spinner5.info(`skipped ${skipped} files`);
1058
+ spinner5.succeed(`done in ${import_termkit7.Color.green.encoder(dir)}`);
1059
+ spinner5.stop();
1096
1060
  };
1097
1061
  var rename_default = rename;
1098
1062
 
1099
1063
  // src/actions/reset.ts
1100
1064
  var import_fs9 = require("fs");
1101
1065
  var import_path10 = require("path");
1102
- var import_termkit9 = require("termkit");
1066
+ var import_termkit8 = require("termkit");
1067
+ var spinner6 = new import_termkit8.Spinner();
1103
1068
  var reset = async ({ dir: inputDir, double }) => {
1104
1069
  let dir = inputDir;
1105
- spinner_default.text = `resetting episodes in ${import_termkit9.Color.white.encoder(dir)}`;
1106
- spinner_default.start();
1070
+ spinner6.update(`resetting episodes in ${import_termkit8.Color.white.encoder(dir)}`);
1071
+ spinner6.start();
1107
1072
  dir = (0, import_path10.resolve)(dir);
1108
1073
  if (!(0, import_fs9.existsSync)(dir)) throw new Error(`dir ${dir} does not exist`);
1109
1074
  const list2 = (0, import_fs9.readdirSync)(dir).sort();
@@ -1119,7 +1084,7 @@ var reset = async ({ dir: inputDir, double }) => {
1119
1084
  if (!seasonNum) throw new Error(`unable to identify season number`);
1120
1085
  const parentFolder = (0, import_path10.basename)((0, import_path10.dirname)(dir));
1121
1086
  const showTitle = parentFolder.match(/^(.+?)\s*(?:\(\d{4}\))?$/)?.[1]?.trim() || void 0;
1122
- spinner_default.info(`identified as season ${seasonNum}${showTitle ? ` of ${showTitle}` : ""}`);
1087
+ spinner6.info(`identified as season ${seasonNum}${showTitle ? ` of ${showTitle}` : ""}`);
1123
1088
  const sublist = list2.filter((f) => {
1124
1089
  const ext = f.match(/([^.]+$)/)?.[0];
1125
1090
  return videoExtensions_default.includes(ext) && f.split(" ").reduce((a, w) => f.toLowerCase().includes(w.toLowerCase()) ? a : false, true);
@@ -1131,7 +1096,7 @@ var reset = async ({ dir: inputDir, double }) => {
1131
1096
  const episodeFormat = getConfig().format?.episode;
1132
1097
  let renamed = 0, skipped = other.length;
1133
1098
  for (const [index, i] of sublist.entries()) {
1134
- spinner_default.text = `resetting episodes in ${import_termkit9.Color.white.encoder(dir)} ${index}/${list2.length}`;
1099
+ spinner6.update(`resetting episodes in ${import_termkit8.Color.white.encoder(dir)} ${index}/${list2.length}`);
1135
1100
  const ext = i.match(/([^.]+$)/)?.[0];
1136
1101
  const episode = double ? index * 2 + 1 : index + 1;
1137
1102
  const name = `${formatEpisode(seasonNum, episode, episodeFormat, double, showTitle)}.${ext}`;
@@ -1142,17 +1107,17 @@ var reset = async ({ dir: inputDir, double }) => {
1142
1107
  (0, import_fs9.renameSync)((0, import_path10.resolve)(dir, i), (0, import_path10.resolve)(dir, name));
1143
1108
  renamed++;
1144
1109
  }
1145
- spinner_default.succeed(`renamed ${renamed} files`);
1146
- spinner_default.info(`skipped ${skipped} files`);
1147
- spinner_default.succeed(`done in ${import_termkit9.Color.green.encoder(dir)}`);
1148
- spinner_default.stop();
1110
+ spinner6.succeed(`renamed ${renamed} files`);
1111
+ spinner6.info(`skipped ${skipped} files`);
1112
+ spinner6.succeed(`done in ${import_termkit8.Color.green.encoder(dir)}`);
1113
+ spinner6.stop();
1149
1114
  };
1150
1115
  var reset_default = reset;
1151
1116
 
1152
1117
  // src/actions/scan.ts
1153
1118
  var import_fs11 = require("fs");
1154
1119
  var import_path12 = require("path");
1155
- var import_termkit10 = require("termkit");
1120
+ var import_termkit9 = require("termkit");
1156
1121
 
1157
1122
  // src/helpers/detectEdition.ts
1158
1123
  var EDITIONS = [
@@ -1313,6 +1278,7 @@ var searchTv = async (title, apiKey) => {
1313
1278
  var bookExtensions_default = ["epub", "mobi", "azw3", "azw"];
1314
1279
 
1315
1280
  // src/actions/scan.ts
1281
+ var spinner7 = new import_termkit9.Spinner();
1316
1282
  var sameDev = (a, b) => {
1317
1283
  try {
1318
1284
  let bExisting = b;
@@ -1445,46 +1411,49 @@ var gatherEntries = (source) => {
1445
1411
  }
1446
1412
  return result;
1447
1413
  };
1448
- var findShowFolder = (destRoot, title) => {
1449
- if (!(0, import_fs11.existsSync)(destRoot)) return null;
1450
- const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1451
- const target = normalize(title);
1452
- return (0, import_fs11.readdirSync)(destRoot).filter((f) => {
1414
+ var nTitle = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1415
+ var showDirIndexCache = /* @__PURE__ */ new Map();
1416
+ var getShowDirIndex = (destRoot) => {
1417
+ if (showDirIndexCache.has(destRoot)) return showDirIndexCache.get(destRoot);
1418
+ const idx = /* @__PURE__ */ new Map();
1419
+ showDirIndexCache.set(destRoot, idx);
1420
+ if (!(0, import_fs11.existsSync)(destRoot)) return idx;
1421
+ let dirs;
1422
+ try {
1423
+ dirs = (0, import_fs11.readdirSync)(destRoot);
1424
+ } catch {
1425
+ return idx;
1426
+ }
1427
+ for (const dir of dirs) {
1453
1428
  try {
1454
- return (0, import_fs11.lstatSync)((0, import_path12.resolve)(destRoot, f)).isDirectory();
1429
+ if (!(0, import_fs11.lstatSync)((0, import_path12.resolve)(destRoot, dir)).isDirectory()) continue;
1455
1430
  } catch {
1456
- return false;
1431
+ continue;
1457
1432
  }
1458
- }).find((f) => normalize(f) === target) ?? null;
1459
- };
1460
- var findShowFolderByContent = (destRoot, title) => {
1461
- if (!(0, import_fs11.existsSync)(destRoot)) return null;
1462
- const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
1463
- const target = normalize(title);
1464
- const matchesTitle = (name) => {
1465
- if (!isTvEpisodeName(name)) return false;
1466
- const p = parseDownloadName(name);
1467
- return !!p && normalize(p.title) === target;
1468
- };
1469
- for (const folder of (0, import_fs11.readdirSync)(destRoot)) {
1433
+ idx.set(nTitle(dir), dir);
1470
1434
  try {
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);
1474
- if (children.some(matchesTitle)) return folder;
1475
- for (const child of children) {
1476
- if (!isSeasonDirName(child)) continue;
1435
+ const children = (0, import_fs11.readdirSync)((0, import_path12.resolve)(destRoot, dir));
1436
+ const tryFile = (name) => {
1437
+ if (!isTvEpisodeName(name)) return;
1438
+ const p = parseDownloadName(name);
1439
+ if (p) {
1440
+ const k = nTitle(p.title);
1441
+ if (!idx.has(k)) idx.set(k, dir);
1442
+ }
1443
+ };
1444
+ children.forEach(tryFile);
1445
+ children.forEach((child) => {
1446
+ if (!isSeasonDirName(child)) return;
1477
1447
  try {
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;
1448
+ const sp = (0, import_path12.resolve)(destRoot, dir, child);
1449
+ if ((0, import_fs11.lstatSync)(sp).isDirectory()) (0, import_fs11.readdirSync)(sp).forEach(tryFile);
1481
1450
  } catch {
1482
1451
  }
1483
- }
1452
+ });
1484
1453
  } catch {
1485
1454
  }
1486
1455
  }
1487
- return null;
1456
+ return idx;
1488
1457
  };
1489
1458
  var findSeasonFolder = (showPath, season, specialsFolder) => {
1490
1459
  if (!(0, import_fs11.existsSync)(showPath)) return null;
@@ -1514,13 +1483,17 @@ var classifyMovieConfidence = (entry) => {
1514
1483
  return "ambiguous";
1515
1484
  };
1516
1485
  var typeColor = {
1517
- movie: (s) => import_termkit10.Color.cyan.encoder(s),
1518
- tv: (s) => import_termkit10.Color.green.encoder(s),
1519
- book: (s) => import_termkit10.Color.yellow.encoder(s),
1520
- ps3: (s) => import_termkit10.Color.magenta.encoder(s)
1521
- };
1522
- var typeGlyph = (t) => typeColor[t]("\u25CF");
1523
- var typeTag = (t) => isVerbose() ? import_termkit10.Color.white.faint.encoder(` (${t})`) : "";
1486
+ movie: (s) => import_termkit9.Color.yellow.encoder(s),
1487
+ tv: (s) => import_termkit9.Color.blue.encoder(s),
1488
+ book: (s) => import_termkit9.Color.white.encoder(s),
1489
+ ps3: (s) => import_termkit9.Color.magenta.encoder(s)
1490
+ };
1491
+ var typeGlyph = (t) => typeColor[t]("?");
1492
+ var checkGlyph = (t) => typeColor[t]("\u2714");
1493
+ var greyGlyph = import_termkit9.Color.white.faint.encoder("\u25CF");
1494
+ var warnGlyph = import_termkit9.Color.yellow.encoder("\u26A0");
1495
+ var typeTag = (t) => isVerbose() ? import_termkit9.Color.white.faint.encoder(` (${t})`) : "";
1496
+ var sortByEntry = (arr) => arr.sort((a, b) => a.entry.localeCompare(b.entry, void 0, { sensitivity: "base" }));
1524
1497
  var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactive }) => {
1525
1498
  const config = getConfig();
1526
1499
  const sessionId = (/* @__PURE__ */ new Date()).toISOString();
@@ -1528,27 +1501,28 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1528
1501
  const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
1529
1502
  const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
1530
1503
  const specialsFolder = config.specialsFolder ?? "Specials";
1504
+ const dryTag = dryRun ? import_termkit9.Color.white.faint.encoder(" [dry]") : "";
1531
1505
  const lookupMovie = async (parsed) => {
1532
1506
  let tmdbId;
1533
1507
  let resolvedTitle = parsed.title;
1534
1508
  let resolvedYear = parsed.year;
1535
1509
  if (config.tmdbApiKey) {
1536
- spinner_default.text = `TMDb: ${parsed.title}`;
1510
+ spinner7.update(`TMDb: ${parsed.title}`);
1537
1511
  const results = await searchMovie(parsed.title, parsed.year, config.tmdbApiKey);
1538
1512
  if (results.length === 1) {
1539
1513
  tmdbId = results[0].id;
1540
1514
  resolvedTitle = results[0].title;
1541
1515
  resolvedYear = results[0].year ?? parsed.year;
1542
1516
  } else if (results.length > 1) {
1543
- spinner_default.stop();
1544
- const select = new import_termkit10.Select();
1517
+ spinner7.stop();
1518
+ const select = new import_termkit9.Select();
1545
1519
  const items = results.map((r) => ({
1546
1520
  label: r.year ? `${r.title} (${r.year})` : r.title,
1547
1521
  description: [r.overview?.slice(0, 60), hyperlink(r.url, "themoviedb.org")].filter(Boolean).join(" \xB7 "),
1548
1522
  ...r
1549
1523
  }));
1550
1524
  const picked = await select.ask(`Multiple movies found for "${parsed.title}":`, items);
1551
- spinner_default.start();
1525
+ spinner7.start();
1552
1526
  if (picked) {
1553
1527
  tmdbId = picked.id;
1554
1528
  resolvedTitle = picked.title;
@@ -1563,12 +1537,12 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1563
1537
  const folderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear, edition);
1564
1538
  const destFolder = (0, import_path12.resolve)(destRoot, folderName);
1565
1539
  if ((0, import_fs11.existsSync)(destFolder)) {
1566
- spinner_default.warn(`already exists: ${folderName}`);
1540
+ spinner7.log(`${typeColor.movie(folderName)}${typeTag("movie")}`, greyGlyph);
1567
1541
  return false;
1568
1542
  }
1569
1543
  const videoFile = isDir ? findVideo(entryPath) : entry;
1570
1544
  if (!videoFile) {
1571
- if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
1545
+ spinner7.log(`${entry}${typeTag("movie")}`, warnGlyph);
1572
1546
  return false;
1573
1547
  }
1574
1548
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
@@ -1589,7 +1563,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1589
1563
  (0, import_fs11.linkSync)(videoSourcePath, destVideoPath);
1590
1564
  mode = "hardlink";
1591
1565
  } catch {
1592
- spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
1566
+ spinner7.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
1593
1567
  (0, import_fs11.cpSync)(videoSourcePath, destVideoPath);
1594
1568
  mode = "copy";
1595
1569
  }
@@ -1615,10 +1589,10 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1615
1589
  recordImport(sessionId, entryPath, destFolder, "move", tmdbId, "movie");
1616
1590
  }
1617
1591
  }
1618
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("movie")} ${typeColor.movie(folderName)}${typeTag("movie")}`);
1592
+ spinner7.log(`${typeColor.movie(folderName)}${typeTag("movie")}${dryTag}`, checkGlyph("movie"));
1619
1593
  return true;
1620
1594
  };
1621
- spinner_default.start();
1595
+ spinner7.start();
1622
1596
  if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
1623
1597
  let imported = 0, skipped = 0;
1624
1598
  const pendingMovies = [];
@@ -1629,15 +1603,19 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1629
1603
  const seenIgnored = /* @__PURE__ */ new Set();
1630
1604
  for (const source of config.sources) {
1631
1605
  if (!(0, import_fs11.existsSync)(source)) {
1632
- spinner_default.warn(`source not found: ${import_termkit10.Color.white.encoder(source)}`);
1606
+ spinner7.warn(`source not found: ${import_termkit9.Color.white.encoder(source)}`);
1633
1607
  continue;
1634
1608
  }
1635
- spinner_default.text = `scanning ${import_termkit10.Color.white.encoder(source)}`;
1636
- for (const { entry, entryPath, isDir } of gatherEntries(source)) {
1637
- spinner_default.text = `scanning: ${entry}`;
1609
+ spinner7.update(`scanning ${import_termkit9.Color.white.encoder(source)}`);
1610
+ const entries = gatherEntries(source).sort(
1611
+ (a, b) => a.entry.localeCompare(b.entry, void 0, { sensitivity: "base" })
1612
+ );
1613
+ for (const { entry, entryPath, isDir } of entries) {
1614
+ spinner7.update(`scanning: ${entry}`);
1638
1615
  if (ignoreSet.has(entry)) {
1639
1616
  seenIgnored.add(entry);
1640
- if (isVerbose()) spinner_default.info(`ignored: ${entry}`);
1617
+ if (isVerbose()) spinner7.log(entry, greyGlyph);
1618
+ skipped++;
1641
1619
  continue;
1642
1620
  }
1643
1621
  const ext = entry.match(/([^.]+$)/)?.[0];
@@ -1657,7 +1635,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1657
1635
  }
1658
1636
  const destRoot = config.dest[detectedType];
1659
1637
  if (!destRoot) {
1660
- if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
1638
+ if (isVerbose()) spinner7.log(`${entry}${typeTag(detectedType)}`, greyGlyph);
1661
1639
  skipped++;
1662
1640
  continue;
1663
1641
  }
@@ -1671,7 +1649,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1671
1649
  const destName = `${nameMatch[0]} [${id}]`;
1672
1650
  const destPath = (0, import_path12.resolve)(destRoot, destName);
1673
1651
  if ((0, import_fs11.existsSync)(destPath)) {
1674
- spinner_default.warn(`already exists: ${destName}`);
1652
+ spinner7.log(`${typeColor.ps3(destName)}${typeTag("ps3")}`, greyGlyph);
1675
1653
  skipped++;
1676
1654
  continue;
1677
1655
  }
@@ -1679,14 +1657,14 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1679
1657
  moveFolder(entryPath, destPath);
1680
1658
  recordImport(sessionId, entryPath, destPath, "move", void 0, "ps3");
1681
1659
  }
1682
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("ps3")} ${typeColor.ps3(destName)}${typeTag("ps3")}`);
1660
+ spinner7.log(`${typeColor.ps3(destName)}${typeTag("ps3")}${dryTag}`, checkGlyph("ps3"));
1683
1661
  imported++;
1684
1662
  continue;
1685
1663
  }
1686
1664
  if (detectedType === "book") {
1687
1665
  const destPath = (0, import_path12.resolve)(destRoot, entry);
1688
1666
  if ((0, import_fs11.existsSync)(destPath)) {
1689
- spinner_default.warn(`already exists: ${entry}`);
1667
+ spinner7.log(`${typeColor.book(entry)}${typeTag("book")}`, greyGlyph);
1690
1668
  skipped++;
1691
1669
  continue;
1692
1670
  }
@@ -1704,7 +1682,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1704
1682
  }
1705
1683
  recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
1706
1684
  }
1707
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("book")} ${typeColor.book(entry)}${typeTag("book")}`);
1685
+ spinner7.log(`${typeColor.book(entry)}${typeTag("book")}${dryTag}`, checkGlyph("book"));
1708
1686
  imported++;
1709
1687
  continue;
1710
1688
  }
@@ -1713,10 +1691,10 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1713
1691
  if (videoCount === 0) {
1714
1692
  if (containsPdf(entryPath)) {
1715
1693
  pendingBooks.push({ entry, entryPath });
1716
- } else if (isVerbose()) {
1717
- spinner_default.info(`no media found, skipped: ${entry}`);
1694
+ } else {
1695
+ if (isVerbose()) spinner7.log(entry, greyGlyph);
1696
+ skipped++;
1718
1697
  }
1719
- skipped++;
1720
1698
  continue;
1721
1699
  }
1722
1700
  if (videoCount >= 2) {
@@ -1726,14 +1704,14 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1726
1704
  }
1727
1705
  const parsed = parseDownloadName(entry);
1728
1706
  if (!parsed) {
1729
- if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
1707
+ if (isVerbose()) spinner7.log(entry, greyGlyph);
1730
1708
  skipped++;
1731
1709
  continue;
1732
1710
  }
1733
1711
  if (detectedType === "movie") {
1734
1712
  const confidence = classifyMovieConfidence(entry);
1735
1713
  if (confidence === "skip") {
1736
- if (isVerbose()) spinner_default.info(`skipped (uncertain): ${entry}`);
1714
+ if (isVerbose()) spinner7.log(entry, greyGlyph);
1737
1715
  skipped++;
1738
1716
  continue;
1739
1717
  }
@@ -1747,22 +1725,22 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1747
1725
  let resolvedYear = parsed.year;
1748
1726
  if (config.tmdbApiKey) {
1749
1727
  if (detectedType === "tv") {
1750
- spinner_default.text = `TMDb: ${parsed.title}`;
1728
+ spinner7.update(`TMDb: ${parsed.title}`);
1751
1729
  const results = await searchTv(parsed.title, config.tmdbApiKey);
1752
1730
  if (results.length === 1) {
1753
1731
  tmdbId = results[0].id;
1754
1732
  resolvedTitle = results[0].title;
1755
1733
  resolvedYear = results[0].year ?? parsed.year;
1756
1734
  } else if (results.length > 1) {
1757
- spinner_default.stop();
1758
- const select = new import_termkit10.Select();
1735
+ spinner7.stop();
1736
+ const select = new import_termkit9.Select();
1759
1737
  const items = results.map((r) => ({
1760
1738
  label: r.year ? `${r.title} (${r.year})` : r.title,
1761
1739
  description: [r.overview?.slice(0, 60), hyperlink(r.url, "themoviedb.org")].filter(Boolean).join(" \xB7 "),
1762
1740
  ...r
1763
1741
  }));
1764
1742
  const picked = await select.ask(`Multiple shows found for "${parsed.title}":`, items);
1765
- spinner_default.start();
1743
+ spinner7.start();
1766
1744
  if (picked) {
1767
1745
  tmdbId = picked.id;
1768
1746
  resolvedTitle = picked.title;
@@ -1778,7 +1756,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1778
1756
  }
1779
1757
  if (detectedType === "tv") {
1780
1758
  if (parsed.season === void 0) {
1781
- if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
1759
+ if (isVerbose()) spinner7.log(entry, greyGlyph);
1782
1760
  skipped++;
1783
1761
  continue;
1784
1762
  }
@@ -1789,7 +1767,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1789
1767
  showPath = registeredShow.path;
1790
1768
  showFolderName = showPath.split("/").pop() ?? registeredShow.path;
1791
1769
  } else {
1792
- const existingFolder = findShowFolder(destRoot, resolvedTitle) ?? findShowFolderByContent(destRoot, resolvedTitle);
1770
+ const existingFolder = getShowDirIndex(destRoot).get(nTitle(resolvedTitle)) ?? null;
1793
1771
  if (existingFolder) {
1794
1772
  showFolderName = existingFolder;
1795
1773
  showPath = (0, import_path12.resolve)(destRoot, existingFolder);
@@ -1797,6 +1775,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1797
1775
  showFolderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear);
1798
1776
  showPath = (0, import_path12.resolve)(destRoot, showFolderName);
1799
1777
  if (!dryRun) upsertShow(showPath, tmdbId ?? null, resolvedTitle);
1778
+ getShowDirIndex(destRoot).set(nTitle(resolvedTitle), showFolderName);
1800
1779
  } else {
1801
1780
  pendingTv.push({ entry, entryPath, isDir, parsed, resolvedTitle, destRoot });
1802
1781
  continue;
@@ -1806,12 +1785,12 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1806
1785
  const seasonPath = (0, import_path12.resolve)(showPath, seasonFolderName);
1807
1786
  const videoFile = isDir ? findVideo(entryPath) : entry;
1808
1787
  if (!videoFile) {
1809
- if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
1788
+ spinner7.log(`${entry}${typeTag("tv")}`, warnGlyph);
1810
1789
  skipped++;
1811
1790
  continue;
1812
1791
  }
1813
1792
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
1814
- if (tmdbId && config.tmdbApiKey) spinner_default.text = `TMDb: episode name for ${resolvedTitle}`;
1793
+ if (tmdbId && config.tmdbApiKey) spinner7.update(`TMDb: episode name for ${resolvedTitle}`);
1815
1794
  const tmdbEpisodeName = tmdbId && config.tmdbApiKey ? await getEpisodeName(tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
1816
1795
  const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, resolvedTitle, tmdbEpisodeName ?? void 0);
1817
1796
  const destVideoName = `${episodeName}.${videoExt}`;
@@ -1821,17 +1800,17 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1821
1800
  const isRepack = /\brepack\d*\b|\bproper\b/i.test(entry);
1822
1801
  let shouldReplace = force || isRepack;
1823
1802
  if (!shouldReplace && interactive) {
1824
- spinner_default.stop();
1825
- const select = new import_termkit10.Select();
1803
+ spinner7.stop();
1804
+ const select = new import_termkit9.Select();
1826
1805
  const picked = await select.ask(`Already exists \u2014 replace?`, [
1827
1806
  { label: `${showFolderName} / ${seasonFolderName} / ${episodeName}`, value: "replace" },
1828
1807
  { label: "Skip", value: "skip" }
1829
1808
  ]);
1830
- spinner_default.start();
1809
+ spinner7.start();
1831
1810
  shouldReplace = picked?.value === "replace";
1832
1811
  }
1833
1812
  if (!shouldReplace) {
1834
- spinner_default.warn(`already exists: ${episodeName}`);
1813
+ spinner7.log(`${typeColor.tv(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`, greyGlyph);
1835
1814
  skipped++;
1836
1815
  continue;
1837
1816
  }
@@ -1855,7 +1834,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1855
1834
  (0, import_fs11.linkSync)(videoSourcePath, destVideoPath);
1856
1835
  mode = "hardlink";
1857
1836
  } catch {
1858
- spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
1837
+ spinner7.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
1859
1838
  (0, import_fs11.cpSync)(videoSourcePath, destVideoPath);
1860
1839
  mode = "copy";
1861
1840
  }
@@ -1872,7 +1851,7 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1872
1851
  }
1873
1852
  recordImport(sessionId, entryPath, seasonPath, mode, tmdbId, "tv");
1874
1853
  }
1875
- spinner_default.succeed(`${dryRun ? "[dry] " : ""}${typeGlyph("tv")} ${typeColor.tv(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}`);
1854
+ spinner7.log(`${typeColor.tv(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}${typeTag("tv")}${dryTag}`, checkGlyph("tv"));
1876
1855
  imported++;
1877
1856
  continue;
1878
1857
  }
@@ -1883,25 +1862,27 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1883
1862
  }
1884
1863
  }
1885
1864
  }
1865
+ let uncertainMovies = 0;
1886
1866
  if (pendingMovies.length > 0) {
1887
- spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
1888
- for (const p of pendingMovies) spinner_default.info(` ${typeGlyph("movie")} ${p.entry.replace(/\/$/, "")}${typeTag("movie")}`);
1889
1867
  let toProcess = [];
1890
1868
  if (interactive) {
1891
- spinner_default.stop();
1892
- const ms = new import_termkit10.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
1869
+ spinner7.stop();
1870
+ const ms = new import_termkit9.MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
1893
1871
  const items = pendingMovies.map((p) => ({
1894
1872
  label: p.entry.replace(/\/$/, ""),
1895
1873
  description: p.parsed.year ? `parsed: ${p.parsed.title} \xB7 ${p.parsed.year}` : `parsed: ${p.parsed.title}`,
1896
1874
  ...p
1897
1875
  }));
1898
1876
  toProcess = await ms.ask("Select entries to import as movies:", items) ?? [];
1899
- spinner_default.start();
1900
- skipped += pendingMovies.length - toProcess.length;
1877
+ spinner7.start();
1901
1878
  } else if (force) {
1902
1879
  toProcess = pendingMovies;
1903
- } else {
1904
- skipped += pendingMovies.length;
1880
+ }
1881
+ const toSkip = pendingMovies.filter((p) => !toProcess.includes(p));
1882
+ uncertainMovies = toSkip.length;
1883
+ sortByEntry(toSkip);
1884
+ for (const p of toSkip) {
1885
+ spinner7.log(`${typeColor.movie(p.entry.replace(/\/$/, ""))}${typeTag("movie")}`, typeGlyph("movie"));
1905
1886
  }
1906
1887
  for (const p of toProcess) {
1907
1888
  const { tmdbId, resolvedTitle, resolvedYear } = await lookupMovie(p.parsed);
@@ -1913,18 +1894,22 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1913
1894
  }
1914
1895
  }
1915
1896
  if (pendingTv.length > 0) {
1916
- spinner_default.warn(`${pendingTv.length} TV show${pendingTv.length > 1 ? "s" : ""} skipped \u2014 no matching folder in destination`);
1917
- for (const p of pendingTv) spinner_default.info(` ${typeGlyph("tv")} ${p.resolvedTitle} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`);
1918
- skipped += pendingTv.length;
1897
+ pendingTv.sort((a, b) => a.resolvedTitle.localeCompare(b.resolvedTitle, void 0, { sensitivity: "base" }));
1898
+ for (const p of pendingTv) {
1899
+ spinner7.log(`${typeColor.tv(p.resolvedTitle)} \u2014 ${p.entry.replace(/\/$/, "")}${typeTag("tv")}`, typeGlyph("tv"));
1900
+ }
1919
1901
  }
1920
1902
  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")}`);
1903
+ sortByEntry(pendingBooks);
1904
+ for (const p of pendingBooks) {
1905
+ spinner7.log(`${typeColor.book(p.entry)}${typeTag("book")}`, typeGlyph("book"));
1906
+ }
1923
1907
  }
1924
1908
  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;
1909
+ sortByEntry(pendingAnime);
1910
+ for (const p of pendingAnime) {
1911
+ spinner7.log(`${typeColor.tv(p.entry)} (${p.videoCount} video${p.videoCount > 1 ? "s" : ""})${typeTag("tv")}`, typeGlyph("tv"));
1912
+ }
1928
1913
  }
1929
1914
  if (ignoreSet.size > 0) {
1930
1915
  const stale = [...ignoreSet].filter((name) => !seenIgnored.has(name));
@@ -1932,25 +1917,31 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, auto, force, interactiv
1932
1917
  const updated = config.ignore.filter((name) => !stale.includes(name));
1933
1918
  config.ignore = updated;
1934
1919
  saveConfig(config);
1935
- for (const name of stale) spinner_default.info(`removed from ignore list (not found): ${import_termkit10.Color.white.encoder(name)}`);
1920
+ for (const name of stale) spinner7.info(`removed from ignore list (not found): ${import_termkit9.Color.white.encoder(name)}`);
1936
1921
  }
1937
1922
  }
1938
- spinner_default.succeed(`${dryRun ? "would import" : "imported"} ${imported} items`);
1939
- if (skipped) spinner_default.info(`skipped ${skipped} items`);
1940
- spinner_default.stop();
1923
+ const summaryParts = [`${dryRun ? "would import" : "imported"} ${imported}`];
1924
+ if (skipped) summaryParts.push(`skipped ${skipped}`);
1925
+ if (uncertainMovies) summaryParts.push(`uncertain movie ${uncertainMovies}`);
1926
+ if (pendingTv.length) summaryParts.push(`uncertain tv ${pendingTv.length}`);
1927
+ if (pendingBooks.length) summaryParts.push(`uncertain book ${pendingBooks.length}`);
1928
+ if (pendingAnime.length) summaryParts.push(`uncertain anime ${pendingAnime.length}`);
1929
+ spinner7.succeed(summaryParts.join(", "));
1930
+ spinner7.stop();
1941
1931
  };
1942
1932
  var scan_default = scan;
1943
1933
 
1944
1934
  // src/actions/undo.ts
1945
1935
  var import_fs12 = require("fs");
1946
- var import_termkit11 = require("termkit");
1936
+ var import_termkit10 = require("termkit");
1937
+ var spinner8 = new import_termkit10.Spinner();
1947
1938
  var undo = async () => {
1948
- spinner_default.start();
1939
+ spinner8.start();
1949
1940
  const renameRecords = getLastSession();
1950
1941
  const importRecords = getLastImportSession();
1951
1942
  if (renameRecords.length === 0 && importRecords.length === 0) {
1952
- spinner_default.info("nothing to undo");
1953
- spinner_default.stop();
1943
+ spinner8.info("nothing to undo");
1944
+ spinner8.stop();
1954
1945
  return;
1955
1946
  }
1956
1947
  const useImports = importRecords.length > 0 && (renameRecords.length === 0 || importRecords[0].sessionId > renameRecords[0].sessionId);
@@ -1958,40 +1949,40 @@ var undo = async () => {
1958
1949
  let undone2 = 0;
1959
1950
  for (const record of renameRecords) {
1960
1951
  (0, import_fs12.renameSync)(record.newPath, record.oldPath);
1961
- spinner_default.succeed(`${import_termkit11.Color.green.encoder(record.newPath)} \u2192 ${import_termkit11.Color.white.encoder(record.oldPath)}`);
1952
+ spinner8.succeed(`${import_termkit10.Color.green.encoder(record.newPath)} \u2192 ${import_termkit10.Color.white.encoder(record.oldPath)}`);
1962
1953
  undone2++;
1963
1954
  }
1964
1955
  deleteSession(renameRecords[0].sessionId);
1965
- spinner_default.succeed(`undid ${undone2} renames`);
1966
- spinner_default.stop();
1956
+ spinner8.succeed(`undid ${undone2} renames`);
1957
+ spinner8.stop();
1967
1958
  return;
1968
1959
  }
1969
1960
  let undone = 0;
1970
1961
  let skipped = 0;
1971
1962
  for (const record of importRecords) {
1972
1963
  if (record.mode !== "move") {
1973
- spinner_default.info(`skipped ${record.destinationPath} (${record.mode} \u2014 source file unchanged)`);
1964
+ spinner8.info(`skipped ${record.destinationPath} (${record.mode} \u2014 source file unchanged)`);
1974
1965
  skipped++;
1975
1966
  continue;
1976
1967
  }
1977
1968
  if (record.type === "tv") {
1978
- spinner_default.info(`skipped TV import \u2014 season folder cannot be cleanly reversed: ${record.destinationPath}`);
1969
+ spinner8.info(`skipped TV import \u2014 season folder cannot be cleanly reversed: ${record.destinationPath}`);
1979
1970
  skipped++;
1980
1971
  continue;
1981
1972
  }
1982
1973
  if (!(0, import_fs12.existsSync)(record.destinationPath)) {
1983
- spinner_default.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
1974
+ spinner8.info(`skipped \u2014 destination no longer exists: ${record.destinationPath}`);
1984
1975
  skipped++;
1985
1976
  continue;
1986
1977
  }
1987
1978
  (0, import_fs12.renameSync)(record.destinationPath, record.sourcePath);
1988
- spinner_default.succeed(`${import_termkit11.Color.green.encoder(record.destinationPath)} \u2192 ${import_termkit11.Color.white.encoder(record.sourcePath)}`);
1979
+ spinner8.succeed(`${import_termkit10.Color.green.encoder(record.destinationPath)} \u2192 ${import_termkit10.Color.white.encoder(record.sourcePath)}`);
1989
1980
  undone++;
1990
1981
  }
1991
1982
  deleteImportSession(importRecords[0].sessionId);
1992
- if (undone > 0) spinner_default.succeed(`undid ${undone} import${undone !== 1 ? "s" : ""}`);
1993
- if (skipped > 0) spinner_default.info(`skipped ${skipped} item${skipped !== 1 ? "s" : ""} (TV or non-move mode)`);
1994
- spinner_default.stop();
1983
+ if (undone > 0) spinner8.succeed(`undid ${undone} import${undone !== 1 ? "s" : ""}`);
1984
+ if (skipped > 0) spinner8.info(`skipped ${skipped} item${skipped !== 1 ? "s" : ""} (TV or non-move mode)`);
1985
+ spinner8.stop();
1995
1986
  };
1996
1987
  var undo_default = undo;
1997
1988
 
@@ -1999,7 +1990,8 @@ var undo_default = undo;
1999
1990
  var import_chokidar = __toESM(require("chokidar"));
2000
1991
  var import_fs13 = require("fs");
2001
1992
  var import_path13 = require("path");
2002
- var import_termkit12 = require("termkit");
1993
+ var import_termkit11 = require("termkit");
1994
+ var spinner9 = new import_termkit11.Spinner();
2003
1995
  var sameDev2 = (a, b) => {
2004
1996
  try {
2005
1997
  let bExisting = b;
@@ -2139,7 +2131,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2139
2131
  }
2140
2132
  const destRoot = config.dest[detectedType];
2141
2133
  if (!destRoot) {
2142
- if (isVerbose()) spinner_default.info(`no ${detectedType} destination configured, skipped: ${entry}`);
2134
+ if (isVerbose()) spinner9.info(`no ${detectedType} destination configured, skipped: ${entry}`);
2143
2135
  return;
2144
2136
  }
2145
2137
  if (detectedType === "ps3") {
@@ -2149,18 +2141,18 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2149
2141
  const destName = `${nameMatch[0]} [${id}]`;
2150
2142
  const destPath = (0, import_path13.resolve)(destRoot, destName);
2151
2143
  if ((0, import_fs13.existsSync)(destPath)) {
2152
- spinner_default.warn(`already exists: ${destName}`);
2144
+ spinner9.warn(`already exists: ${destName}`);
2153
2145
  return;
2154
2146
  }
2155
2147
  moveItem(entryPath, destPath);
2156
2148
  recordImport(sessionId, entryPath, destPath, "move", void 0, "ps3");
2157
- spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(destName)}`);
2149
+ spinner9.succeed(`imported ${import_termkit11.Color.green.encoder(destName)}`);
2158
2150
  return;
2159
2151
  }
2160
2152
  if (detectedType === "book") {
2161
2153
  const destPath = (0, import_path13.resolve)(destRoot, entry);
2162
2154
  if ((0, import_fs13.existsSync)(destPath)) {
2163
- spinner_default.warn(`already exists: ${entry}`);
2155
+ spinner9.warn(`already exists: ${entry}`);
2164
2156
  return;
2165
2157
  }
2166
2158
  if (isDir || isBookDir) {
@@ -2175,17 +2167,17 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2175
2167
  }
2176
2168
  }
2177
2169
  recordImport(sessionId, entryPath, destPath, "move", void 0, "book");
2178
- spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(entry)}`);
2170
+ spinner9.succeed(`imported ${import_termkit11.Color.green.encoder(entry)}`);
2179
2171
  return;
2180
2172
  }
2181
2173
  const parsed = parseDownloadName(entry);
2182
2174
  if (!parsed) {
2183
- if (isVerbose()) spinner_default.info(`could not parse: ${entry}`);
2175
+ if (isVerbose()) spinner9.info(`could not parse: ${entry}`);
2184
2176
  return;
2185
2177
  }
2186
2178
  if (detectedType === "tv") {
2187
2179
  if (parsed.season === void 0) {
2188
- if (isVerbose()) spinner_default.info(`could not detect season from: ${entry}`);
2180
+ if (isVerbose()) spinner9.info(`could not detect season from: ${entry}`);
2189
2181
  return;
2190
2182
  }
2191
2183
  const registeredShow = getShowByTitle(parsed.title);
@@ -2199,14 +2191,14 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2199
2191
  showPath = (0, import_path13.resolve)(destRoot, showFolderName);
2200
2192
  upsertShow(showPath, null, parsed.title);
2201
2193
  } else {
2202
- if (isVerbose()) spinner_default.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
2194
+ if (isVerbose()) spinner9.info(`not registered, skipped: ${parsed.title} \u2014 run: reelsort add "${parsed.title}"`);
2203
2195
  return;
2204
2196
  }
2205
2197
  const seasonFolderName = findSeasonFolder2(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
2206
2198
  const seasonPath = (0, import_path13.resolve)(showPath, seasonFolderName);
2207
2199
  const videoFile2 = isDir ? findVideo2(entryPath) : entry;
2208
2200
  if (!videoFile2) {
2209
- if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
2201
+ if (isVerbose()) spinner9.info(`no video found in: ${entry}`);
2210
2202
  return;
2211
2203
  }
2212
2204
  const videoExt2 = videoFile2.match(/([^.]+$)/)?.[0];
@@ -2216,7 +2208,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2216
2208
  const destVideoPath = (0, import_path13.resolve)(seasonPath, destVideoName2);
2217
2209
  const videoSourcePath2 = isDir ? (0, import_path13.resolve)(entryPath, videoFile2) : entryPath;
2218
2210
  if ((0, import_fs13.existsSync)(destVideoPath)) {
2219
- spinner_default.warn(`already exists: ${episodeName}`);
2211
+ spinner9.warn(`already exists: ${episodeName}`);
2220
2212
  return;
2221
2213
  }
2222
2214
  const dirFiles2 = isDir ? (0, import_fs13.readdirSync)(entryPath) : [];
@@ -2232,7 +2224,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2232
2224
  (0, import_fs13.linkSync)(videoSourcePath2, destVideoPath);
2233
2225
  mode = "hardlink";
2234
2226
  } catch {
2235
- spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
2227
+ spinner9.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
2236
2228
  (0, import_fs13.cpSync)(videoSourcePath2, destVideoPath);
2237
2229
  mode = "copy";
2238
2230
  }
@@ -2248,19 +2240,19 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2248
2240
  if (isDir) (0, import_fs13.rmSync)(entryPath, { recursive: true, force: true });
2249
2241
  }
2250
2242
  recordImport(sessionId, entryPath, seasonPath, mode, void 0, "tv");
2251
- spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
2243
+ spinner9.succeed(`imported ${import_termkit11.Color.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
2252
2244
  return;
2253
2245
  }
2254
2246
  const edition = detectEdition(entry);
2255
2247
  const folderName = formatMovieName(movieFormat, parsed.title, parsed.year, edition);
2256
2248
  const destFolder = (0, import_path13.resolve)(destRoot, folderName);
2257
2249
  if ((0, import_fs13.existsSync)(destFolder)) {
2258
- spinner_default.warn(`already exists: ${folderName}`);
2250
+ spinner9.warn(`already exists: ${folderName}`);
2259
2251
  return;
2260
2252
  }
2261
2253
  const videoFile = isDir ? findVideo2(entryPath) : entry;
2262
2254
  if (!videoFile) {
2263
- if (isVerbose()) spinner_default.info(`no video found in: ${entry}`);
2255
+ if (isVerbose()) spinner9.info(`no video found in: ${entry}`);
2264
2256
  return;
2265
2257
  }
2266
2258
  const videoExt = videoFile.match(/([^.]+$)/)?.[0];
@@ -2280,7 +2272,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2280
2272
  (0, import_fs13.linkSync)(videoSourcePath, destVideoPath);
2281
2273
  mode = "hardlink";
2282
2274
  } catch {
2283
- spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
2275
+ spinner9.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
2284
2276
  (0, import_fs13.cpSync)(videoSourcePath, destVideoPath);
2285
2277
  mode = "copy";
2286
2278
  }
@@ -2305,7 +2297,7 @@ var processItem = async (entryPath, useHardlink, language, auto) => {
2305
2297
  }
2306
2298
  recordImport(sessionId, entryPath, destFolder, "move", void 0, "movie");
2307
2299
  }
2308
- spinner_default.succeed(`imported ${import_termkit12.Color.green.encoder(folderName)}`);
2300
+ spinner9.succeed(`imported ${import_termkit11.Color.green.encoder(folderName)}`);
2309
2301
  };
2310
2302
  var watch = async ({ hardlink = false, auto = false }) => {
2311
2303
  const config = getConfig();
@@ -2324,7 +2316,7 @@ var watch = async ({ hardlink = false, auto = false }) => {
2324
2316
  await processItem(entry, hardlink, language, auto);
2325
2317
  }
2326
2318
  } catch (err) {
2327
- spinner_default.fail(`error processing ${path}: ${err.message}`);
2319
+ spinner9.fail(`error processing ${path}: ${err.message}`);
2328
2320
  }
2329
2321
  }, 5e3)
2330
2322
  );
@@ -2336,10 +2328,10 @@ var watch = async ({ hardlink = false, auto = false }) => {
2336
2328
  });
2337
2329
  watcher.on("addDir", handle);
2338
2330
  watcher.on("add", handle);
2339
- spinner_default.start();
2340
- spinner_default.succeed(`watching ${config.sources.length} source${config.sources.length !== 1 ? "s" : ""}`);
2341
- for (const s of config.sources) spinner_default.info(` ${import_termkit12.Color.white.encoder(s)}`);
2342
- spinner_default.stop();
2331
+ spinner9.start();
2332
+ spinner9.succeed(`watching ${config.sources.length} source${config.sources.length !== 1 ? "s" : ""}`);
2333
+ for (const s of config.sources) spinner9.info(` ${import_termkit11.Color.white.encoder(s)}`);
2334
+ spinner9.stop();
2343
2335
  process.stdin.resume();
2344
2336
  };
2345
2337
  var watch_default = watch;