reelsort 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +321 -152
- package/dist/index.d.mts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +303 -138
- package/dist/index.mjs +304 -139
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -204,17 +204,17 @@ var clean = async ({ dryRun, olderThan }) => {
|
|
|
204
204
|
continue;
|
|
205
205
|
}
|
|
206
206
|
if (dryRun) {
|
|
207
|
-
spinner_default.succeed(`[dry] would remove ${Color.
|
|
207
|
+
spinner_default.succeed(`[dry] would remove ${Color.white.encoder(imp.sourcePath)}`);
|
|
208
208
|
cleaned++;
|
|
209
209
|
continue;
|
|
210
210
|
}
|
|
211
211
|
try {
|
|
212
212
|
rmSync(imp.sourcePath, { recursive: true, force: true });
|
|
213
213
|
deleteImport(imp.id);
|
|
214
|
-
spinner_default.succeed(`removed ${Color.
|
|
214
|
+
spinner_default.succeed(`removed ${Color.white.encoder(imp.sourcePath)}`);
|
|
215
215
|
cleaned++;
|
|
216
216
|
} catch {
|
|
217
|
-
spinner_default.warn(`locked or inaccessible, skipped: ${Color.
|
|
217
|
+
spinner_default.warn(`locked or inaccessible, skipped: ${Color.white.encoder(imp.sourcePath)}`);
|
|
218
218
|
skipped++;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
@@ -273,20 +273,20 @@ var formatMovieName = (template, title, year, edition) => {
|
|
|
273
273
|
};
|
|
274
274
|
|
|
275
275
|
// src/actions/config.ts
|
|
276
|
-
var DEST_TYPES = ["movie", "tv", "ps3"];
|
|
276
|
+
var DEST_TYPES = ["movie", "tv", "ps3", "book"];
|
|
277
277
|
var sourceAdd = async ({ dir }) => {
|
|
278
278
|
const resolved = resolve(dir);
|
|
279
279
|
const config = getConfig();
|
|
280
280
|
if (config.sources.includes(resolved)) {
|
|
281
281
|
spinner_default.start();
|
|
282
|
-
spinner_default.info(`source already configured: ${Color2.
|
|
282
|
+
spinner_default.info(`source already configured: ${Color2.white.encoder(resolved)}`);
|
|
283
283
|
spinner_default.stop();
|
|
284
284
|
return;
|
|
285
285
|
}
|
|
286
286
|
config.sources.push(resolved);
|
|
287
287
|
saveConfig(config);
|
|
288
288
|
spinner_default.start();
|
|
289
|
-
spinner_default.succeed(`added source: ${Color2.
|
|
289
|
+
spinner_default.succeed(`added source: ${Color2.white.encoder(resolved)}`);
|
|
290
290
|
spinner_default.stop();
|
|
291
291
|
};
|
|
292
292
|
var sourceRemove = async ({ dir }) => {
|
|
@@ -295,14 +295,14 @@ var sourceRemove = async ({ dir }) => {
|
|
|
295
295
|
const index = config.sources.indexOf(resolved);
|
|
296
296
|
if (index === -1) {
|
|
297
297
|
spinner_default.start();
|
|
298
|
-
spinner_default.warn(`source not found: ${Color2.
|
|
298
|
+
spinner_default.warn(`source not found: ${Color2.white.encoder(resolved)}`);
|
|
299
299
|
spinner_default.stop();
|
|
300
300
|
return;
|
|
301
301
|
}
|
|
302
302
|
config.sources.splice(index, 1);
|
|
303
303
|
saveConfig(config);
|
|
304
304
|
spinner_default.start();
|
|
305
|
-
spinner_default.succeed(`removed source: ${Color2.
|
|
305
|
+
spinner_default.succeed(`removed source: ${Color2.white.encoder(resolved)}`);
|
|
306
306
|
spinner_default.stop();
|
|
307
307
|
};
|
|
308
308
|
var destAdd = async ({ type, dir }) => {
|
|
@@ -314,7 +314,7 @@ var destAdd = async ({ type, dir }) => {
|
|
|
314
314
|
config.dest[type] = resolved;
|
|
315
315
|
saveConfig(config);
|
|
316
316
|
spinner_default.start();
|
|
317
|
-
spinner_default.succeed(`set ${type} destination: ${Color2.
|
|
317
|
+
spinner_default.succeed(`set ${type} destination: ${Color2.green.encoder(resolved)}`);
|
|
318
318
|
spinner_default.stop();
|
|
319
319
|
};
|
|
320
320
|
var destRemove = async ({ type }) => {
|
|
@@ -340,7 +340,7 @@ var configSet = async ({ key, subkey, value }) => {
|
|
|
340
340
|
config.language = subkey;
|
|
341
341
|
saveConfig(config);
|
|
342
342
|
spinner_default.start();
|
|
343
|
-
spinner_default.succeed(`set subtitle language: ${Color2.
|
|
343
|
+
spinner_default.succeed(`set subtitle language: ${Color2.green.encoder(subkey)}`);
|
|
344
344
|
spinner_default.stop();
|
|
345
345
|
return;
|
|
346
346
|
}
|
|
@@ -370,7 +370,7 @@ var configSet = async ({ key, subkey, value }) => {
|
|
|
370
370
|
}
|
|
371
371
|
saveConfig(config);
|
|
372
372
|
spinner_default.start();
|
|
373
|
-
spinner_default.succeed(`set ${subkey} format: ${Color2.
|
|
373
|
+
spinner_default.succeed(`set ${subkey} format: ${Color2.green.encoder(value ?? subkey)}`);
|
|
374
374
|
spinner_default.stop();
|
|
375
375
|
return;
|
|
376
376
|
}
|
|
@@ -382,7 +382,7 @@ var configShow = async () => {
|
|
|
382
382
|
if (config.sources.length === 0) {
|
|
383
383
|
console.log(" (none)");
|
|
384
384
|
} else {
|
|
385
|
-
for (const s of config.sources) console.log(` ${Color2.
|
|
385
|
+
for (const s of config.sources) console.log(` ${Color2.white.encoder(s)}`);
|
|
386
386
|
}
|
|
387
387
|
console.log("\nDestinations:");
|
|
388
388
|
const entries = DEST_TYPES.map((t) => ({ type: t, path: config.dest[t] })).filter((e) => e.path);
|
|
@@ -390,15 +390,15 @@ var configShow = async () => {
|
|
|
390
390
|
console.log(" (none)");
|
|
391
391
|
} else {
|
|
392
392
|
for (const { type, path } of entries) {
|
|
393
|
-
console.log(` ${type.padEnd(6)} ${Color2.
|
|
393
|
+
console.log(` ${type.padEnd(6)} ${Color2.green.encoder(path)}`);
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
396
|
console.log(`
|
|
397
|
-
Subtitle language: ${Color2.
|
|
397
|
+
Subtitle language: ${Color2.green.encoder(config.language ?? "eng (default)")}`);
|
|
398
398
|
console.log(`TMDb API key: ${config.tmdbApiKey ? Color2.green.encoder("configured") : Color2.red.encoder("not set")}`);
|
|
399
|
-
console.log(`Movie format: ${Color2.
|
|
400
|
-
console.log(`Episode format: ${Color2.
|
|
401
|
-
console.log(`Season folder: ${Color2.
|
|
399
|
+
console.log(`Movie format: ${Color2.green.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
|
|
400
|
+
console.log(`Episode format: ${Color2.green.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
|
|
401
|
+
console.log(`Season folder: ${Color2.green.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
|
|
402
402
|
console.log();
|
|
403
403
|
};
|
|
404
404
|
|
|
@@ -409,7 +409,7 @@ import { Color as Color3 } from "termkit";
|
|
|
409
409
|
var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore }) => {
|
|
410
410
|
let dir1 = rawDir1;
|
|
411
411
|
let dir2 = rawDir2;
|
|
412
|
-
spinner_default.text = `checking differences between ${Color3.
|
|
412
|
+
spinner_default.text = `checking differences between ${Color3.white.encoder(dir1)} and ${Color3.white.encoder(dir2)}`;
|
|
413
413
|
spinner_default.start();
|
|
414
414
|
dir1 = resolve2(dir1);
|
|
415
415
|
dir2 = resolve2(dir2);
|
|
@@ -445,7 +445,7 @@ var differences = async ({ dir1: rawDir1, dir2: rawDir2, only, ignore }) => {
|
|
|
445
445
|
removed.push(l);
|
|
446
446
|
}
|
|
447
447
|
}
|
|
448
|
-
spinner_default.succeed(`checked differences between ${Color3.
|
|
448
|
+
spinner_default.succeed(`checked differences between ${Color3.white.encoder(dir1)} and ${Color3.white.encoder(dir2)}`);
|
|
449
449
|
spinner_default.succeed(`found ${added.length} added files`);
|
|
450
450
|
spinner_default.succeed(`found ${removed.length} removed files`);
|
|
451
451
|
spinner_default.stop();
|
|
@@ -471,8 +471,8 @@ var history = async ({ limit, imports }) => {
|
|
|
471
471
|
${Color4.yellow.encoder(label)} (${session.records.length} item${session.records.length !== 1 ? "s" : ""})`);
|
|
472
472
|
for (const r of session.records) {
|
|
473
473
|
const src = basename(r.sourcePath);
|
|
474
|
-
const dest = Color4.
|
|
475
|
-
const mode = r.mode !== "move" ? ` ${Color4.
|
|
474
|
+
const dest = Color4.green.encoder(r.destinationPath);
|
|
475
|
+
const mode = r.mode !== "move" ? ` ${Color4.white.encoder(`[${r.mode}]`)}` : "";
|
|
476
476
|
console.log(` ${src} \u2192 ${dest}${mode}`);
|
|
477
477
|
}
|
|
478
478
|
}
|
|
@@ -491,7 +491,7 @@ ${Color4.yellow.encoder(label)} (${folders.length} item${folders.length !== 1 ?
|
|
|
491
491
|
for (const r of folders) {
|
|
492
492
|
const oldName = basename(r.oldPath);
|
|
493
493
|
const newName = basename(r.newPath);
|
|
494
|
-
console.log(` ${Color4.
|
|
494
|
+
console.log(` ${Color4.white.encoder(oldName)} \u2192 ${Color4.green.encoder(newName)}`);
|
|
495
495
|
}
|
|
496
496
|
}
|
|
497
497
|
}
|
|
@@ -642,7 +642,7 @@ var list = async ({ type, missingSubs, codec: codecFilter, resolution: resFilter
|
|
|
642
642
|
const destRoot = config.dest[t];
|
|
643
643
|
if (!existsSync5(destRoot)) {
|
|
644
644
|
console.log(`
|
|
645
|
-
${t.toUpperCase()} ${Color5.
|
|
645
|
+
${t.toUpperCase()} ${Color5.white.encoder(destRoot)} (not found)`);
|
|
646
646
|
continue;
|
|
647
647
|
}
|
|
648
648
|
const folders = readdirSync3(destRoot).filter((f) => {
|
|
@@ -673,7 +673,7 @@ ${t.toUpperCase()} ${Color5.blue.encoder(destRoot)} (not found)`);
|
|
|
673
673
|
return yearDiff !== 0 ? yearDiff : a.title.localeCompare(b.title);
|
|
674
674
|
});
|
|
675
675
|
console.log(`
|
|
676
|
-
${Color5.yellow.encoder(t.toUpperCase())} ${Color5.
|
|
676
|
+
${Color5.yellow.encoder(t.toUpperCase())} ${Color5.white.encoder(destRoot)}`);
|
|
677
677
|
new Table(
|
|
678
678
|
filtered.map((e) => ({
|
|
679
679
|
title: e.title,
|
|
@@ -781,7 +781,7 @@ var probe = async ({ type, force, verbose }) => {
|
|
|
781
781
|
for (const t of types) {
|
|
782
782
|
const destRoot = config.dest[t];
|
|
783
783
|
if (!existsSync6(destRoot)) continue;
|
|
784
|
-
spinner_default.text = `scanning ${Color6.
|
|
784
|
+
spinner_default.text = `scanning ${Color6.white.encoder(destRoot)}`;
|
|
785
785
|
const files = walkVideoFiles(destRoot);
|
|
786
786
|
for (const filePath of files) {
|
|
787
787
|
if (!force && getMediaInfo(filePath)) {
|
|
@@ -789,7 +789,7 @@ var probe = async ({ type, force, verbose }) => {
|
|
|
789
789
|
skipped++;
|
|
790
790
|
continue;
|
|
791
791
|
}
|
|
792
|
-
spinner_default.text = `probing ${Color6.
|
|
792
|
+
spinner_default.text = `probing ${Color6.white.encoder(filePath)}`;
|
|
793
793
|
const result = runFfprobe(filePath);
|
|
794
794
|
if (!result) {
|
|
795
795
|
if (verbose) spinner_default.warn(`ffprobe failed: ${filePath}`);
|
|
@@ -873,13 +873,13 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
873
873
|
const config = getConfig();
|
|
874
874
|
const language = config.language ?? "eng";
|
|
875
875
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
876
|
-
spinner_default.text = `renaming in ${Color7.
|
|
876
|
+
spinner_default.text = `renaming in ${Color7.white.encoder(dir)}`;
|
|
877
877
|
spinner_default.start();
|
|
878
878
|
if (!existsSync7(dir)) throw new Error(`dir ${dir} does not exist`);
|
|
879
879
|
const list2 = readdirSync5(dir);
|
|
880
880
|
let renamed = 0, removed = 0, skipped = 0;
|
|
881
881
|
for (const [index, entry] of list2.entries()) {
|
|
882
|
-
spinner_default.text = `renaming in ${Color7.
|
|
882
|
+
spinner_default.text = `renaming in ${Color7.white.encoder(dir)} ${index + 1}/${list2.length}`;
|
|
883
883
|
if (!lstatSync3(resolve6(dir, entry)).isDirectory()) {
|
|
884
884
|
if (verbose) spinner_default.info(`skipped ${entry}`);
|
|
885
885
|
skipped++;
|
|
@@ -964,7 +964,7 @@ var rename = async ({ dir: inputDir, type, verbose }) => {
|
|
|
964
964
|
spinner_default.succeed(`renamed ${renamed} files`);
|
|
965
965
|
if (removed) spinner_default.info(`removed ${removed} files`);
|
|
966
966
|
spinner_default.info(`skipped ${skipped} files`);
|
|
967
|
-
spinner_default.succeed(`done in ${Color7.
|
|
967
|
+
spinner_default.succeed(`done in ${Color7.green.encoder(dir)}`);
|
|
968
968
|
spinner_default.stop();
|
|
969
969
|
};
|
|
970
970
|
var rename_default = rename;
|
|
@@ -975,7 +975,7 @@ import { basename as basename2, dirname, resolve as resolve7, sep } from "path";
|
|
|
975
975
|
import { Color as Color8 } from "termkit";
|
|
976
976
|
var reset = async ({ dir: inputDir, double }) => {
|
|
977
977
|
let dir = inputDir;
|
|
978
|
-
spinner_default.text = `resetting episodes in ${Color8.
|
|
978
|
+
spinner_default.text = `resetting episodes in ${Color8.white.encoder(dir)}`;
|
|
979
979
|
spinner_default.start();
|
|
980
980
|
dir = resolve7(dir);
|
|
981
981
|
if (!existsSync8(dir)) throw new Error(`dir ${dir} does not exist`);
|
|
@@ -1004,7 +1004,7 @@ var reset = async ({ dir: inputDir, double }) => {
|
|
|
1004
1004
|
const episodeFormat = getConfig().format?.episode;
|
|
1005
1005
|
let renamed = 0, skipped = other.length;
|
|
1006
1006
|
for (const [index, i] of sublist.entries()) {
|
|
1007
|
-
spinner_default.text = `resetting episodes in ${Color8.
|
|
1007
|
+
spinner_default.text = `resetting episodes in ${Color8.white.encoder(dir)} ${index}/${list2.length}`;
|
|
1008
1008
|
const ext = i.match(/([^.]+$)/)?.[0];
|
|
1009
1009
|
const episode = double ? index * 2 + 1 : index + 1;
|
|
1010
1010
|
const name = `${formatEpisode(seasonNum, episode, episodeFormat, double, showTitle)}.${ext}`;
|
|
@@ -1017,7 +1017,7 @@ var reset = async ({ dir: inputDir, double }) => {
|
|
|
1017
1017
|
}
|
|
1018
1018
|
spinner_default.succeed(`renamed ${renamed} files`);
|
|
1019
1019
|
spinner_default.info(`skipped ${skipped} files`);
|
|
1020
|
-
spinner_default.succeed(`done in ${Color8.
|
|
1020
|
+
spinner_default.succeed(`done in ${Color8.green.encoder(dir)}`);
|
|
1021
1021
|
spinner_default.stop();
|
|
1022
1022
|
};
|
|
1023
1023
|
var reset_default = reset;
|
|
@@ -1025,7 +1025,7 @@ var reset_default = reset;
|
|
|
1025
1025
|
// src/actions/scan.ts
|
|
1026
1026
|
import { cpSync, existsSync as existsSync9, linkSync, lstatSync as lstatSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync7, renameSync as renameSync3, rmSync as rmSync2, statSync as statSync2 } from "fs";
|
|
1027
1027
|
import { dirname as dirname2, resolve as resolve8 } from "path";
|
|
1028
|
-
import { Color as Color9, Select } from "termkit";
|
|
1028
|
+
import { Color as Color9, MultiSelect, Select } from "termkit";
|
|
1029
1029
|
|
|
1030
1030
|
// src/helpers/detectEdition.ts
|
|
1031
1031
|
var EDITIONS = [
|
|
@@ -1098,18 +1098,17 @@ var searchMovie = async (title, year, apiKey) => {
|
|
|
1098
1098
|
if (year) url.searchParams.set("year", String(year));
|
|
1099
1099
|
try {
|
|
1100
1100
|
const res = await fetch(url.toString());
|
|
1101
|
-
if (!res.ok) return
|
|
1101
|
+
if (!res.ok) return [];
|
|
1102
1102
|
const data = await res.json();
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
};
|
|
1103
|
+
return data.results.slice(0, 5).map((r) => ({
|
|
1104
|
+
id: r.id,
|
|
1105
|
+
title: r.title,
|
|
1106
|
+
year: r.release_date ? parseInt(r.release_date.slice(0, 4)) : void 0,
|
|
1107
|
+
overview: r.overview || void 0,
|
|
1108
|
+
url: `${TMDB_WEB}/movie/${r.id}`
|
|
1109
|
+
}));
|
|
1111
1110
|
} catch {
|
|
1112
|
-
return
|
|
1111
|
+
return [];
|
|
1113
1112
|
}
|
|
1114
1113
|
};
|
|
1115
1114
|
var getEpisodeName = async (seriesId, season, episode, apiKey) => {
|
|
@@ -1144,6 +1143,9 @@ var searchTv = async (title, apiKey) => {
|
|
|
1144
1143
|
}
|
|
1145
1144
|
};
|
|
1146
1145
|
|
|
1146
|
+
// src/refs/bookExtensions.json
|
|
1147
|
+
var bookExtensions_default = ["epub", "mobi", "azw3", "azw"];
|
|
1148
|
+
|
|
1147
1149
|
// src/actions/scan.ts
|
|
1148
1150
|
var sameDev = (a, b) => {
|
|
1149
1151
|
try {
|
|
@@ -1166,6 +1168,10 @@ var findVideo = (dir) => readdirSync7(dir).find((f) => {
|
|
|
1166
1168
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1167
1169
|
return ext && videoExtensions_default.includes(ext);
|
|
1168
1170
|
}) ?? null;
|
|
1171
|
+
var containsBook = (dir) => readdirSync7(dir).some((f) => {
|
|
1172
|
+
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1173
|
+
return ext && bookExtensions_default.includes(ext);
|
|
1174
|
+
});
|
|
1169
1175
|
var findSeasonFolder = (showPath, season) => {
|
|
1170
1176
|
if (!existsSync9(showPath)) return null;
|
|
1171
1177
|
const folders = readdirSync7(showPath).filter((f) => {
|
|
@@ -1180,27 +1186,128 @@ var findSeasonFolder = (showPath, season) => {
|
|
|
1180
1186
|
return match && parseInt(match[1]) === season;
|
|
1181
1187
|
}) ?? null;
|
|
1182
1188
|
};
|
|
1183
|
-
var
|
|
1189
|
+
var classifyMovieConfidence = (entry) => {
|
|
1190
|
+
if (/^\[(?:Team|Group)\s/i.test(entry)) return "skip";
|
|
1191
|
+
if (/\bepisodes?\s+\d+[-–]\d+/i.test(entry)) return "skip";
|
|
1192
|
+
if (/\b(?:patch|keygen|crack)\b|\bkeys?\s*\{/i.test(entry)) return "skip";
|
|
1193
|
+
if (/\[YTS[.\-]/i.test(entry)) return "auto";
|
|
1194
|
+
if (/\(\d{4}\)/.test(entry) && /\[(?:2160p|1080p|720p|480p|576p|BluRay|BDRip|BDRemux|WEBRip|WEB-DL|HDRip|DVDRip|HDTV)/i.test(entry)) return "auto";
|
|
1195
|
+
if (/\.\d{4}\..*?(?:2160p|1080p|720p|BluRay|WEBRip|WEB-DL|HDTV)/i.test(entry)) return "auto";
|
|
1196
|
+
return "ambiguous";
|
|
1197
|
+
};
|
|
1198
|
+
var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto, force, interactive }) => {
|
|
1184
1199
|
const config = getConfig();
|
|
1185
1200
|
const sessionId = (/* @__PURE__ */ new Date()).toISOString();
|
|
1186
1201
|
const language = config.language ?? "eng";
|
|
1187
1202
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
1188
1203
|
const seasonFormat = config.format?.season ?? DEFAULT_SEASON_FORMAT;
|
|
1204
|
+
const lookupMovie = async (parsed) => {
|
|
1205
|
+
let tmdbId;
|
|
1206
|
+
let resolvedTitle = parsed.title;
|
|
1207
|
+
let resolvedYear = parsed.year;
|
|
1208
|
+
if (config.tmdbApiKey) {
|
|
1209
|
+
const results = await searchMovie(parsed.title, parsed.year, config.tmdbApiKey);
|
|
1210
|
+
if (results.length === 1) {
|
|
1211
|
+
tmdbId = results[0].id;
|
|
1212
|
+
resolvedTitle = results[0].title;
|
|
1213
|
+
resolvedYear = results[0].year ?? parsed.year;
|
|
1214
|
+
} else if (results.length > 1) {
|
|
1215
|
+
spinner_default.stop();
|
|
1216
|
+
const select = new Select();
|
|
1217
|
+
const items = results.map((r) => ({
|
|
1218
|
+
label: r.year ? `${r.title} (${r.year})` : r.title,
|
|
1219
|
+
description: [r.overview?.slice(0, 60), hyperlink(r.url, "themoviedb.org")].filter(Boolean).join(" \xB7 "),
|
|
1220
|
+
...r
|
|
1221
|
+
}));
|
|
1222
|
+
const picked = await select.ask(`Multiple movies found for "${parsed.title}":`, items);
|
|
1223
|
+
spinner_default.start();
|
|
1224
|
+
if (picked) {
|
|
1225
|
+
tmdbId = picked.id;
|
|
1226
|
+
resolvedTitle = picked.title;
|
|
1227
|
+
resolvedYear = picked.year ?? parsed.year;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return { tmdbId, resolvedTitle, resolvedYear };
|
|
1232
|
+
};
|
|
1233
|
+
const importMovie = async (entry, entryPath, isDir, resolvedTitle, resolvedYear, tmdbId, destRoot) => {
|
|
1234
|
+
const edition = detectEdition(entry);
|
|
1235
|
+
const folderName = formatMovieName(movieFormat, resolvedTitle, resolvedYear, edition);
|
|
1236
|
+
const destFolder = resolve8(destRoot, folderName);
|
|
1237
|
+
if (existsSync9(destFolder)) {
|
|
1238
|
+
spinner_default.warn(`already exists: ${folderName}`);
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1242
|
+
if (!videoFile) {
|
|
1243
|
+
if (verbose) spinner_default.info(`no video found in: ${entry}`);
|
|
1244
|
+
return false;
|
|
1245
|
+
}
|
|
1246
|
+
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
1247
|
+
const destVideoName = `${folderName}.${videoExt}`;
|
|
1248
|
+
const videoSourcePath = isDir ? resolve8(entryPath, videoFile) : entryPath;
|
|
1249
|
+
const dirFiles = isDir ? readdirSync7(entryPath) : [];
|
|
1250
|
+
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1251
|
+
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1252
|
+
const subtitleSourcePath = subtitle ? resolve8(entryPath, subtitle) : null;
|
|
1253
|
+
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
1254
|
+
if (!dryRun) {
|
|
1255
|
+
if (useHardlink) {
|
|
1256
|
+
mkdirSync3(destFolder, { recursive: true });
|
|
1257
|
+
const destVideoPath = resolve8(destFolder, destVideoName);
|
|
1258
|
+
let mode;
|
|
1259
|
+
try {
|
|
1260
|
+
if (!sameDev(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
|
|
1261
|
+
linkSync(videoSourcePath, destVideoPath);
|
|
1262
|
+
mode = "hardlink";
|
|
1263
|
+
} catch {
|
|
1264
|
+
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
|
|
1265
|
+
cpSync(videoSourcePath, destVideoPath);
|
|
1266
|
+
mode = "copy";
|
|
1267
|
+
}
|
|
1268
|
+
if (subtitleSourcePath && destSubtitleName) cpSync(subtitleSourcePath, resolve8(destFolder, destSubtitleName));
|
|
1269
|
+
recordImport(sessionId, entryPath, destFolder, mode, tmdbId);
|
|
1270
|
+
} else {
|
|
1271
|
+
if (isDir) {
|
|
1272
|
+
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
1273
|
+
for (const f of dirFiles.filter((f2) => !keep.has(f2))) rmSync2(resolve8(entryPath, f), { recursive: true, force: true });
|
|
1274
|
+
renameSync3(videoSourcePath, resolve8(entryPath, destVideoName));
|
|
1275
|
+
if (subtitleSourcePath && destSubtitleName) renameSync3(subtitleSourcePath, resolve8(entryPath, destSubtitleName));
|
|
1276
|
+
moveFolder(entryPath, destFolder);
|
|
1277
|
+
} else {
|
|
1278
|
+
mkdirSync3(destFolder, { recursive: true });
|
|
1279
|
+
const destVideoPath = resolve8(destFolder, destVideoName);
|
|
1280
|
+
if (sameDev(videoSourcePath, destRoot)) {
|
|
1281
|
+
renameSync3(videoSourcePath, destVideoPath);
|
|
1282
|
+
} else {
|
|
1283
|
+
cpSync(videoSourcePath, destVideoPath);
|
|
1284
|
+
rmSync2(videoSourcePath);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${folderName}`);
|
|
1291
|
+
return true;
|
|
1292
|
+
};
|
|
1189
1293
|
spinner_default.start();
|
|
1190
1294
|
if (config.sources.length === 0) throw new Error("no sources configured \u2014 run: reelsort config add source <dir>");
|
|
1191
1295
|
let imported = 0, skipped = 0;
|
|
1296
|
+
const pendingMovies = [];
|
|
1192
1297
|
for (const source of config.sources) {
|
|
1193
1298
|
if (!existsSync9(source)) {
|
|
1194
|
-
spinner_default.warn(`source not found: ${Color9.
|
|
1299
|
+
spinner_default.warn(`source not found: ${Color9.white.encoder(source)}`);
|
|
1195
1300
|
continue;
|
|
1196
1301
|
}
|
|
1197
|
-
spinner_default.text = `scanning ${Color9.
|
|
1302
|
+
spinner_default.text = `scanning ${Color9.white.encoder(source)}`;
|
|
1198
1303
|
for (const entry of readdirSync7(source)) {
|
|
1199
1304
|
const entryPath = resolve8(source, entry);
|
|
1200
1305
|
const isDir = lstatSync4(entryPath).isDirectory();
|
|
1201
1306
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
1202
1307
|
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
1203
|
-
|
|
1308
|
+
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1309
|
+
const isBookDir = isDir && containsBook(entryPath);
|
|
1310
|
+
if (!isDir && !isVideo && !isBook) {
|
|
1204
1311
|
if (verbose) spinner_default.info(`skipped ${entry}`);
|
|
1205
1312
|
skipped++;
|
|
1206
1313
|
continue;
|
|
@@ -1208,6 +1315,8 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto }) => {
|
|
|
1208
1315
|
let detectedType;
|
|
1209
1316
|
if (type) {
|
|
1210
1317
|
detectedType = type;
|
|
1318
|
+
} else if (isBook || isBookDir) {
|
|
1319
|
+
detectedType = "book";
|
|
1211
1320
|
} else if (isDir && /(?<=\[).+?(?=\])/.test(entry)) {
|
|
1212
1321
|
detectedType = "ps3";
|
|
1213
1322
|
} else if (/S\d{2,3}E\d{2,3}/i.test(entry) || /\d+x\d{2,3}/i.test(entry)) {
|
|
@@ -1243,12 +1352,49 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto }) => {
|
|
|
1243
1352
|
imported++;
|
|
1244
1353
|
continue;
|
|
1245
1354
|
}
|
|
1355
|
+
if (detectedType === "book") {
|
|
1356
|
+
const destPath = resolve8(destRoot, entry);
|
|
1357
|
+
if (existsSync9(destPath)) {
|
|
1358
|
+
spinner_default.warn(`already exists: ${entry}`);
|
|
1359
|
+
skipped++;
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
if (!dryRun) {
|
|
1363
|
+
if (isDir || isBookDir) {
|
|
1364
|
+
moveFolder(entryPath, destPath);
|
|
1365
|
+
} else {
|
|
1366
|
+
mkdirSync3(destRoot, { recursive: true });
|
|
1367
|
+
if (sameDev(entryPath, destRoot)) {
|
|
1368
|
+
renameSync3(entryPath, destPath);
|
|
1369
|
+
} else {
|
|
1370
|
+
cpSync(entryPath, destPath);
|
|
1371
|
+
rmSync2(entryPath);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
recordImport(sessionId, entryPath, destPath, "move");
|
|
1375
|
+
}
|
|
1376
|
+
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${entry}`);
|
|
1377
|
+
imported++;
|
|
1378
|
+
continue;
|
|
1379
|
+
}
|
|
1246
1380
|
const parsed = parseDownloadName(entry);
|
|
1247
1381
|
if (!parsed) {
|
|
1248
1382
|
if (verbose) spinner_default.info(`could not parse: ${entry}`);
|
|
1249
1383
|
skipped++;
|
|
1250
1384
|
continue;
|
|
1251
1385
|
}
|
|
1386
|
+
if (detectedType === "movie") {
|
|
1387
|
+
const confidence = classifyMovieConfidence(entry);
|
|
1388
|
+
if (confidence === "skip") {
|
|
1389
|
+
if (verbose) spinner_default.info(`skipped (uncertain): ${entry}`);
|
|
1390
|
+
skipped++;
|
|
1391
|
+
continue;
|
|
1392
|
+
}
|
|
1393
|
+
if (confidence === "ambiguous") {
|
|
1394
|
+
pendingMovies.push({ entry, entryPath, isDir, parsed, destRoot });
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1252
1398
|
let tmdbId;
|
|
1253
1399
|
let resolvedTitle = parsed.title;
|
|
1254
1400
|
let resolvedYear = parsed.year;
|
|
@@ -1276,12 +1422,10 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto }) => {
|
|
|
1276
1422
|
}
|
|
1277
1423
|
}
|
|
1278
1424
|
} else {
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
resolvedYear = tmdb.year ?? parsed.year;
|
|
1284
|
-
}
|
|
1425
|
+
const result = await lookupMovie(parsed);
|
|
1426
|
+
tmdbId = result.tmdbId;
|
|
1427
|
+
resolvedTitle = result.resolvedTitle;
|
|
1428
|
+
resolvedYear = result.resolvedYear;
|
|
1285
1429
|
}
|
|
1286
1430
|
}
|
|
1287
1431
|
if (detectedType === "tv") {
|
|
@@ -1307,50 +1451,68 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto }) => {
|
|
|
1307
1451
|
}
|
|
1308
1452
|
const seasonFolderName = findSeasonFolder(showPath, parsed.season) ?? formatSeasonFolder(seasonFormat, parsed.season);
|
|
1309
1453
|
const seasonPath = resolve8(showPath, seasonFolderName);
|
|
1310
|
-
const
|
|
1311
|
-
if (!
|
|
1454
|
+
const videoFile = isDir ? findVideo(entryPath) : entry;
|
|
1455
|
+
if (!videoFile) {
|
|
1312
1456
|
if (verbose) spinner_default.info(`no video found in: ${entry}`);
|
|
1313
1457
|
skipped++;
|
|
1314
1458
|
continue;
|
|
1315
1459
|
}
|
|
1316
|
-
const
|
|
1460
|
+
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
1317
1461
|
const tmdbEpisodeName = tmdbId && config.tmdbApiKey ? await getEpisodeName(tmdbId, parsed.season, parsed.episode ?? 1, config.tmdbApiKey) : null;
|
|
1318
1462
|
const episodeName = formatEpisode(parsed.season, parsed.episode ?? 1, config.format?.episode, false, resolvedTitle, tmdbEpisodeName ?? void 0);
|
|
1319
|
-
const
|
|
1320
|
-
const destVideoPath = resolve8(seasonPath,
|
|
1321
|
-
const
|
|
1463
|
+
const destVideoName = `${episodeName}.${videoExt}`;
|
|
1464
|
+
const destVideoPath = resolve8(seasonPath, destVideoName);
|
|
1465
|
+
const videoSourcePath = isDir ? resolve8(entryPath, videoFile) : entryPath;
|
|
1322
1466
|
if (existsSync9(destVideoPath)) {
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1467
|
+
let shouldReplace = force;
|
|
1468
|
+
if (!shouldReplace && interactive) {
|
|
1469
|
+
spinner_default.stop();
|
|
1470
|
+
const select = new Select();
|
|
1471
|
+
const picked = await select.ask(`Already exists \u2014 replace?`, [
|
|
1472
|
+
{ label: `${showFolderName} / ${seasonFolderName} / ${episodeName}`, value: "replace" },
|
|
1473
|
+
{ label: "Skip", value: "skip" }
|
|
1474
|
+
]);
|
|
1475
|
+
spinner_default.start();
|
|
1476
|
+
shouldReplace = picked?.value === "replace";
|
|
1477
|
+
}
|
|
1478
|
+
if (!shouldReplace) {
|
|
1479
|
+
spinner_default.warn(`already exists: ${episodeName}`);
|
|
1480
|
+
skipped++;
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
if (!dryRun) {
|
|
1484
|
+
for (const f of readdirSync7(seasonPath)) {
|
|
1485
|
+
if (f.startsWith(`${episodeName}.`)) rmSync2(resolve8(seasonPath, f));
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1326
1488
|
}
|
|
1327
|
-
const
|
|
1328
|
-
const
|
|
1329
|
-
const
|
|
1330
|
-
const
|
|
1331
|
-
const
|
|
1489
|
+
const dirFiles = isDir ? readdirSync7(entryPath) : [];
|
|
1490
|
+
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1491
|
+
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1492
|
+
const subtitleSourcePath = subtitle ? resolve8(entryPath, subtitle) : null;
|
|
1493
|
+
const destSubtitleName = subtitle && subtitleExt ? `${episodeName}.${subtitleExt}` : null;
|
|
1332
1494
|
if (!dryRun) {
|
|
1333
1495
|
mkdirSync3(seasonPath, { recursive: true });
|
|
1334
1496
|
let mode = "move";
|
|
1335
1497
|
if (useHardlink) {
|
|
1336
1498
|
try {
|
|
1337
|
-
if (!sameDev(
|
|
1338
|
-
linkSync(
|
|
1499
|
+
if (!sameDev(videoSourcePath, seasonPath)) throw new Error("cross-filesystem");
|
|
1500
|
+
linkSync(videoSourcePath, destVideoPath);
|
|
1339
1501
|
mode = "hardlink";
|
|
1340
1502
|
} catch {
|
|
1341
1503
|
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${episodeName}`);
|
|
1342
|
-
cpSync(
|
|
1504
|
+
cpSync(videoSourcePath, destVideoPath);
|
|
1343
1505
|
mode = "copy";
|
|
1344
1506
|
}
|
|
1345
|
-
if (
|
|
1507
|
+
if (subtitleSourcePath && destSubtitleName) cpSync(subtitleSourcePath, resolve8(seasonPath, destSubtitleName));
|
|
1346
1508
|
} else {
|
|
1347
|
-
if (sameDev(
|
|
1348
|
-
renameSync3(
|
|
1509
|
+
if (sameDev(videoSourcePath, seasonPath)) {
|
|
1510
|
+
renameSync3(videoSourcePath, destVideoPath);
|
|
1349
1511
|
} else {
|
|
1350
|
-
cpSync(
|
|
1351
|
-
rmSync2(
|
|
1512
|
+
cpSync(videoSourcePath, destVideoPath);
|
|
1513
|
+
rmSync2(videoSourcePath);
|
|
1352
1514
|
}
|
|
1353
|
-
if (
|
|
1515
|
+
if (subtitleSourcePath && destSubtitleName) renameSync3(subtitleSourcePath, resolve8(seasonPath, destSubtitleName));
|
|
1354
1516
|
if (isDir) rmSync2(entryPath, { recursive: true, force: true });
|
|
1355
1517
|
}
|
|
1356
1518
|
recordImport(sessionId, entryPath, seasonPath, mode, tmdbId);
|
|
@@ -1359,66 +1521,40 @@ var scan = async ({ type, hardlink: useHardlink, dryRun, verbose, auto }) => {
|
|
|
1359
1521
|
imported++;
|
|
1360
1522
|
continue;
|
|
1361
1523
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
if (existsSync9(destFolder)) {
|
|
1366
|
-
spinner_default.warn(`already exists: ${folderName}`);
|
|
1524
|
+
if (await importMovie(entry, entryPath, isDir, resolvedTitle, resolvedYear, tmdbId, destRoot)) {
|
|
1525
|
+
imported++;
|
|
1526
|
+
} else {
|
|
1367
1527
|
skipped++;
|
|
1368
|
-
continue;
|
|
1369
1528
|
}
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (pendingMovies.length > 0) {
|
|
1532
|
+
spinner_default.warn(`${pendingMovies.length} uncertain movie match${pendingMovies.length > 1 ? "es" : ""} skipped \u2014 use -i to review or -f to import all`);
|
|
1533
|
+
for (const p of pendingMovies) spinner_default.info(` ? ${p.entry.replace(/\/$/, "")}`);
|
|
1534
|
+
let toProcess = [];
|
|
1535
|
+
if (interactive) {
|
|
1536
|
+
spinner_default.stop();
|
|
1537
|
+
const ms = new MultiSelect({ allowSkip: true, search: true, maxHeight: 20 });
|
|
1538
|
+
const items = pendingMovies.map((p) => ({
|
|
1539
|
+
label: p.entry.replace(/\/$/, ""),
|
|
1540
|
+
description: p.parsed.year ? `parsed: ${p.parsed.title} \xB7 ${p.parsed.year}` : `parsed: ${p.parsed.title}`,
|
|
1541
|
+
...p
|
|
1542
|
+
}));
|
|
1543
|
+
toProcess = await ms.ask("Select entries to import as movies:", items) ?? [];
|
|
1544
|
+
spinner_default.start();
|
|
1545
|
+
skipped += pendingMovies.length - toProcess.length;
|
|
1546
|
+
} else if (force) {
|
|
1547
|
+
toProcess = pendingMovies;
|
|
1548
|
+
} else {
|
|
1549
|
+
skipped += pendingMovies.length;
|
|
1550
|
+
}
|
|
1551
|
+
for (const p of toProcess) {
|
|
1552
|
+
const { tmdbId, resolvedTitle, resolvedYear } = await lookupMovie(p.parsed);
|
|
1553
|
+
if (await importMovie(p.entry, p.entryPath, p.isDir, resolvedTitle, resolvedYear, tmdbId, p.destRoot)) {
|
|
1554
|
+
imported++;
|
|
1555
|
+
} else {
|
|
1373
1556
|
skipped++;
|
|
1374
|
-
continue;
|
|
1375
1557
|
}
|
|
1376
|
-
const videoExt = videoFile.match(/([^.]+$)/)?.[0];
|
|
1377
|
-
const destVideoName = `${folderName}.${videoExt}`;
|
|
1378
|
-
const videoSourcePath = isDir ? resolve8(entryPath, videoFile) : entryPath;
|
|
1379
|
-
const dirFiles = isDir ? readdirSync7(entryPath) : [];
|
|
1380
|
-
const subtitle = isDir ? findSubtitle(dirFiles, language) : null;
|
|
1381
|
-
const subtitleExt = subtitle?.match(/([^.]+$)/)?.[0];
|
|
1382
|
-
const subtitleSourcePath = subtitle ? resolve8(entryPath, subtitle) : null;
|
|
1383
|
-
const destSubtitleName = subtitle && subtitleExt ? `${folderName}.${subtitleExt}` : null;
|
|
1384
|
-
if (!dryRun) {
|
|
1385
|
-
if (useHardlink) {
|
|
1386
|
-
mkdirSync3(destFolder, { recursive: true });
|
|
1387
|
-
const destVideoPath = resolve8(destFolder, destVideoName);
|
|
1388
|
-
let mode;
|
|
1389
|
-
try {
|
|
1390
|
-
if (!sameDev(videoSourcePath, destRoot)) throw new Error("cross-filesystem");
|
|
1391
|
-
linkSync(videoSourcePath, destVideoPath);
|
|
1392
|
-
mode = "hardlink";
|
|
1393
|
-
} catch {
|
|
1394
|
-
spinner_default.warn(`hardlink unavailable \u2014 copying instead: ${folderName}`);
|
|
1395
|
-
cpSync(videoSourcePath, destVideoPath);
|
|
1396
|
-
mode = "copy";
|
|
1397
|
-
}
|
|
1398
|
-
if (subtitleSourcePath && destSubtitleName) cpSync(subtitleSourcePath, resolve8(destFolder, destSubtitleName));
|
|
1399
|
-
recordImport(sessionId, entryPath, destFolder, mode, tmdbId);
|
|
1400
|
-
} else {
|
|
1401
|
-
if (isDir) {
|
|
1402
|
-
const keep = new Set([videoFile, subtitle].filter(Boolean));
|
|
1403
|
-
for (const f of dirFiles.filter((f2) => !keep.has(f2))) rmSync2(resolve8(entryPath, f), { recursive: true, force: true });
|
|
1404
|
-
renameSync3(videoSourcePath, resolve8(entryPath, destVideoName));
|
|
1405
|
-
if (subtitleSourcePath && destSubtitleName) renameSync3(subtitleSourcePath, resolve8(entryPath, destSubtitleName));
|
|
1406
|
-
moveFolder(entryPath, destFolder);
|
|
1407
|
-
} else {
|
|
1408
|
-
mkdirSync3(destFolder, { recursive: true });
|
|
1409
|
-
const destVideoPath = resolve8(destFolder, destVideoName);
|
|
1410
|
-
if (sameDev(videoSourcePath, destRoot)) {
|
|
1411
|
-
renameSync3(videoSourcePath, destVideoPath);
|
|
1412
|
-
} else {
|
|
1413
|
-
cpSync(videoSourcePath, destVideoPath);
|
|
1414
|
-
rmSync2(videoSourcePath);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
recordImport(sessionId, entryPath, destFolder, "move", tmdbId);
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
spinner_default.succeed(`${dryRun ? "[dry] " : ""}${folderName}`);
|
|
1421
|
-
imported++;
|
|
1422
1558
|
}
|
|
1423
1559
|
}
|
|
1424
1560
|
spinner_default.succeed(`imported ${imported} items`);
|
|
@@ -1441,7 +1577,7 @@ var undo = async () => {
|
|
|
1441
1577
|
let undone = 0;
|
|
1442
1578
|
for (const record of records) {
|
|
1443
1579
|
renameSync4(record.newPath, record.oldPath);
|
|
1444
|
-
spinner_default.succeed(`${Color10.
|
|
1580
|
+
spinner_default.succeed(`${Color10.green.encoder(record.newPath)} \u2192 ${Color10.white.encoder(record.oldPath)}`);
|
|
1445
1581
|
undone++;
|
|
1446
1582
|
}
|
|
1447
1583
|
deleteSession(records[0].sessionId);
|
|
@@ -1476,6 +1612,10 @@ var findVideo2 = (dir) => readdirSync8(dir).find((f) => {
|
|
|
1476
1612
|
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1477
1613
|
return ext && videoExtensions_default.includes(ext);
|
|
1478
1614
|
}) ?? null;
|
|
1615
|
+
var containsBook2 = (dir) => readdirSync8(dir).some((f) => {
|
|
1616
|
+
const ext = f.match(/([^.]+$)/)?.[0];
|
|
1617
|
+
return ext && bookExtensions_default.includes(ext);
|
|
1618
|
+
});
|
|
1479
1619
|
var findSeasonFolder2 = (showPath, season) => {
|
|
1480
1620
|
if (!existsSync10(showPath)) return null;
|
|
1481
1621
|
const folders = readdirSync8(showPath).filter((f) => {
|
|
@@ -1499,9 +1639,13 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1499
1639
|
const isDir = lstatSync5(entryPath).isDirectory();
|
|
1500
1640
|
const ext = entry.match(/([^.]+$)/)?.[0];
|
|
1501
1641
|
const isVideo = !isDir && ext && videoExtensions_default.includes(ext);
|
|
1502
|
-
|
|
1642
|
+
const isBook = !isDir && ext && bookExtensions_default.includes(ext);
|
|
1643
|
+
const isBookDir = isDir && containsBook2(entryPath);
|
|
1644
|
+
if (!isDir && !isVideo && !isBook) return;
|
|
1503
1645
|
let detectedType;
|
|
1504
|
-
if (
|
|
1646
|
+
if (isBook || isBookDir) {
|
|
1647
|
+
detectedType = "book";
|
|
1648
|
+
} else if (isDir && /(?<=\[).+?(?=\])/.test(entry)) {
|
|
1505
1649
|
detectedType = "ps3";
|
|
1506
1650
|
} else if (/S\d{2,3}E\d{2,3}/i.test(entry) || /\d+x\d{2,3}/i.test(entry)) {
|
|
1507
1651
|
detectedType = "tv";
|
|
@@ -1525,7 +1669,28 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1525
1669
|
}
|
|
1526
1670
|
moveItem(entryPath, destPath);
|
|
1527
1671
|
recordImport(sessionId, entryPath, destPath, "move");
|
|
1528
|
-
spinner_default.succeed(`imported ${Color11.
|
|
1672
|
+
spinner_default.succeed(`imported ${Color11.green.encoder(destName)}`);
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
if (detectedType === "book") {
|
|
1676
|
+
const destPath = resolve9(destRoot, entry);
|
|
1677
|
+
if (existsSync10(destPath)) {
|
|
1678
|
+
spinner_default.warn(`already exists: ${entry}`);
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
if (isDir || isBookDir) {
|
|
1682
|
+
moveItem(entryPath, destPath);
|
|
1683
|
+
} else {
|
|
1684
|
+
mkdirSync4(destRoot, { recursive: true });
|
|
1685
|
+
if (sameDev2(entryPath, destRoot)) {
|
|
1686
|
+
renameSync5(entryPath, destPath);
|
|
1687
|
+
} else {
|
|
1688
|
+
cpSync2(entryPath, destPath);
|
|
1689
|
+
rmSync3(entryPath);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
recordImport(sessionId, entryPath, destPath, "move");
|
|
1693
|
+
spinner_default.succeed(`imported ${Color11.green.encoder(entry)}`);
|
|
1529
1694
|
return;
|
|
1530
1695
|
}
|
|
1531
1696
|
const parsed = parseDownloadName(entry);
|
|
@@ -1598,7 +1763,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1598
1763
|
if (isDir) rmSync3(entryPath, { recursive: true, force: true });
|
|
1599
1764
|
}
|
|
1600
1765
|
recordImport(sessionId, entryPath, seasonPath, mode);
|
|
1601
|
-
spinner_default.succeed(`imported ${Color11.
|
|
1766
|
+
spinner_default.succeed(`imported ${Color11.green.encoder(`${showFolderName} / ${seasonFolderName} / ${episodeName}`)}`);
|
|
1602
1767
|
return;
|
|
1603
1768
|
}
|
|
1604
1769
|
const edition = detectEdition(entry);
|
|
@@ -1655,7 +1820,7 @@ var processItem = async (entryPath, useHardlink, verbose, language, auto) => {
|
|
|
1655
1820
|
}
|
|
1656
1821
|
recordImport(sessionId, entryPath, destFolder, "move");
|
|
1657
1822
|
}
|
|
1658
|
-
spinner_default.succeed(`imported ${Color11.
|
|
1823
|
+
spinner_default.succeed(`imported ${Color11.green.encoder(folderName)}`);
|
|
1659
1824
|
};
|
|
1660
1825
|
var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
1661
1826
|
const config = getConfig();
|
|
@@ -1686,7 +1851,7 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
1686
1851
|
watcher.on("add", handle);
|
|
1687
1852
|
spinner_default.start();
|
|
1688
1853
|
spinner_default.succeed(`watching ${config.sources.length} source${config.sources.length !== 1 ? "s" : ""}`);
|
|
1689
|
-
for (const s of config.sources) spinner_default.info(` ${Color11.
|
|
1854
|
+
for (const s of config.sources) spinner_default.info(` ${Color11.white.encoder(s)}`);
|
|
1690
1855
|
spinner_default.stop();
|
|
1691
1856
|
process.stdin.resume();
|
|
1692
1857
|
};
|