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" | |
| } |