redate-cli 0.1.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/package.json +21 -0
- package/src/redate.js +110 -0
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "redate-cli",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A CLI tool for renaming images based on EXIF data.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/redate.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"redate": "./src/redate.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/niilopoutanen/redate.git"
|
|
13
|
+
},
|
|
14
|
+
"author": "Niilo Poutanen",
|
|
15
|
+
"license": "ISC",
|
|
16
|
+
"homepage": "https://github.com/niilopoutanen/redate",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"commander": "^14.0.2",
|
|
19
|
+
"exifr": "^7.1.3"
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/redate.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import exifr from 'exifr';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const program = new Command();
|
|
9
|
+
|
|
10
|
+
program
|
|
11
|
+
.name("redate")
|
|
12
|
+
.description("Rename images based on EXIF dates")
|
|
13
|
+
.version("0.1.1")
|
|
14
|
+
.argument("[paths...]", "File(s) or folder(s) to process")
|
|
15
|
+
.action((paths) => {
|
|
16
|
+
if (!paths || paths.length === 0) {
|
|
17
|
+
program.help({ error: true });
|
|
18
|
+
}
|
|
19
|
+
for (const p of paths) {
|
|
20
|
+
if (!fs.existsSync(p)) {
|
|
21
|
+
console.error(`Path does not exist: ${p}`);
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const stats = fs.statSync(p);
|
|
26
|
+
if (stats.isFile()) {
|
|
27
|
+
processFile(p);
|
|
28
|
+
} else if (stats.isDirectory()) {
|
|
29
|
+
processFiles(p);
|
|
30
|
+
} else {
|
|
31
|
+
console.error(`Unsupported path type: ${p}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
program.parse(process.argv);
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
function processFiles(folderPath) {
|
|
41
|
+
const files = fs.readdirSync(folderPath);
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
const filePath = path.join(folderPath, file);
|
|
44
|
+
if (fs.statSync(filePath).isFile()) {
|
|
45
|
+
getDateFromFile(filePath).then((date) => {
|
|
46
|
+
if (date) {
|
|
47
|
+
const newFileName = formatFileName(date, file);
|
|
48
|
+
fs.renameSync(filePath, path.join(folderPath, newFileName));
|
|
49
|
+
console.log(`Renamed to: ${newFileName}`);
|
|
50
|
+
}
|
|
51
|
+
}).catch((err) => {
|
|
52
|
+
console.error(`Error reading EXIF data from ${filePath}: ${err}`);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
function processFile(filePath) {
|
|
61
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
62
|
+
console.log(`Processing file: ${filePath}`);
|
|
63
|
+
getDateFromFile(filePath).then((date) => {
|
|
64
|
+
if (date) {
|
|
65
|
+
const dir = path.dirname(filePath);
|
|
66
|
+
const originalName = path.basename(filePath);
|
|
67
|
+
const newFileName = formatFileName(date, originalName);
|
|
68
|
+
fs.renameSync(filePath, path.join(dir, newFileName));
|
|
69
|
+
console.log(`Renamed to: ${newFileName}`);
|
|
70
|
+
} else {
|
|
71
|
+
console.log(`No EXIF date found for file: ${filePath}`);
|
|
72
|
+
}
|
|
73
|
+
}).catch((err) => {
|
|
74
|
+
console.error(`Error reading EXIF data from ${filePath}: ${err}`);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.error(`File does not exist: ${filePath}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
function formatFileName(date, originalName) {
|
|
84
|
+
const yyyy = date.getFullYear();
|
|
85
|
+
const mm = String(date.getMonth() + 1).padStart(2, '0');
|
|
86
|
+
const dd = String(date.getDate()).padStart(2, '0');
|
|
87
|
+
const hh = String(date.getHours()).padStart(2, '0');
|
|
88
|
+
const min = String(date.getMinutes()).padStart(2, '0');
|
|
89
|
+
const ss = String(date.getSeconds()).padStart(2, '0');
|
|
90
|
+
const ext = path.extname(originalName);
|
|
91
|
+
|
|
92
|
+
return `${yyyy}-${mm}-${dd} ${hh}-${min}-${ss}${ext}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
async function getDateFromFile(filePath) {
|
|
97
|
+
const exif = await exifr.parse(filePath, { reviveValues: true });
|
|
98
|
+
if (!exif?.DateTimeOriginal) return null;
|
|
99
|
+
|
|
100
|
+
const date = exif.DateTimeOriginal;
|
|
101
|
+
const offset = exif.OffsetTimeOriginal;
|
|
102
|
+
|
|
103
|
+
if (!offset) return date;
|
|
104
|
+
|
|
105
|
+
const sign = offset[0] === '-' ? -1 : 1;
|
|
106
|
+
const [h, m] = offset.slice(1).split(':').map(Number);
|
|
107
|
+
const offsetMs = sign * (h * 60 + m) * 60_000;
|
|
108
|
+
|
|
109
|
+
return new Date(date.getTime() + offsetMs);
|
|
110
|
+
}
|