redate-cli 0.3.0 → 0.4.0
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/package.json +1 -1
- package/src/config.js +19 -46
- package/src/core.js +33 -23
- package/src/defaults.js +43 -0
- package/src/redate.js +11 -3
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -1,53 +1,10 @@
|
|
|
1
1
|
import os from "os";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs";
|
|
4
|
+
import { DEFAULT_CONFIG, TOKENS } from "./defaults.js";
|
|
5
|
+
const CONFIG_DIR = getConfigDir();
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "cli-config.json");
|
|
4
7
|
|
|
5
|
-
const CONFIG_DIR = path.join(os.homedir(), ".redate");
|
|
6
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
7
|
-
|
|
8
|
-
export const DEFAULT_CONFIG = {
|
|
9
|
-
format: "yyyy-mm-dd hh-min-ss",
|
|
10
|
-
|
|
11
|
-
fileHandling: "rename" // rename, copy, copy_in_folder
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const TOKENS = {
|
|
15
|
-
yyyy: { desc: "4-digit year", value: (date) => date.getFullYear() },
|
|
16
|
-
yy: { desc: "2-digit year", value: (date) => String(date.getFullYear()).slice(-2) },
|
|
17
|
-
mm: { desc: "Month with leading zero", value: (date) => String(date.getMonth() + 1).padStart(2, "0") },
|
|
18
|
-
m: { desc: "Month without leading zero", value: (date) => date.getMonth() + 1 },
|
|
19
|
-
mmm: { desc: "Short month name", value: (date) => date.toLocaleString("en-US", { month: "short" }) },
|
|
20
|
-
mmmm: { desc: "Full month name", value: (date) => date.toLocaleString("en-US", { month: "long" }) },
|
|
21
|
-
dd: { desc: "Day with leading zero", value: (date) => String(date.getDate()).padStart(2, "0") },
|
|
22
|
-
d: { desc: "Day without leading zero", value: (date) => date.getDate() },
|
|
23
|
-
ddd: { desc: "Day short name", value: (date) => date.toLocaleString("en-US", { weekday: "short" }) },
|
|
24
|
-
dddd: { desc: "Day full name", value: (date) => date.toLocaleString("en-US", { weekday: "long" }) },
|
|
25
|
-
hh: { desc: "Hour 00-23", value: (date) => String(date.getHours()).padStart(2, "0") },
|
|
26
|
-
h: { desc: "Hour 0-23", value: (date) => date.getHours() },
|
|
27
|
-
H: { desc: "Hour 1-12", value: (date) => ((date.getHours() + 11) % 12 + 1) },
|
|
28
|
-
HH: { desc: "Hour 01-12", value: (date) => String((date.getHours() + 11) % 12 + 1).padStart(2, "0") },
|
|
29
|
-
a: { desc: "AM/PM", value: (date) => date.getHours() < 12 ? "AM" : "PM" },
|
|
30
|
-
A: { desc: "am/pm", value: (date) => date.getHours() < 12 ? "am" : "pm" },
|
|
31
|
-
min: { desc: "Minutes with leading zero", value: (date) => String(date.getMinutes()).padStart(2, "0") },
|
|
32
|
-
m_: { desc: "Minutes without leading zero", value: (date) => date.getMinutes() },
|
|
33
|
-
ss: { desc: "Seconds with leading zero", value: (date) => String(date.getSeconds()).padStart(2, "0") },
|
|
34
|
-
s: { desc: "Seconds without leading zero", value: (date) => date.getSeconds() },
|
|
35
|
-
ms: { desc: "Milliseconds", value: (date) => date.getMilliseconds() },
|
|
36
|
-
w: {
|
|
37
|
-
desc: "Week of year", value: (date) => {
|
|
38
|
-
const start = new Date(date.getFullYear(), 0, 1);
|
|
39
|
-
const diff = (date - start) + ((start.getDay() + 6) % 7) * 86400000;
|
|
40
|
-
return Math.floor(diff / (7 * 86400000)) + 1;
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
D: {
|
|
44
|
-
desc: "Day of year", value: (date) => {
|
|
45
|
-
const start = new Date(date.getFullYear(), 0, 0);
|
|
46
|
-
const diff = date - start;
|
|
47
|
-
return Math.floor(diff / 86400000);
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
8
|
|
|
52
9
|
function ensureConfig() {
|
|
53
10
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
@@ -93,4 +50,20 @@ function deepMerge(target, source) {
|
|
|
93
50
|
}
|
|
94
51
|
|
|
95
52
|
return output;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
function getConfigDir() {
|
|
57
|
+
|
|
58
|
+
switch (process.platform) {
|
|
59
|
+
case "win32":
|
|
60
|
+
return path.join(process.env.LOCALAPPDATA || process.env.APPDATA, "Poutanen", "ReDate");
|
|
61
|
+
|
|
62
|
+
case "darwin":
|
|
63
|
+
return path.join(os.homedir(), "Library", "Application Support", "ReDate");
|
|
64
|
+
|
|
65
|
+
default:
|
|
66
|
+
const xdg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
67
|
+
return path.join(xdg, appName);
|
|
68
|
+
}
|
|
96
69
|
}
|
package/src/core.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import exifr from "exifr";
|
|
4
|
-
import { getConfig
|
|
4
|
+
import { getConfig } from "./config.js";
|
|
5
|
+
import { TOKENS } from "./defaults.js";
|
|
5
6
|
|
|
6
7
|
const fileHandlers = {
|
|
7
8
|
rename: (src, dest) => {
|
|
@@ -24,52 +25,62 @@ const fileHandlers = {
|
|
|
24
25
|
|
|
25
26
|
export async function redate(paths) {
|
|
26
27
|
const config = getConfig();
|
|
28
|
+
const result = {
|
|
29
|
+
totalFiles: 0,
|
|
30
|
+
processed: 0,
|
|
31
|
+
skippedNoDate: 0,
|
|
32
|
+
errors: []
|
|
33
|
+
};
|
|
27
34
|
|
|
28
35
|
for (const p of paths) {
|
|
29
36
|
if (!fs.existsSync(p)) {
|
|
30
|
-
|
|
37
|
+
result.errors.push(`Path does not exist: ${p}`);
|
|
31
38
|
continue;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
const stats = fs.statSync(p);
|
|
35
42
|
|
|
36
43
|
if (stats.isFile()) {
|
|
37
|
-
await processFile(p, config);
|
|
44
|
+
await processFile(p, config, result);
|
|
38
45
|
} else if (stats.isDirectory()) {
|
|
39
|
-
await processFiles(p, config);
|
|
46
|
+
await processFiles(p, config, result);
|
|
40
47
|
}
|
|
41
48
|
}
|
|
49
|
+
|
|
50
|
+
return result;
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
|
|
45
|
-
export async function processFiles(folderPath, config) {
|
|
46
|
-
if (!config
|
|
47
|
-
config = getConfig();
|
|
48
|
-
}
|
|
54
|
+
export async function processFiles(folderPath, config, result) {
|
|
55
|
+
if (!config) config = getConfig();
|
|
49
56
|
const files = fs.readdirSync(folderPath);
|
|
57
|
+
|
|
50
58
|
for (const file of files) {
|
|
51
59
|
const filePath = path.join(folderPath, file);
|
|
52
|
-
|
|
53
60
|
if (!fs.statSync(filePath).isFile()) continue;
|
|
54
61
|
|
|
55
|
-
await processFile(filePath, config);
|
|
62
|
+
await processFile(filePath, config, result);
|
|
56
63
|
}
|
|
57
64
|
}
|
|
65
|
+
export async function processFile(filePath, config, result) {
|
|
66
|
+
result.totalFiles += 1;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const date = await getDateFromFile(filePath);
|
|
70
|
+
if (!date) {
|
|
71
|
+
result.skippedNoDate += 1;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
58
74
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
config = getConfig();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const date = await getDateFromFile(filePath);
|
|
65
|
-
if (!date) return;
|
|
66
|
-
|
|
67
|
-
const originalName = path.basename(filePath);
|
|
68
|
-
const newFileName = formatFileName(date, originalName, config);
|
|
75
|
+
const originalName = path.basename(filePath);
|
|
76
|
+
const newFileName = formatFileName(date, originalName, config);
|
|
69
77
|
|
|
70
|
-
|
|
78
|
+
applyFileHandling(filePath, newFileName, config);
|
|
71
79
|
|
|
72
|
-
|
|
80
|
+
result.processed += 1;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
result.errors.push(`Error processing file ${filePath}: ${err.message}`);
|
|
83
|
+
}
|
|
73
84
|
}
|
|
74
85
|
function applyFileHandling(srcPath, newFileName, config) {
|
|
75
86
|
const dir = path.dirname(srcPath);
|
|
@@ -109,7 +120,6 @@ export async function getDateFromFile(filePath) {
|
|
|
109
120
|
exif?.ModifyDate;
|
|
110
121
|
|
|
111
122
|
if (!date) {
|
|
112
|
-
console.error(`No EXIF date found for file: ${filePath}`);
|
|
113
123
|
return null;
|
|
114
124
|
};
|
|
115
125
|
|
package/src/defaults.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const DEFAULT_CONFIG = {
|
|
2
|
+
format: "yyyy-mm-dd hh-min-ss",
|
|
3
|
+
fileHandling: "rename" // rename, copy, copy_in_folder
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export const TOKENS = {
|
|
7
|
+
yyyy: { desc: "4-digit year", value: (date) => date.getFullYear() },
|
|
8
|
+
yy: { desc: "2-digit year", value: (date) => String(date.getFullYear()).slice(-2) },
|
|
9
|
+
mm: { desc: "Month with leading zero", value: (date) => String(date.getMonth() + 1).padStart(2, "0") },
|
|
10
|
+
m: { desc: "Month without leading zero", value: (date) => date.getMonth() + 1 },
|
|
11
|
+
mmm: { desc: "Short month name", value: (date) => date.toLocaleString("en-US", { month: "short" }) },
|
|
12
|
+
mmmm: { desc: "Full month name", value: (date) => date.toLocaleString("en-US", { month: "long" }) },
|
|
13
|
+
dd: { desc: "Day with leading zero", value: (date) => String(date.getDate()).padStart(2, "0") },
|
|
14
|
+
d: { desc: "Day without leading zero", value: (date) => date.getDate() },
|
|
15
|
+
ddd: { desc: "Day short name", value: (date) => date.toLocaleString("en-US", { weekday: "short" }) },
|
|
16
|
+
dddd: { desc: "Day full name", value: (date) => date.toLocaleString("en-US", { weekday: "long" }) },
|
|
17
|
+
hh: { desc: "Hour 00-23", value: (date) => String(date.getHours()).padStart(2, "0") },
|
|
18
|
+
h: { desc: "Hour 0-23", value: (date) => date.getHours() },
|
|
19
|
+
H: { desc: "Hour 1-12", value: (date) => ((date.getHours() + 11) % 12 + 1) },
|
|
20
|
+
HH: { desc: "Hour 01-12", value: (date) => String((date.getHours() + 11) % 12 + 1).padStart(2, "0") },
|
|
21
|
+
a: { desc: "AM/PM", value: (date) => date.getHours() < 12 ? "AM" : "PM" },
|
|
22
|
+
A: { desc: "am/pm", value: (date) => date.getHours() < 12 ? "am" : "pm" },
|
|
23
|
+
min: { desc: "Minutes with leading zero", value: (date) => String(date.getMinutes()).padStart(2, "0") },
|
|
24
|
+
m_: { desc: "Minutes without leading zero", value: (date) => date.getMinutes() },
|
|
25
|
+
ss: { desc: "Seconds with leading zero", value: (date) => String(date.getSeconds()).padStart(2, "0") },
|
|
26
|
+
s: { desc: "Seconds without leading zero", value: (date) => date.getSeconds() },
|
|
27
|
+
ms: { desc: "Milliseconds", value: (date) => date.getMilliseconds() },
|
|
28
|
+
w: {
|
|
29
|
+
desc: "Week of year",
|
|
30
|
+
value: (date) => {
|
|
31
|
+
const start = new Date(date.getFullYear(), 0, 1);
|
|
32
|
+
const diff = (date - start) + ((start.getDay() + 6) % 7) * 86400000;
|
|
33
|
+
return Math.floor(diff / (7 * 86400000)) + 1;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
D: {
|
|
37
|
+
desc: "Day of year",
|
|
38
|
+
value: (date) => {
|
|
39
|
+
const start = new Date(date.getFullYear(), 0, 0);
|
|
40
|
+
return Math.floor((date - start) / 86400000);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
};
|
package/src/redate.js
CHANGED
|
@@ -3,7 +3,8 @@ import { Command } from "commander";
|
|
|
3
3
|
import https from "https";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import readline from "readline";
|
|
6
|
-
import { getConfig, setConfig
|
|
6
|
+
import { getConfig, setConfig } from "./config.js";
|
|
7
|
+
import { DEFAULT_CONFIG } from "./defaults.js";
|
|
7
8
|
import redate from "./core.js";
|
|
8
9
|
|
|
9
10
|
const program = new Command();
|
|
@@ -11,13 +12,20 @@ const program = new Command();
|
|
|
11
12
|
program
|
|
12
13
|
.name("redate")
|
|
13
14
|
.description("Rename images based on EXIF dates")
|
|
14
|
-
.version("0.
|
|
15
|
+
.version("0.4.0");
|
|
15
16
|
|
|
16
17
|
program
|
|
17
18
|
.command("process <paths...>")
|
|
18
19
|
.description("Process file(s) or folder(s)")
|
|
19
20
|
.action(async (paths) => {
|
|
20
|
-
await redate(paths);
|
|
21
|
+
const result = await redate(paths);
|
|
22
|
+
console.log(`Total files: ${result.totalFiles}`);
|
|
23
|
+
console.log(`Processed: ${result.processed}`);
|
|
24
|
+
console.log(`Skipped (no date): ${result.skippedNoDate}`);
|
|
25
|
+
if (result.errors.length > 0) {
|
|
26
|
+
console.log("Errors:");
|
|
27
|
+
result.errors.forEach((err) => console.log(` - ${err}`));
|
|
28
|
+
}
|
|
21
29
|
});
|
|
22
30
|
|
|
23
31
|
const configCommand = program
|