redate-cli 0.2.2 → 0.3.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 +6 -2
- package/src/core.js +127 -0
- package/src/redate.js +4 -125
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redate-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A CLI tool for renaming images based on EXIF data.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "src/
|
|
6
|
+
"main": "./src/core.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"redate": "./src/redate.js"
|
|
9
9
|
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/core.js",
|
|
12
|
+
"./config": "./src/config.js"
|
|
13
|
+
},
|
|
10
14
|
"scripts": {
|
|
11
15
|
"redate": "node ./src/redate.js"
|
|
12
16
|
},
|
package/src/core.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import exifr from "exifr";
|
|
4
|
+
import { getConfig, TOKENS } from "./config.js";
|
|
5
|
+
|
|
6
|
+
const fileHandlers = {
|
|
7
|
+
rename: (src, dest) => {
|
|
8
|
+
fs.renameSync(src, dest);
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
copy: (src, dest) => {
|
|
12
|
+
fs.copyFileSync(src, dest);
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
copy_in_folder: (src, dest) => {
|
|
16
|
+
const dir = path.dirname(src);
|
|
17
|
+
const targetDir = path.join(dir, "redate");
|
|
18
|
+
if (!fs.existsSync(targetDir)) {
|
|
19
|
+
fs.mkdirSync(targetDir);
|
|
20
|
+
}
|
|
21
|
+
fs.copyFileSync(src, path.join(targetDir, path.basename(dest)));
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export async function redate(paths) {
|
|
26
|
+
const config = getConfig();
|
|
27
|
+
|
|
28
|
+
for (const p of paths) {
|
|
29
|
+
if (!fs.existsSync(p)) {
|
|
30
|
+
console.error(`Path does not exist: ${p}`);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const stats = fs.statSync(p);
|
|
35
|
+
|
|
36
|
+
if (stats.isFile()) {
|
|
37
|
+
await processFile(p, config);
|
|
38
|
+
} else if (stats.isDirectory()) {
|
|
39
|
+
await processFiles(p, config);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
export async function processFiles(folderPath, config) {
|
|
46
|
+
if (!config || config == null) {
|
|
47
|
+
config = getConfig();
|
|
48
|
+
}
|
|
49
|
+
const files = fs.readdirSync(folderPath);
|
|
50
|
+
for (const file of files) {
|
|
51
|
+
const filePath = path.join(folderPath, file);
|
|
52
|
+
|
|
53
|
+
if (!fs.statSync(filePath).isFile()) continue;
|
|
54
|
+
|
|
55
|
+
await processFile(filePath, config);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function processFile(filePath, config) {
|
|
60
|
+
if (!config || config == null) {
|
|
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);
|
|
69
|
+
|
|
70
|
+
applyFileHandling(filePath, newFileName, config);
|
|
71
|
+
|
|
72
|
+
console.log(`Processed: ${newFileName}`);
|
|
73
|
+
}
|
|
74
|
+
function applyFileHandling(srcPath, newFileName, config) {
|
|
75
|
+
const dir = path.dirname(srcPath);
|
|
76
|
+
const dest = path.join(dir, newFileName);
|
|
77
|
+
|
|
78
|
+
const handler = fileHandlers[config.fileHandling];
|
|
79
|
+
|
|
80
|
+
if (!handler) {
|
|
81
|
+
throw new Error(`Unknown fileHandling: ${config.fileHandling}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
handler(srcPath, dest);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
export function formatFileName(date, originalName, config) {
|
|
89
|
+
let formatted = config.format;
|
|
90
|
+
|
|
91
|
+
const sortedTokens = Object.keys(TOKENS).sort((a, b) => b.length - a.length);
|
|
92
|
+
|
|
93
|
+
for (const key of sortedTokens) {
|
|
94
|
+
formatted = formatted.replaceAll(key, TOKENS[key].value(date));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const ext = path.extname(originalName);
|
|
98
|
+
return `${formatted}${ext}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
export async function getDateFromFile(filePath) {
|
|
103
|
+
const exif = await exifr.parse(filePath, { reviveValues: true });
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
const date =
|
|
107
|
+
exif?.DateTimeOriginal ||
|
|
108
|
+
exif?.CreateDate ||
|
|
109
|
+
exif?.ModifyDate;
|
|
110
|
+
|
|
111
|
+
if (!date) {
|
|
112
|
+
console.error(`No EXIF date found for file: ${filePath}`);
|
|
113
|
+
return null;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const offset = exif.OffsetTimeOriginal;
|
|
117
|
+
|
|
118
|
+
if (!offset) return date;
|
|
119
|
+
|
|
120
|
+
const sign = offset[0] === '-' ? -1 : 1;
|
|
121
|
+
const [h, m] = offset.slice(1).split(':').map(Number);
|
|
122
|
+
const offsetMs = sign * (h * 60 + m) * 60_000;
|
|
123
|
+
|
|
124
|
+
return new Date(date.getTime() + offsetMs);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default redate;
|
package/src/redate.js
CHANGED
|
@@ -1,41 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import exifr from 'exifr';
|
|
6
3
|
import https from "https";
|
|
7
4
|
import { execSync } from "child_process";
|
|
8
5
|
import readline from "readline";
|
|
9
6
|
import { getConfig, setConfig, TOKENS, DEFAULT_CONFIG } from "./config.js";
|
|
10
|
-
|
|
7
|
+
import redate from "./core.js";
|
|
11
8
|
|
|
12
9
|
const program = new Command();
|
|
13
10
|
|
|
14
11
|
program
|
|
15
12
|
.name("redate")
|
|
16
13
|
.description("Rename images based on EXIF dates")
|
|
17
|
-
.version("0.
|
|
14
|
+
.version("0.3.0");
|
|
18
15
|
|
|
19
16
|
program
|
|
20
17
|
.command("process <paths...>")
|
|
21
18
|
.description("Process file(s) or folder(s)")
|
|
22
19
|
.action(async (paths) => {
|
|
23
|
-
|
|
24
|
-
if (!fs.existsSync(p)) {
|
|
25
|
-
console.error(`Path does not exist: ${p}`);
|
|
26
|
-
continue;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const stats = fs.statSync(p);
|
|
30
|
-
|
|
31
|
-
if (stats.isFile()) {
|
|
32
|
-
await processFile(p);
|
|
33
|
-
} else if (stats.isDirectory()) {
|
|
34
|
-
await processFiles(p);
|
|
35
|
-
} else {
|
|
36
|
-
console.error(`Unsupported path type: ${p}`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
20
|
+
await redate(paths);
|
|
39
21
|
});
|
|
40
22
|
|
|
41
23
|
const configCommand = program
|
|
@@ -99,7 +81,7 @@ program
|
|
|
99
81
|
}
|
|
100
82
|
|
|
101
83
|
console.log("Updating...");
|
|
102
|
-
execSync("npm install -g redate", { stdio: "inherit" });
|
|
84
|
+
execSync("npm install -g redate-cli", { stdio: "inherit" });
|
|
103
85
|
|
|
104
86
|
console.log("Update completed.");
|
|
105
87
|
} catch (err) {
|
|
@@ -108,109 +90,6 @@ program
|
|
|
108
90
|
});
|
|
109
91
|
program.parse(process.argv);
|
|
110
92
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const fileHandlers = {
|
|
114
|
-
rename: (src, dest) => {
|
|
115
|
-
fs.renameSync(src, dest);
|
|
116
|
-
},
|
|
117
|
-
|
|
118
|
-
copy: (src, dest) => {
|
|
119
|
-
fs.copyFileSync(src, dest);
|
|
120
|
-
},
|
|
121
|
-
|
|
122
|
-
copy_in_folder: (src, dest) => {
|
|
123
|
-
const dir = path.dirname(src);
|
|
124
|
-
const targetDir = path.join(dir, "redate");
|
|
125
|
-
if (!fs.existsSync(targetDir)) {
|
|
126
|
-
fs.mkdirSync(targetDir);
|
|
127
|
-
}
|
|
128
|
-
fs.copyFileSync(src, path.join(targetDir, path.basename(dest)));
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
async function processFiles(folderPath) {
|
|
134
|
-
const files = fs.readdirSync(folderPath);
|
|
135
|
-
const config = getConfig();
|
|
136
|
-
for (const file of files) {
|
|
137
|
-
const filePath = path.join(folderPath, file);
|
|
138
|
-
|
|
139
|
-
if (!fs.statSync(filePath).isFile()) continue;
|
|
140
|
-
|
|
141
|
-
processFile(filePath, config);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
async function processFile(filePath, config) {
|
|
148
|
-
if (!config || config == null) {
|
|
149
|
-
config = getConfig();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const date = await getDateFromFile(filePath);
|
|
153
|
-
if (!date) return;
|
|
154
|
-
|
|
155
|
-
const originalName = path.basename(filePath);
|
|
156
|
-
const newFileName = formatFileName(date, originalName, config);
|
|
157
|
-
|
|
158
|
-
applyFileHandling(filePath, newFileName, config);
|
|
159
|
-
|
|
160
|
-
console.log(`Processed: ${newFileName}`);
|
|
161
|
-
}
|
|
162
|
-
function applyFileHandling(srcPath, newFileName, config) {
|
|
163
|
-
const dir = path.dirname(srcPath);
|
|
164
|
-
const dest = path.join(dir, newFileName);
|
|
165
|
-
|
|
166
|
-
const handler = fileHandlers[config.fileHandling];
|
|
167
|
-
|
|
168
|
-
if (!handler) {
|
|
169
|
-
throw new Error(`Unknown fileHandling: ${config.fileHandling}`);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
handler(srcPath, dest);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
export function formatFileName(date, originalName, config) {
|
|
177
|
-
let formatted = config.format;
|
|
178
|
-
|
|
179
|
-
const sortedTokens = Object.keys(TOKENS).sort((a, b) => b.length - a.length);
|
|
180
|
-
|
|
181
|
-
for (const key of sortedTokens) {
|
|
182
|
-
formatted = formatted.replaceAll(key, TOKENS[key].value(date));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const ext = path.extname(originalName);
|
|
186
|
-
return `${formatted}${ext}`;
|
|
187
|
-
}
|
|
188
|
-
async function getDateFromFile(filePath) {
|
|
189
|
-
const exif = await exifr.parse(filePath, { reviveValues: true });
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const date =
|
|
193
|
-
exif?.DateTimeOriginal ||
|
|
194
|
-
exif?.CreateDate ||
|
|
195
|
-
exif?.ModifyDate;
|
|
196
|
-
|
|
197
|
-
if (!date) {
|
|
198
|
-
console.error(`No EXIF date found for file: ${filePath}`);
|
|
199
|
-
return null;
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const offset = exif.OffsetTimeOriginal;
|
|
203
|
-
|
|
204
|
-
if (!offset) return date;
|
|
205
|
-
|
|
206
|
-
const sign = offset[0] === '-' ? -1 : 1;
|
|
207
|
-
const [h, m] = offset.slice(1).split(':').map(Number);
|
|
208
|
-
const offsetMs = sign * (h * 60 + m) * 60_000;
|
|
209
|
-
|
|
210
|
-
return new Date(date.getTime() + offsetMs);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
93
|
function getLatestVersionFromNpm(packageName) {
|
|
215
94
|
return new Promise((resolve, reject) => {
|
|
216
95
|
https
|