reelsort 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +234 -321
- package/dist/index.d.mts +78 -81
- package/dist/index.d.ts +78 -81
- package/dist/index.js +1013 -1063
- package/dist/index.mjs +1003 -1053
- package/package.json +13 -3
package/dist/cli.js
CHANGED
|
@@ -26,9 +26,11 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
// src/program.ts
|
|
27
27
|
var import_termkit = require("termkit");
|
|
28
28
|
|
|
29
|
-
// src/actions/
|
|
29
|
+
// src/actions/add.ts
|
|
30
30
|
var import_cosmetic = __toESM(require("cosmetic"));
|
|
31
|
-
var
|
|
31
|
+
var import_fs3 = require("fs");
|
|
32
|
+
var import_path3 = require("path");
|
|
33
|
+
var import_termpulse2 = require("termpulse");
|
|
32
34
|
|
|
33
35
|
// src/config.ts
|
|
34
36
|
var import_fs = require("fs");
|
|
@@ -45,211 +47,13 @@ var saveConfig = (config) => {
|
|
|
45
47
|
(0, import_fs.writeFileSync)(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
46
48
|
};
|
|
47
49
|
|
|
48
|
-
// src/helpers/formatEpisode.ts
|
|
49
|
-
var DEFAULT_EPISODE_FORMAT = "{s}x{ee}";
|
|
50
|
-
var DEFAULT_SEASON_FORMAT = "Season {s}";
|
|
51
|
-
var renderEpisode = (format, season, episode, title, name) => {
|
|
52
|
-
return format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).replace(/\{eee\}/g, episode.toString().padStart(3, "0")).replace(/\{ee\}/g, episode.toString().padStart(2, "0")).replace(/\{e\}/g, episode.toString()).replace(/\{title\}/g, title ?? "").replace(/\{name\}/g, name ?? "").replace(/\s+/g, " ").trim();
|
|
53
|
-
};
|
|
54
|
-
var formatSeasonFolder = (format, season) => format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).trim();
|
|
55
|
-
var formatEpisode = (season, episode, format = DEFAULT_EPISODE_FORMAT, double = false, title, name) => {
|
|
56
|
-
if (double) {
|
|
57
|
-
const a = renderEpisode(format, season, episode, title, name);
|
|
58
|
-
const b = renderEpisode(format, season, episode + 1, title, name);
|
|
59
|
-
return `${a}-${b}`;
|
|
60
|
-
}
|
|
61
|
-
return renderEpisode(format, season, episode, title, name);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// src/helpers/formatName.ts
|
|
65
|
-
var DEFAULT_MOVIE_FORMAT = "{title} ({year})";
|
|
66
|
-
var formatMovieName = (template, title, year, edition) => {
|
|
67
|
-
const editionTag = edition ? ` {edition-${edition}}` : "";
|
|
68
|
-
let result = template.replace("{title}", title).replace("{edition}", editionTag);
|
|
69
|
-
if (year) {
|
|
70
|
-
result = result.replace("{year}", year.toString());
|
|
71
|
-
} else {
|
|
72
|
-
result = result.replace(/\s*[([{]\{year\}[)\]}]/g, "").replace("{year}", "");
|
|
73
|
-
}
|
|
74
|
-
return result.replace(/\s+/g, " ").trim();
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
// src/refs/spinner.ts
|
|
78
|
-
var import_termpulse = require("termpulse");
|
|
79
|
-
var Spinner = class {
|
|
80
|
-
spinner;
|
|
81
|
-
_isSpinning = false;
|
|
82
|
-
_text = "";
|
|
83
|
-
constructor() {
|
|
84
|
-
this.spinner = new import_termpulse.Spinner();
|
|
85
|
-
}
|
|
86
|
-
get text() {
|
|
87
|
-
return this._text;
|
|
88
|
-
}
|
|
89
|
-
set text(t) {
|
|
90
|
-
this._text = t;
|
|
91
|
-
if (this._isSpinning) this.spinner.message(t);
|
|
92
|
-
}
|
|
93
|
-
get isSpinning() {
|
|
94
|
-
return this._isSpinning;
|
|
95
|
-
}
|
|
96
|
-
start(s) {
|
|
97
|
-
if (s) this._text = s;
|
|
98
|
-
this.spinner.start();
|
|
99
|
-
this._isSpinning = true;
|
|
100
|
-
return this;
|
|
101
|
-
}
|
|
102
|
-
info(s) {
|
|
103
|
-
this.spinner.info(s);
|
|
104
|
-
return this;
|
|
105
|
-
}
|
|
106
|
-
warn(s) {
|
|
107
|
-
this.spinner.warn(s);
|
|
108
|
-
return this;
|
|
109
|
-
}
|
|
110
|
-
fail(s) {
|
|
111
|
-
this.spinner.fail(s);
|
|
112
|
-
return this;
|
|
113
|
-
}
|
|
114
|
-
succeed(s) {
|
|
115
|
-
this.spinner.succeed(s);
|
|
116
|
-
return this;
|
|
117
|
-
}
|
|
118
|
-
stop() {
|
|
119
|
-
this.spinner.stop();
|
|
120
|
-
this._isSpinning = false;
|
|
121
|
-
process.stdin.resume();
|
|
122
|
-
return this;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
var spinner_default = new Spinner();
|
|
126
|
-
|
|
127
|
-
// src/actions/config.ts
|
|
128
|
-
var DEST_TYPES = ["movie", "tv", "ps3"];
|
|
129
|
-
var configAdd = async ({ key, value }) => {
|
|
130
|
-
if (key !== "source") throw new Error(`unknown key '${key}', expected: source`);
|
|
131
|
-
const dir = (0, import_path2.resolve)(value);
|
|
132
|
-
const config = getConfig();
|
|
133
|
-
if (config.sources.includes(dir)) {
|
|
134
|
-
spinner_default.start();
|
|
135
|
-
spinner_default.info(`source already configured: ${import_cosmetic.default.blue.encoder(dir)}`);
|
|
136
|
-
spinner_default.stop();
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
config.sources.push(dir);
|
|
140
|
-
saveConfig(config);
|
|
141
|
-
spinner_default.start();
|
|
142
|
-
spinner_default.succeed(`added source: ${import_cosmetic.default.blue.encoder(dir)}`);
|
|
143
|
-
spinner_default.stop();
|
|
144
|
-
};
|
|
145
|
-
var configRemove = async ({ key, value }) => {
|
|
146
|
-
if (key !== "source") throw new Error(`unknown key '${key}', expected: source`);
|
|
147
|
-
const dir = (0, import_path2.resolve)(value);
|
|
148
|
-
const config = getConfig();
|
|
149
|
-
const index = config.sources.indexOf(dir);
|
|
150
|
-
if (index === -1) {
|
|
151
|
-
spinner_default.start();
|
|
152
|
-
spinner_default.warn(`source not found: ${import_cosmetic.default.blue.encoder(dir)}`);
|
|
153
|
-
spinner_default.stop();
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
config.sources.splice(index, 1);
|
|
157
|
-
saveConfig(config);
|
|
158
|
-
spinner_default.start();
|
|
159
|
-
spinner_default.succeed(`removed source: ${import_cosmetic.default.blue.encoder(dir)}`);
|
|
160
|
-
spinner_default.stop();
|
|
161
|
-
};
|
|
162
|
-
var configSet = async ({ key, subkey, value }) => {
|
|
163
|
-
const config = getConfig();
|
|
164
|
-
if (key === "language") {
|
|
165
|
-
config.language = subkey;
|
|
166
|
-
saveConfig(config);
|
|
167
|
-
spinner_default.start();
|
|
168
|
-
spinner_default.succeed(`set subtitle language: ${import_cosmetic.default.cyan.encoder(subkey)}`);
|
|
169
|
-
spinner_default.stop();
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (key === "tmdb-key") {
|
|
173
|
-
config.tmdbApiKey = subkey;
|
|
174
|
-
saveConfig(config);
|
|
175
|
-
spinner_default.start();
|
|
176
|
-
spinner_default.succeed(`set TMDb API key`);
|
|
177
|
-
spinner_default.stop();
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
if (key === "format") {
|
|
181
|
-
if (subkey === "episode") {
|
|
182
|
-
if (!value) throw new Error('missing format string for episode, e.g. "S{ss}E{ee}"');
|
|
183
|
-
if (!/\{e+\}/.test(value)) throw new Error("episode format must include an episode token: {e}, {ee}, or {eee}");
|
|
184
|
-
config.format = { ...config.format, episode: value };
|
|
185
|
-
} else if (subkey === "movie") {
|
|
186
|
-
if (!value) throw new Error('missing format string for movie, e.g. "{title} ({year})"');
|
|
187
|
-
if (!value.includes("{title}")) throw new Error("movie format must include {title}");
|
|
188
|
-
config.format = { ...config.format, movie: value };
|
|
189
|
-
} else if (subkey === "season") {
|
|
190
|
-
if (!value) throw new Error('missing format string for season, e.g. "Season {s}"');
|
|
191
|
-
if (!/\{s+\}/.test(value)) throw new Error("season format must include a season token: {s}, {ss}, or {sss}");
|
|
192
|
-
config.format = { ...config.format, season: value };
|
|
193
|
-
} else {
|
|
194
|
-
throw new Error(`unknown format key '${subkey}', expected: movie, episode, season`);
|
|
195
|
-
}
|
|
196
|
-
saveConfig(config);
|
|
197
|
-
spinner_default.start();
|
|
198
|
-
spinner_default.succeed(`set ${subkey} format: ${import_cosmetic.default.cyan.encoder(value ?? subkey)}`);
|
|
199
|
-
spinner_default.stop();
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (key !== "dest") throw new Error(`unknown key '${key}', expected: dest, language, tmdb-key, format`);
|
|
203
|
-
if (!DEST_TYPES.includes(subkey)) {
|
|
204
|
-
throw new Error(`unknown type '${subkey}', expected: ${DEST_TYPES.join(", ")}`);
|
|
205
|
-
}
|
|
206
|
-
if (!value) throw new Error(`missing path for dest ${subkey}`);
|
|
207
|
-
const dir = (0, import_path2.resolve)(value);
|
|
208
|
-
config.dest[subkey] = dir;
|
|
209
|
-
saveConfig(config);
|
|
210
|
-
spinner_default.start();
|
|
211
|
-
spinner_default.succeed(`set ${subkey} destination: ${import_cosmetic.default.cyan.encoder(dir)}`);
|
|
212
|
-
spinner_default.stop();
|
|
213
|
-
};
|
|
214
|
-
var configShow = async (_) => {
|
|
215
|
-
const config = getConfig();
|
|
216
|
-
console.log("\nSources:");
|
|
217
|
-
if (config.sources.length === 0) {
|
|
218
|
-
console.log(" (none)");
|
|
219
|
-
} else {
|
|
220
|
-
for (const s of config.sources) console.log(` ${import_cosmetic.default.blue.encoder(s)}`);
|
|
221
|
-
}
|
|
222
|
-
console.log("\nDestinations:");
|
|
223
|
-
const entries = DEST_TYPES.map((t) => ({ type: t, path: config.dest[t] })).filter((e) => e.path);
|
|
224
|
-
if (entries.length === 0) {
|
|
225
|
-
console.log(" (none)");
|
|
226
|
-
} else {
|
|
227
|
-
for (const { type, path } of entries) {
|
|
228
|
-
console.log(` ${type.padEnd(6)} ${import_cosmetic.default.cyan.encoder(path)}`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
console.log(`
|
|
232
|
-
Subtitle language: ${import_cosmetic.default.cyan.encoder(config.language ?? "eng (default)")}`);
|
|
233
|
-
console.log(`TMDb API key: ${config.tmdbApiKey ? import_cosmetic.default.green.encoder("configured") : import_cosmetic.default.red.encoder("not set")}`);
|
|
234
|
-
console.log(`Movie format: ${import_cosmetic.default.cyan.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
|
|
235
|
-
console.log(`Episode format: ${import_cosmetic.default.cyan.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
|
|
236
|
-
console.log(`Season folder: ${import_cosmetic.default.cyan.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
|
|
237
|
-
console.log();
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
// src/actions/add.ts
|
|
241
|
-
var import_cosmetic2 = __toESM(require("cosmetic"));
|
|
242
|
-
var import_fs3 = require("fs");
|
|
243
|
-
var import_path4 = require("path");
|
|
244
|
-
var import_termpulse2 = require("termpulse");
|
|
245
|
-
|
|
246
50
|
// src/db.ts
|
|
247
51
|
var import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
248
52
|
var import_fs2 = require("fs");
|
|
249
53
|
var import_os2 = require("os");
|
|
250
|
-
var
|
|
251
|
-
var DB_DIR = (0,
|
|
252
|
-
var DB_PATH = (0,
|
|
54
|
+
var import_path2 = require("path");
|
|
55
|
+
var DB_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".config", "reelsort");
|
|
56
|
+
var DB_PATH = (0, import_path2.join)(DB_DIR, "reelsort.db");
|
|
253
57
|
var _db = null;
|
|
254
58
|
var db = () => {
|
|
255
59
|
if (_db) return _db;
|
|
@@ -315,12 +119,14 @@ var getMediaInfo = (filePath) => {
|
|
|
315
119
|
return db().prepare("SELECT * FROM mediaInfo WHERE filePath = ?").get(filePath);
|
|
316
120
|
};
|
|
317
121
|
var upsertMediaInfo = (filePath, codec, resolution, width, height, duration) => {
|
|
318
|
-
db().prepare(
|
|
122
|
+
db().prepare(
|
|
123
|
+
`INSERT INTO mediaInfo (filePath, codec, resolution, width, height, duration, probedAt)
|
|
319
124
|
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
320
125
|
ON CONFLICT(filePath) DO UPDATE SET
|
|
321
126
|
codec = excluded.codec, resolution = excluded.resolution,
|
|
322
127
|
width = excluded.width, height = excluded.height,
|
|
323
|
-
duration = excluded.duration, probedAt = excluded.probedAt`
|
|
128
|
+
duration = excluded.duration, probedAt = excluded.probedAt`
|
|
129
|
+
).run(filePath, codec, resolution, width, height, duration);
|
|
324
130
|
};
|
|
325
131
|
var getImportByDest = (destPath) => {
|
|
326
132
|
return db().prepare("SELECT * FROM imports WHERE destinationPath = ? LIMIT 1").get(destPath);
|
|
@@ -335,12 +141,14 @@ var getShow = (path) => {
|
|
|
335
141
|
};
|
|
336
142
|
var getShows = () => db().prepare("SELECT * FROM shows ORDER BY path ASC").all().map(rowToShow);
|
|
337
143
|
var upsertShow = (path, tmdbId, title) => {
|
|
338
|
-
db().prepare(
|
|
144
|
+
db().prepare(
|
|
145
|
+
`
|
|
339
146
|
INSERT INTO shows (path, tmdbId, title) VALUES (?, ?, ?)
|
|
340
147
|
ON CONFLICT(path) DO UPDATE SET
|
|
341
148
|
tmdbId = COALESCE(excluded.tmdbId, tmdbId),
|
|
342
149
|
title = COALESCE(excluded.title, title)
|
|
343
|
-
`
|
|
150
|
+
`
|
|
151
|
+
).run(path, tmdbId, title ?? null);
|
|
344
152
|
};
|
|
345
153
|
var getShowByTitle = (title) => {
|
|
346
154
|
const t = title.toLowerCase();
|
|
@@ -380,6 +188,19 @@ var getHistory = (limit = 10) => {
|
|
|
380
188
|
}));
|
|
381
189
|
};
|
|
382
190
|
|
|
191
|
+
// src/helpers/formatName.ts
|
|
192
|
+
var DEFAULT_MOVIE_FORMAT = "{title} ({year})";
|
|
193
|
+
var formatMovieName = (template, title, year, edition) => {
|
|
194
|
+
const editionTag = edition ? ` {edition-${edition}}` : "";
|
|
195
|
+
let result = template.replace("{title}", title).replace("{edition}", editionTag);
|
|
196
|
+
if (year) {
|
|
197
|
+
result = result.replace("{year}", year.toString());
|
|
198
|
+
} else {
|
|
199
|
+
result = result.replace(/\s*[([{]\{year\}[)\]}]/g, "").replace("{year}", "");
|
|
200
|
+
}
|
|
201
|
+
return result.replace(/\s+/g, " ").trim();
|
|
202
|
+
};
|
|
203
|
+
|
|
383
204
|
// src/helpers/hyperlink.ts
|
|
384
205
|
var hyperlink = (url, text) => `\x1B]8;;${url}\x1B\\${text}\x1B]8;;\x1B\\`;
|
|
385
206
|
|
|
@@ -455,6 +276,56 @@ var searchTv = async (title, apiKey) => {
|
|
|
455
276
|
}
|
|
456
277
|
};
|
|
457
278
|
|
|
279
|
+
// src/refs/spinner.ts
|
|
280
|
+
var import_termpulse = require("termpulse");
|
|
281
|
+
var Spinner = class {
|
|
282
|
+
spinner;
|
|
283
|
+
_isSpinning = false;
|
|
284
|
+
_text = "";
|
|
285
|
+
constructor() {
|
|
286
|
+
this.spinner = new import_termpulse.Spinner();
|
|
287
|
+
}
|
|
288
|
+
get text() {
|
|
289
|
+
return this._text;
|
|
290
|
+
}
|
|
291
|
+
set text(t) {
|
|
292
|
+
this._text = t;
|
|
293
|
+
if (this._isSpinning) this.spinner.message(t);
|
|
294
|
+
}
|
|
295
|
+
get isSpinning() {
|
|
296
|
+
return this._isSpinning;
|
|
297
|
+
}
|
|
298
|
+
start(s) {
|
|
299
|
+
if (s) this._text = s;
|
|
300
|
+
this.spinner.start();
|
|
301
|
+
this._isSpinning = true;
|
|
302
|
+
return this;
|
|
303
|
+
}
|
|
304
|
+
info(s) {
|
|
305
|
+
this.spinner.info(s);
|
|
306
|
+
return this;
|
|
307
|
+
}
|
|
308
|
+
warn(s) {
|
|
309
|
+
this.spinner.warn(s);
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
fail(s) {
|
|
313
|
+
this.spinner.fail(s);
|
|
314
|
+
return this;
|
|
315
|
+
}
|
|
316
|
+
succeed(s) {
|
|
317
|
+
this.spinner.succeed(s);
|
|
318
|
+
return this;
|
|
319
|
+
}
|
|
320
|
+
stop() {
|
|
321
|
+
this.spinner.stop();
|
|
322
|
+
this._isSpinning = false;
|
|
323
|
+
process.stdin.resume();
|
|
324
|
+
return this;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
var spinner_default = new Spinner();
|
|
328
|
+
|
|
458
329
|
// src/actions/add.ts
|
|
459
330
|
var add = async ({ name }) => {
|
|
460
331
|
const config = getConfig();
|
|
@@ -478,17 +349,17 @@ var add = async ({ name }) => {
|
|
|
478
349
|
}
|
|
479
350
|
const movieFormat = config.format?.movie ?? DEFAULT_MOVIE_FORMAT;
|
|
480
351
|
const folderName = formatMovieName(movieFormat, picked.title, picked.year);
|
|
481
|
-
const showPath = (0,
|
|
352
|
+
const showPath = (0, import_path3.resolve)(destRoot, folderName);
|
|
482
353
|
(0, import_fs3.mkdirSync)(showPath, { recursive: true });
|
|
483
354
|
upsertShow(showPath, picked.id, picked.title);
|
|
484
355
|
spinner_default.start();
|
|
485
|
-
spinner_default.succeed(`added ${
|
|
356
|
+
spinner_default.succeed(`added ${import_cosmetic.default.cyan.encoder(folderName)}`);
|
|
486
357
|
spinner_default.stop();
|
|
487
358
|
};
|
|
488
359
|
var add_default = add;
|
|
489
360
|
|
|
490
361
|
// src/actions/clean.ts
|
|
491
|
-
var
|
|
362
|
+
var import_cosmetic2 = __toESM(require("cosmetic"));
|
|
492
363
|
var import_fs4 = require("fs");
|
|
493
364
|
var parseOlderThan = (s) => {
|
|
494
365
|
const match = s.match(/^(\d+)([dhm])$/);
|
|
@@ -522,17 +393,17 @@ var clean = async ({ dryRun, olderThan }) => {
|
|
|
522
393
|
continue;
|
|
523
394
|
}
|
|
524
395
|
if (dryRun) {
|
|
525
|
-
spinner_default.succeed(`[dry] would remove ${
|
|
396
|
+
spinner_default.succeed(`[dry] would remove ${import_cosmetic2.default.blue.encoder(imp.sourcePath)}`);
|
|
526
397
|
cleaned++;
|
|
527
398
|
continue;
|
|
528
399
|
}
|
|
529
400
|
try {
|
|
530
401
|
(0, import_fs4.rmSync)(imp.sourcePath, { recursive: true, force: true });
|
|
531
402
|
deleteImport(imp.id);
|
|
532
|
-
spinner_default.succeed(`removed ${
|
|
403
|
+
spinner_default.succeed(`removed ${import_cosmetic2.default.blue.encoder(imp.sourcePath)}`);
|
|
533
404
|
cleaned++;
|
|
534
405
|
} catch {
|
|
535
|
-
spinner_default.warn(`locked or inaccessible, skipped: ${
|
|
406
|
+
spinner_default.warn(`locked or inaccessible, skipped: ${import_cosmetic2.default.blue.encoder(imp.sourcePath)}`);
|
|
536
407
|
skipped++;
|
|
537
408
|
}
|
|
538
409
|
}
|
|
@@ -542,6 +413,139 @@ var clean = async ({ dryRun, olderThan }) => {
|
|
|
542
413
|
};
|
|
543
414
|
var clean_default = clean;
|
|
544
415
|
|
|
416
|
+
// src/actions/config.ts
|
|
417
|
+
var import_cosmetic3 = __toESM(require("cosmetic"));
|
|
418
|
+
var import_path4 = require("path");
|
|
419
|
+
|
|
420
|
+
// src/helpers/formatEpisode.ts
|
|
421
|
+
var DEFAULT_EPISODE_FORMAT = "{s}x{ee}";
|
|
422
|
+
var DEFAULT_SEASON_FORMAT = "Season {s}";
|
|
423
|
+
var renderEpisode = (format, season, episode, title, name) => {
|
|
424
|
+
return format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).replace(/\{eee\}/g, episode.toString().padStart(3, "0")).replace(/\{ee\}/g, episode.toString().padStart(2, "0")).replace(/\{e\}/g, episode.toString()).replace(/\{title\}/g, title ?? "").replace(/\{name\}/g, name ?? "").replace(/\s+/g, " ").trim();
|
|
425
|
+
};
|
|
426
|
+
var formatSeasonFolder = (format, season) => format.replace(/\{sss\}/g, season.toString().padStart(3, "0")).replace(/\{ss\}/g, season.toString().padStart(2, "0")).replace(/\{s\}/g, season.toString()).trim();
|
|
427
|
+
var formatEpisode = (season, episode, format = DEFAULT_EPISODE_FORMAT, double = false, title, name) => {
|
|
428
|
+
if (double) {
|
|
429
|
+
const a = renderEpisode(format, season, episode, title, name);
|
|
430
|
+
const b = renderEpisode(format, season, episode + 1, title, name);
|
|
431
|
+
return `${a}-${b}`;
|
|
432
|
+
}
|
|
433
|
+
return renderEpisode(format, season, episode, title, name);
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// src/actions/config.ts
|
|
437
|
+
var DEST_TYPES = ["movie", "tv", "ps3"];
|
|
438
|
+
var configAdd = async ({ key, value }) => {
|
|
439
|
+
if (key !== "source") throw new Error(`unknown key '${key}', expected: source`);
|
|
440
|
+
const dir = (0, import_path4.resolve)(value);
|
|
441
|
+
const config = getConfig();
|
|
442
|
+
if (config.sources.includes(dir)) {
|
|
443
|
+
spinner_default.start();
|
|
444
|
+
spinner_default.info(`source already configured: ${import_cosmetic3.default.blue.encoder(dir)}`);
|
|
445
|
+
spinner_default.stop();
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
config.sources.push(dir);
|
|
449
|
+
saveConfig(config);
|
|
450
|
+
spinner_default.start();
|
|
451
|
+
spinner_default.succeed(`added source: ${import_cosmetic3.default.blue.encoder(dir)}`);
|
|
452
|
+
spinner_default.stop();
|
|
453
|
+
};
|
|
454
|
+
var configRemove = async ({ key, value }) => {
|
|
455
|
+
if (key !== "source") throw new Error(`unknown key '${key}', expected: source`);
|
|
456
|
+
const dir = (0, import_path4.resolve)(value);
|
|
457
|
+
const config = getConfig();
|
|
458
|
+
const index = config.sources.indexOf(dir);
|
|
459
|
+
if (index === -1) {
|
|
460
|
+
spinner_default.start();
|
|
461
|
+
spinner_default.warn(`source not found: ${import_cosmetic3.default.blue.encoder(dir)}`);
|
|
462
|
+
spinner_default.stop();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
config.sources.splice(index, 1);
|
|
466
|
+
saveConfig(config);
|
|
467
|
+
spinner_default.start();
|
|
468
|
+
spinner_default.succeed(`removed source: ${import_cosmetic3.default.blue.encoder(dir)}`);
|
|
469
|
+
spinner_default.stop();
|
|
470
|
+
};
|
|
471
|
+
var configSet = async ({ key, subkey, value }) => {
|
|
472
|
+
const config = getConfig();
|
|
473
|
+
if (key === "language") {
|
|
474
|
+
config.language = subkey;
|
|
475
|
+
saveConfig(config);
|
|
476
|
+
spinner_default.start();
|
|
477
|
+
spinner_default.succeed(`set subtitle language: ${import_cosmetic3.default.cyan.encoder(subkey)}`);
|
|
478
|
+
spinner_default.stop();
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (key === "tmdb-key") {
|
|
482
|
+
config.tmdbApiKey = subkey;
|
|
483
|
+
saveConfig(config);
|
|
484
|
+
spinner_default.start();
|
|
485
|
+
spinner_default.succeed(`set TMDb API key`);
|
|
486
|
+
spinner_default.stop();
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (key === "format") {
|
|
490
|
+
if (subkey === "episode") {
|
|
491
|
+
if (!value) throw new Error('missing format string for episode, e.g. "S{ss}E{ee}"');
|
|
492
|
+
if (!/\{e+\}/.test(value)) throw new Error("episode format must include an episode token: {e}, {ee}, or {eee}");
|
|
493
|
+
config.format = { ...config.format, episode: value };
|
|
494
|
+
} else if (subkey === "movie") {
|
|
495
|
+
if (!value) throw new Error('missing format string for movie, e.g. "{title} ({year})"');
|
|
496
|
+
if (!value.includes("{title}")) throw new Error("movie format must include {title}");
|
|
497
|
+
config.format = { ...config.format, movie: value };
|
|
498
|
+
} else if (subkey === "season") {
|
|
499
|
+
if (!value) throw new Error('missing format string for season, e.g. "Season {s}"');
|
|
500
|
+
if (!/\{s+\}/.test(value)) throw new Error("season format must include a season token: {s}, {ss}, or {sss}");
|
|
501
|
+
config.format = { ...config.format, season: value };
|
|
502
|
+
} else {
|
|
503
|
+
throw new Error(`unknown format key '${subkey}', expected: movie, episode, season`);
|
|
504
|
+
}
|
|
505
|
+
saveConfig(config);
|
|
506
|
+
spinner_default.start();
|
|
507
|
+
spinner_default.succeed(`set ${subkey} format: ${import_cosmetic3.default.cyan.encoder(value ?? subkey)}`);
|
|
508
|
+
spinner_default.stop();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (key !== "dest") throw new Error(`unknown key '${key}', expected: dest, language, tmdb-key, format`);
|
|
512
|
+
if (!DEST_TYPES.includes(subkey)) {
|
|
513
|
+
throw new Error(`unknown type '${subkey}', expected: ${DEST_TYPES.join(", ")}`);
|
|
514
|
+
}
|
|
515
|
+
if (!value) throw new Error(`missing path for dest ${subkey}`);
|
|
516
|
+
const dir = (0, import_path4.resolve)(value);
|
|
517
|
+
config.dest[subkey] = dir;
|
|
518
|
+
saveConfig(config);
|
|
519
|
+
spinner_default.start();
|
|
520
|
+
spinner_default.succeed(`set ${subkey} destination: ${import_cosmetic3.default.cyan.encoder(dir)}`);
|
|
521
|
+
spinner_default.stop();
|
|
522
|
+
};
|
|
523
|
+
var configShow = async () => {
|
|
524
|
+
const config = getConfig();
|
|
525
|
+
console.log("\nSources:");
|
|
526
|
+
if (config.sources.length === 0) {
|
|
527
|
+
console.log(" (none)");
|
|
528
|
+
} else {
|
|
529
|
+
for (const s of config.sources) console.log(` ${import_cosmetic3.default.blue.encoder(s)}`);
|
|
530
|
+
}
|
|
531
|
+
console.log("\nDestinations:");
|
|
532
|
+
const entries = DEST_TYPES.map((t) => ({ type: t, path: config.dest[t] })).filter((e) => e.path);
|
|
533
|
+
if (entries.length === 0) {
|
|
534
|
+
console.log(" (none)");
|
|
535
|
+
} else {
|
|
536
|
+
for (const { type, path } of entries) {
|
|
537
|
+
console.log(` ${type.padEnd(6)} ${import_cosmetic3.default.cyan.encoder(path)}`);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
console.log(`
|
|
541
|
+
Subtitle language: ${import_cosmetic3.default.cyan.encoder(config.language ?? "eng (default)")}`);
|
|
542
|
+
console.log(`TMDb API key: ${config.tmdbApiKey ? import_cosmetic3.default.green.encoder("configured") : import_cosmetic3.default.red.encoder("not set")}`);
|
|
543
|
+
console.log(`Movie format: ${import_cosmetic3.default.cyan.encoder(config.format?.movie ?? DEFAULT_MOVIE_FORMAT + " (default)")}`);
|
|
544
|
+
console.log(`Episode format: ${import_cosmetic3.default.cyan.encoder(config.format?.episode ?? DEFAULT_EPISODE_FORMAT + " (default)")}`);
|
|
545
|
+
console.log(`Season folder: ${import_cosmetic3.default.cyan.encoder(config.format?.season ?? DEFAULT_SEASON_FORMAT + " (default)")}`);
|
|
546
|
+
console.log();
|
|
547
|
+
};
|
|
548
|
+
|
|
545
549
|
// src/actions/differences.ts
|
|
546
550
|
var import_cosmetic4 = __toESM(require("cosmetic"));
|
|
547
551
|
var import_fs5 = require("fs");
|
|
@@ -781,7 +785,7 @@ var RESOLUTION_MAP = {
|
|
|
781
785
|
"1080p": "1080p",
|
|
782
786
|
"2160p": "2160p",
|
|
783
787
|
"4k": "2160p",
|
|
784
|
-
|
|
788
|
+
uhd: "2160p",
|
|
785
789
|
"8k": "8K"
|
|
786
790
|
};
|
|
787
791
|
var CODEC_MAP = {
|
|
@@ -923,9 +927,7 @@ ${import_cosmetic8.default.yellow.encoder(t.toUpperCase())} ${import_cosmetic8.
|
|
|
923
927
|
console.log(divider);
|
|
924
928
|
for (const e of filtered) {
|
|
925
929
|
const sub = e.hasSub ? import_cosmetic8.default.green.encoder("\u2713") : import_cosmetic8.default.red.encoder("\u2717");
|
|
926
|
-
console.log(
|
|
927
|
-
`${col(e.title, titleW)} ${col(e.year?.toString() ?? "\u2014", 6)} ${col(e.resolution ?? "\u2014", 6)} ${col(e.codec ?? "\u2014", 6)} ${col(e.size, 10)} ${sub}`
|
|
928
|
-
);
|
|
930
|
+
console.log(`${col(e.title, titleW)} ${col(e.year?.toString() ?? "\u2014", 6)} ${col(e.resolution ?? "\u2014", 6)} ${col(e.codec ?? "\u2014", 6)} ${col(e.size, 10)} ${sub}`);
|
|
929
931
|
}
|
|
930
932
|
console.log(divider);
|
|
931
933
|
console.log(`${filtered.length} of ${entries.length} item${entries.length !== 1 ? "s" : ""}`);
|
|
@@ -1017,8 +1019,8 @@ ${totalMissing} missing episode${totalMissing !== 1 ? "s" : ""} total`);
|
|
|
1017
1019
|
var missing_default = missing;
|
|
1018
1020
|
|
|
1019
1021
|
// src/actions/probe.ts
|
|
1020
|
-
var import_cosmetic10 = __toESM(require("cosmetic"));
|
|
1021
1022
|
var import_child_process = require("child_process");
|
|
1023
|
+
var import_cosmetic10 = __toESM(require("cosmetic"));
|
|
1022
1024
|
var import_fs10 = require("fs");
|
|
1023
1025
|
var import_path11 = require("path");
|
|
1024
1026
|
var DEST_TYPES3 = ["movie", "tv", "ps3"];
|
|
@@ -1362,59 +1364,7 @@ var detectEdition = (filename) => {
|
|
|
1362
1364
|
};
|
|
1363
1365
|
|
|
1364
1366
|
// src/helpers/parseDownloadName.ts
|
|
1365
|
-
var QUALITY_TOKENS = /* @__PURE__ */ new Set([
|
|
1366
|
-
"480p",
|
|
1367
|
-
"576p",
|
|
1368
|
-
"720p",
|
|
1369
|
-
"1080p",
|
|
1370
|
-
"2160p",
|
|
1371
|
-
"4k",
|
|
1372
|
-
"8k",
|
|
1373
|
-
"bluray",
|
|
1374
|
-
"bdrip",
|
|
1375
|
-
"bdremux",
|
|
1376
|
-
"brrip",
|
|
1377
|
-
"webrip",
|
|
1378
|
-
"web-dl",
|
|
1379
|
-
"webdl",
|
|
1380
|
-
"web",
|
|
1381
|
-
"hdtv",
|
|
1382
|
-
"dvdrip",
|
|
1383
|
-
"dvdscr",
|
|
1384
|
-
"cam",
|
|
1385
|
-
"ts",
|
|
1386
|
-
"scr",
|
|
1387
|
-
"x264",
|
|
1388
|
-
"x265",
|
|
1389
|
-
"hevc",
|
|
1390
|
-
"avc",
|
|
1391
|
-
"h264",
|
|
1392
|
-
"h265",
|
|
1393
|
-
"xvid",
|
|
1394
|
-
"divx",
|
|
1395
|
-
"dts",
|
|
1396
|
-
"ac3",
|
|
1397
|
-
"aac",
|
|
1398
|
-
"mp3",
|
|
1399
|
-
"truehd",
|
|
1400
|
-
"atmos",
|
|
1401
|
-
"dd5",
|
|
1402
|
-
"hdr",
|
|
1403
|
-
"hdr10",
|
|
1404
|
-
"hlg",
|
|
1405
|
-
"dv",
|
|
1406
|
-
"dolby",
|
|
1407
|
-
"remux",
|
|
1408
|
-
"proper",
|
|
1409
|
-
"repack",
|
|
1410
|
-
"extended",
|
|
1411
|
-
"theatrical",
|
|
1412
|
-
"unrated",
|
|
1413
|
-
"multi",
|
|
1414
|
-
"dubbed",
|
|
1415
|
-
"subbed",
|
|
1416
|
-
"internal"
|
|
1417
|
-
]);
|
|
1367
|
+
var QUALITY_TOKENS = /* @__PURE__ */ new Set(["480p", "576p", "720p", "1080p", "2160p", "4k", "8k", "bluray", "bdrip", "bdremux", "brrip", "webrip", "web-dl", "webdl", "web", "hdtv", "dvdrip", "dvdscr", "cam", "ts", "scr", "x264", "x265", "hevc", "avc", "h264", "h265", "xvid", "divx", "dts", "ac3", "aac", "mp3", "truehd", "atmos", "dd5", "hdr", "hdr10", "hlg", "dv", "dolby", "remux", "proper", "repack", "extended", "theatrical", "unrated", "multi", "dubbed", "subbed", "internal"]);
|
|
1418
1368
|
var TV_PATTERN = /^(.*?)[.\s_-]*(?:S(\d{2,3})E(\d{2,3})|(\d{1,2})x(\d{2,3})|Season[\s.](\d+))/i;
|
|
1419
1369
|
var parseDownloadName = (name) => {
|
|
1420
1370
|
const base = name.replace(/\.[a-z0-9]{2,4}$/i, "");
|
|
@@ -1740,7 +1690,7 @@ var scan_default = scan;
|
|
|
1740
1690
|
var import_cosmetic14 = __toESM(require("cosmetic"));
|
|
1741
1691
|
var import_fs14 = require("fs");
|
|
1742
1692
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
1743
|
-
var shows = async (
|
|
1693
|
+
var shows = async () => {
|
|
1744
1694
|
const config = getConfig();
|
|
1745
1695
|
const destRoot = config.dest.tv;
|
|
1746
1696
|
const allShows = getShows();
|
|
@@ -1812,7 +1762,7 @@ var countDirs = (dir) => {
|
|
|
1812
1762
|
return 0;
|
|
1813
1763
|
}
|
|
1814
1764
|
};
|
|
1815
|
-
var stats = async (
|
|
1765
|
+
var stats = async () => {
|
|
1816
1766
|
const config = getConfig();
|
|
1817
1767
|
const shows2 = getShows();
|
|
1818
1768
|
const label = (s) => ` ${import_cosmetic15.default.yellow.encoder(s.padEnd(14))}`;
|
|
@@ -1845,7 +1795,7 @@ var stats_default = stats;
|
|
|
1845
1795
|
// src/actions/undo.ts
|
|
1846
1796
|
var import_cosmetic16 = __toESM(require("cosmetic"));
|
|
1847
1797
|
var import_fs16 = require("fs");
|
|
1848
|
-
var undo = async (
|
|
1798
|
+
var undo = async () => {
|
|
1849
1799
|
spinner_default.start();
|
|
1850
1800
|
const records = getLastSession();
|
|
1851
1801
|
if (records.length === 0) {
|
|
@@ -2108,59 +2058,22 @@ var watch = async ({ hardlink = false, verbose = false, auto = false }) => {
|
|
|
2108
2058
|
var watch_default = watch;
|
|
2109
2059
|
|
|
2110
2060
|
// package.json
|
|
2111
|
-
var version = "0.1.
|
|
2061
|
+
var version = "0.1.2";
|
|
2112
2062
|
|
|
2113
2063
|
// src/program.ts
|
|
2114
2064
|
var adapt = (fn) => (options) => fn(options);
|
|
2115
2065
|
var program = (0, import_termkit.command)("reelsort").version(version).description("a cli to manage media").commands([
|
|
2116
|
-
(0, import_termkit.command)("config").description("manage configuration").commands([
|
|
2117
|
-
|
|
2118
|
-
(0, import_termkit.command)("remove", "<key> <value>").description("remove a value (e.g. remove source <dir>)").action(adapt(configRemove)),
|
|
2119
|
-
(0, import_termkit.command)("set", "<key> <subkey> [value]").description("set a value (e.g. set dest movie <dir>)").action(adapt(configSet)),
|
|
2120
|
-
(0, import_termkit.command)("show").description("show current configuration").action(adapt(configShow))
|
|
2121
|
-
]),
|
|
2122
|
-
(0, import_termkit.command)("rename", "<dir>").description("rename media files in directory").options([
|
|
2123
|
-
(0, import_termkit.option)("t", "type", "<type>", "media type: movie, tv, ps3"),
|
|
2124
|
-
(0, import_termkit.option)("v", "verbose", null, "additional output")
|
|
2125
|
-
]).action(adapt(rename_default)),
|
|
2066
|
+
(0, import_termkit.command)("config").description("manage configuration").commands([(0, import_termkit.command)("add", "<key> <value>").description("add a value (e.g. add source <dir>)").action(adapt(configAdd)), (0, import_termkit.command)("remove", "<key> <value>").description("remove a value (e.g. remove source <dir>)").action(adapt(configRemove)), (0, import_termkit.command)("set", "<key> <subkey> [value]").description("set a value (e.g. set dest movie <dir>)").action(adapt(configSet)), (0, import_termkit.command)("show").description("show current configuration").action(adapt(configShow))]),
|
|
2067
|
+
(0, import_termkit.command)("rename", "<dir>").description("rename media files in directory").options([(0, import_termkit.option)("t", "type", "<type>", "media type: movie, tv, ps3"), (0, import_termkit.option)("v", "verbose", null, "additional output")]).action(adapt(rename_default)),
|
|
2126
2068
|
(0, import_termkit.command)("reset", "<dir>").description("reset episode names (sxee)").options([(0, import_termkit.option)("d", "double", null, "episodes are doubles")]).action(adapt(reset_default)),
|
|
2127
|
-
(0, import_termkit.command)("probe").description("index library metadata using ffprobe").options([
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
]).action(adapt(
|
|
2132
|
-
(0, import_termkit.command)("list").description("list library contents").options([
|
|
2133
|
-
(0, import_termkit.option)("t", "type", "<type>", "media type: movie, tv, ps3"),
|
|
2134
|
-
(0, import_termkit.option)("m", "missing-subs", null, "only show items without subtitles"),
|
|
2135
|
-
(0, import_termkit.option)("c", "codec", "<codec>", "filter by codec (e.g. x265)"),
|
|
2136
|
-
(0, import_termkit.option)("r", "resolution", "<res>", "filter by resolution (e.g. 1080p, 4K)"),
|
|
2137
|
-
(0, import_termkit.option)("s", "sort", "<field>", "sort by: year (default), title")
|
|
2138
|
-
]).action(adapt(list_default)),
|
|
2139
|
-
(0, import_termkit.command)("watch").description("watch sources and auto-import new media").options([
|
|
2140
|
-
(0, import_termkit.option)("H", "hardlink", null, "hardlink instead of moving (falls back to copy across filesystems)"),
|
|
2141
|
-
(0, import_termkit.option)("v", "verbose", null, "additional output"),
|
|
2142
|
-
(0, import_termkit.option)("a", "auto", null, "auto-register unrecognised TV shows instead of skipping them")
|
|
2143
|
-
]).action(adapt(watch_default)),
|
|
2144
|
-
(0, import_termkit.command)("scan").description("import media from configured sources to destinations").options([
|
|
2145
|
-
(0, import_termkit.option)("t", "type", "<type>", "only process this media type: movie, tv, ps3"),
|
|
2146
|
-
(0, import_termkit.option)("H", "hardlink", null, "hardlink instead of moving (falls back to copy across filesystems)"),
|
|
2147
|
-
(0, import_termkit.option)("n", "dry-run", null, "show what would be imported without doing it"),
|
|
2148
|
-
(0, import_termkit.option)("v", "verbose", null, "additional output"),
|
|
2149
|
-
(0, import_termkit.option)("a", "auto", null, "auto-register unrecognised TV shows instead of skipping them")
|
|
2150
|
-
]).action(adapt(scan_default)),
|
|
2151
|
-
(0, import_termkit.command)("clean").description("remove source files that have already been imported").options([
|
|
2152
|
-
(0, import_termkit.option)("n", "dry-run", null, "show what would be removed without doing it"),
|
|
2153
|
-
(0, import_termkit.option)("o", "older-than", "<age>", "only clean imports older than age (e.g. 14d, 6h, 30m)")
|
|
2154
|
-
]).action(adapt(clean_default)),
|
|
2069
|
+
(0, import_termkit.command)("probe").description("index library metadata using ffprobe").options([(0, import_termkit.option)("t", "type", "<type>", "media type: movie, tv, ps3"), (0, import_termkit.option)("f", "force", null, "re-probe files already indexed"), (0, import_termkit.option)("v", "verbose", null, "show each file as it is probed")]).action(adapt(probe_default)),
|
|
2070
|
+
(0, import_termkit.command)("list").description("list library contents").options([(0, import_termkit.option)("t", "type", "<type>", "media type: movie, tv, ps3"), (0, import_termkit.option)("m", "missing-subs", null, "only show items without subtitles"), (0, import_termkit.option)("c", "codec", "<codec>", "filter by codec (e.g. x265)"), (0, import_termkit.option)("r", "resolution", "<res>", "filter by resolution (e.g. 1080p, 4K)"), (0, import_termkit.option)("s", "sort", "<field>", "sort by: year (default), title")]).action(adapt(list_default)),
|
|
2071
|
+
(0, import_termkit.command)("watch").description("watch sources and auto-import new media").options([(0, import_termkit.option)("H", "hardlink", null, "hardlink instead of moving (falls back to copy across filesystems)"), (0, import_termkit.option)("v", "verbose", null, "additional output"), (0, import_termkit.option)("a", "auto", null, "auto-register unrecognised TV shows instead of skipping them")]).action(adapt(watch_default)),
|
|
2072
|
+
(0, import_termkit.command)("scan").description("import media from configured sources to destinations").options([(0, import_termkit.option)("t", "type", "<type>", "only process this media type: movie, tv, ps3"), (0, import_termkit.option)("H", "hardlink", null, "hardlink instead of moving (falls back to copy across filesystems)"), (0, import_termkit.option)("n", "dry-run", null, "show what would be imported without doing it"), (0, import_termkit.option)("v", "verbose", null, "additional output"), (0, import_termkit.option)("a", "auto", null, "auto-register unrecognised TV shows instead of skipping them")]).action(adapt(scan_default)),
|
|
2073
|
+
(0, import_termkit.command)("clean").description("remove source files that have already been imported").options([(0, import_termkit.option)("n", "dry-run", null, "show what would be removed without doing it"), (0, import_termkit.option)("o", "older-than", "<age>", "only clean imports older than age (e.g. 14d, 6h, 30m)")]).action(adapt(clean_default)),
|
|
2155
2074
|
(0, import_termkit.command)("undo").description("undo the last rename session").action(adapt(undo_default)),
|
|
2156
|
-
(0, import_termkit.command)("history").description("show rename or import history").options([
|
|
2157
|
-
|
|
2158
|
-
(0, import_termkit.option)("i", "imports", null, "show import history instead of rename history")
|
|
2159
|
-
]).action(adapt(history_default)),
|
|
2160
|
-
(0, import_termkit.command)("diff", "<dir1> <dir2>").description("compare differences between two directories").options([
|
|
2161
|
-
(0, import_termkit.option)("o", "only", "[ext...]", "check specified extensions"),
|
|
2162
|
-
(0, import_termkit.option)("i", "ignore", "[ext...]", "ignore specified extensions")
|
|
2163
|
-
]).action(adapt(differences_default)),
|
|
2075
|
+
(0, import_termkit.command)("history").description("show rename or import history").options([(0, import_termkit.option)("l", "limit", "<n>", "number of sessions to show (default 10)"), (0, import_termkit.option)("i", "imports", null, "show import history instead of rename history")]).action(adapt(history_default)),
|
|
2076
|
+
(0, import_termkit.command)("diff", "<dir1> <dir2>").description("compare differences between two directories").options([(0, import_termkit.option)("o", "only", "[ext...]", "check specified extensions"), (0, import_termkit.option)("i", "ignore", "[ext...]", "ignore specified extensions")]).action(adapt(differences_default)),
|
|
2164
2077
|
(0, import_termkit.command)("missing").description("show missing episodes for TV shows (requires TMDb key)").options([(0, import_termkit.option)("s", "show", "<name>", "check a specific show instead of all")]).action(adapt(missing_default)),
|
|
2165
2078
|
(0, import_termkit.command)("shows").description("list all registered TV shows with TMDb status and size").action(adapt(shows_default)),
|
|
2166
2079
|
(0, import_termkit.command)("stats").description("show library statistics").action(adapt(stats_default)),
|