Requires exiftool executable to be available within the system path.
- Copy this script file and the package.json to a local directory.
- Follow these instructions: https://stackoverflow.com/a/46509168/11793117.
Requires exiftool executable to be available within the system path.
import { execSync } from "node:child_process"; | |
const workdir = process.argv[2] || process.cwd(); | |
import { resolve, basename, dirname } from "path"; | |
import { readdirSync, renameSync } from "fs"; | |
import { join } from "node:path"; | |
import { closeSync, existsSync, openSync, rmSync, watch } from "node:fs"; | |
async function* getFiles(dir) { | |
const dirents = readdirSync(dir, { withFileTypes: true }); | |
for (const dirent of dirents) { | |
const res = resolve(dir, dirent.name); | |
if (dirent.isDirectory()) { | |
yield* getFiles(res); | |
} else { | |
yield res; | |
} | |
} | |
} | |
async function getKeypress() { | |
return new Promise((resolve) => { | |
var stdin = process.stdin; | |
stdin.setRawMode(true); // so get each keypress | |
stdin.resume(); // resume stdin in the parent process | |
stdin.once("data", onData); // like on but removes listener also | |
function onData(buffer) { | |
stdin.setRawMode(false); | |
resolve(buffer.toString()); | |
} | |
}); | |
} | |
/** | |
* Returns a hash code from a string | |
* @param {String} str The string to hash. | |
* @return {Number} A 32bit integer | |
* @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ | |
*/ | |
function hashCode(str) { | |
let hash = 0; | |
for (let i = 0, len = str.length; i < len; i++) { | |
let chr = str.charCodeAt(i); | |
hash = (hash << 5) - hash + chr; | |
hash |= 0; // Convert to 32bit integer | |
} | |
return hash; | |
} | |
function isWritable(filePath) { | |
let fileAccess = false; | |
try { | |
closeSync(openSync(filePath, "r+")); | |
fileAccess = true; | |
} catch (err) { | |
console.log({ err }); | |
} | |
return fileAccess; | |
} | |
async function toArray(asyncIterator) { | |
const arr = []; | |
for await (const i of asyncIterator) arr.push(i); | |
return arr; | |
} | |
function purifyFile(file) { | |
try { | |
if (!isWritable(file)) { | |
return; | |
} | |
const parent = dirname(file); | |
const hashName = Math.abs(hashCode(file)).toString("16"); | |
const temp = join(parent, hashName); | |
console.log(`${file} -> ${temp}`); | |
renameSync(file, temp); | |
try { | |
execSync(`exiftool -s -all= "${temp}"`); | |
} catch (e) { | |
} finally { | |
renameSync(temp, file); | |
try { | |
rmSync(temp + "_original"); | |
} catch (e) { | |
// console.log({ e }); | |
return; | |
} | |
} | |
} catch (e) { | |
console.log({ | |
err: e, | |
}); | |
} | |
} | |
(async () => { | |
const files = await toArray(getFiles(workdir)); | |
for (const file of files) { | |
purifyFile(file); | |
} | |
})(); |
{ | |
"name": "exiftool", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"type": "module", | |
"scripts": { | |
"test": "echo \"Error: no test specified\" && exit 1" | |
}, | |
"keywords": [], | |
"author": "", | |
"license": "ISC" | |
} |