exiftool-vendored
Version:
Efficient, cross-platform access to ExifTool
267 lines • 11.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
var dispose, inner;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
dispose = value[Symbol.asyncDispose];
}
if (dispose === void 0) {
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
dispose = value[Symbol.dispose];
if (async) inner = dispose;
}
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
env.stack.push({ value: value, dispose: dispose, async: async });
}
else if (async) {
env.stack.push({ async: true });
}
return value;
};
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
return function (env) {
function fail(e) {
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
env.hasError = true;
}
var r, s = 0;
function next() {
while (r = env.stack.pop()) {
try {
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
if (r.dispose) {
var result = r.dispose.call(r.value);
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
}
else s |= 1;
}
catch (e) {
fail(e);
}
}
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
if (env.hasError) throw env.error;
}
return next();
};
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
});
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnicodeTestMessage = exports.NonAlphaStrings = exports.IPTC_JPG = exports.tmpdir = exports.testDir = exports.expect = void 0;
exports.mkdirp = mkdirp;
exports.end = end;
exports.randomChar = randomChar;
exports.randomChars = randomChars;
exports.tmpname = tmpname;
exports.renderTagsWithISO = renderTagsWithISO;
exports.renderTagsWithRawValues = renderTagsWithRawValues;
exports.testImg = testImg;
exports.testFile = testFile;
exports.sha1 = sha1;
exports.sha1buffer = sha1buffer;
exports.assertEqlDateish = assertEqlDateish;
exports.measureSpawnTime = measureSpawnTime;
const batch_cluster_1 = require("batch-cluster");
const chai_1 = require("chai");
const chai_as_promised_1 = __importDefault(require("chai-as-promised"));
const node_crypto_1 = require("node:crypto");
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const node_process_1 = __importStar(require("node:process"));
const tmp_1 = require("tmp");
const Array_1 = require("./Array");
const DateTime_1 = require("./DateTime");
const ExifTool_1 = require("./ExifTool");
const IsWin32_1 = require("./IsWin32");
const Lazy_1 = require("./Lazy");
const Object_1 = require("./Object");
const String_1 = require("./String");
const Times_1 = require("./Times");
// Tests create many ExifTool instances, each adds a beforeExit listener via
// batch-cluster. Increase the limit to avoid spurious warnings.
node_process_1.default.setMaxListeners(20);
(0, chai_1.use)(chai_as_promised_1.default);
function mkdirp(dir) {
return (0, promises_1.mkdir)(dir, { recursive: true });
}
// Tests should be quiet unless LOG is set
(0, batch_cluster_1.setLogger)(batch_cluster_1.Log.withLevels(batch_cluster_1.Log.withTimestamps(batch_cluster_1.Log.filterLevels({
trace: console.log,
debug: console.log,
info: console.log,
warn: console.warn,
error: console.error,
}, node_process_1.env.LOG ?? "error"))));
var chai_2 = require("chai");
Object.defineProperty(exports, "expect", { enumerable: true, get: function () { return chai_2.expect; } });
exports.testDir = (0, node_path_1.join)(__dirname, "..", "test");
const LOWERCASE_A_CHAR_CODE = "a".charCodeAt(0);
const ALPHABET_LENGTH = "z".charCodeAt(0) - "a".charCodeAt(0) + 1;
async function end(et) {
await et?.end();
if (et != null)
(0, chai_1.expect)(et.batchCluster.internalErrorCount).to.eql(0);
}
function randomChar() {
return String.fromCharCode(LOWERCASE_A_CHAR_CODE + (0, node_crypto_1.randomInt)(ALPHABET_LENGTH));
}
function randomChars(chars = 8) {
return (0, Times_1.times)(chars, randomChar).join("");
}
exports.tmpdir = (0, Lazy_1.lazy)(() => (0, tmp_1.dirSync)().name);
function tmpname(prefix = "") {
return (0, node_path_1.join)((0, exports.tmpdir)(), prefix + randomChars());
}
function renderTagsWithISO(t) {
return (0, Object_1.fromEntries)(Object.entries(t).map(([k, v]) => k === "SourceFile" ? undefined : [k, v["toISOString"]?.() ?? v]));
}
function renderTagsWithRawValues(t) {
return (0, Object_1.fromEntries)(Object.entries(t).map(([k, v]) => [k, v["rawValue"]]));
}
/**
* Copy a test image to a tmp directory and return the path
*/
async function testImg({ srcBasename = "img.jpg", parentDir, destBasename, } = {}) {
const parent = tmpname();
const dir = parentDir == null ? parent : (0, node_path_1.join)(parent, parentDir);
await mkdirp(dir);
const dest = (0, node_path_1.join)(dir, destBasename ?? srcBasename);
await (0, promises_1.copyFile)((0, node_path_1.join)(exports.testDir, srcBasename), dest);
return dest;
}
exports.IPTC_JPG = "iptc.jpg";
async function testFile(name) {
const dir = tmpname();
await mkdirp(dir);
return (0, node_path_1.join)(dir, name);
}
function sha1(path) {
const d = new batch_cluster_1.Deferred();
const readStream = (0, node_fs_1.createReadStream)(path, { autoClose: true });
const sha = (0, node_crypto_1.createHash)("sha1");
readStream.on("data", (ea) => sha.update(ea));
readStream.on("error", (err) => d.reject(err));
readStream.on("end", () => d.resolve(sha.digest().toString("hex")));
return d.promise;
}
function sha1buffer(input) {
return (0, node_crypto_1.createHash)("sha1").update(input).digest().toString("hex");
}
function dateishToExifString(d) {
return d == null ? undefined : (0, String_1.isString)(d) ? d : (0, DateTime_1.toExifString)(d);
}
function assertEqlDateish(a, b) {
return (0, chai_1.expect)(dateishToExifString(a)).to.eql(dateishToExifString(b));
}
exports.NonAlphaStrings = (0, Array_1.compact)([
{ str: `'`, desc: "straight single quote" },
// windows doesn't support double-quotes in filenames (!!)
(0, IsWin32_1.isWin32)() ? undefined : { str: `"`, desc: "straight double quote" },
{ str: `‘’“”«»`, desc: "curly quotes" },
{ str: "ñöᵽȅ", desc: "latin extended" },
{ str: "✋", desc: "dingbats block" },
// These emoji tests fail on node 18.18+ and node 20 on Windows, but it's a
// bug in node, as it passes on macOS and Linux.
(0, IsWin32_1.isWin32)() ? undefined : { str: "😤", desc: "emoticons block" },
(0, IsWin32_1.isWin32)() ? undefined : { str: "🚵🏿♀", desc: "transport block" },
{ str: "你好", desc: "Mandarin" },
{ str: "ようこそ", desc: "Japanese" },
{ str: "ברוך הבא", desc: "Hebrew" },
{ str: "ਸੁਆਗਤ ਹੈ", desc: "Punjabi" },
]);
// Using Unicode escapes for quote characters to prevent editor/tool mangling:
// \u201c = " (left double quotation mark)
// \u201d = " (right double quotation mark)
// \u00ab = « (left-pointing double angle quotation mark)
// \u00bb = » (right-pointing double angle quotation mark)
// \u2018 = ' (left single quotation mark)
// \u2019 = ' (right single quotation mark)
// \u2039 = ‹ (single left-pointing angle quotation mark)
// \u203a = › (single right-pointing angle quotation mark)
exports.UnicodeTestMessage = `Double quotes("\u201c\u201d\u00ab\u00bb) and single quotes('\u2018\u2019\u2039\u203a) and backquotes(\`), oh my 👍🌹🐱👓🚵♀️. ਸੁਆਗਤ ਹੈ ยินดีต้อนรับ 환영하다 ようこそ 歡迎 欢迎 ברוך הבא خوش آمدید`;
let _cachedSpawnTimeMs;
/**
* Measure how long it takes to spawn ExifTool and get a response. This provides
* a baseline for setting realistic timeout values in tests. CI environments
* (especially Windows) can be 10-100x slower than local dev.
*
* Results are cached after first measurement to avoid overhead.
*
* CAUTION: You probably want to schedule this in a before() block so it doesn't
* impact test timeouts!
*/
async function measureSpawnTime() {
if (_cachedSpawnTimeMs != null) {
return _cachedSpawnTimeMs;
}
// Take 3 measurements, each with a fresh ExifTool instance
// to accurately measure cold-start spawn time
const measurements = [];
for (let i = 0; i < 4; i++) {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
const et = __addDisposableResource(env_1, new ExifTool_1.ExifTool({ maxProcs: 1 }), true);
const start = Date.now();
await et.version();
measurements.push(Date.now() - start);
}
catch (e_1) {
env_1.error = e_1;
env_1.hasError = true;
}
finally {
const result_1 = __disposeResources(env_1);
if (result_1)
await result_1;
}
}
_cachedSpawnTimeMs = Math.ceil(measurements.reduce((a, b) => a + b, 0) / measurements.length);
console.log("measureSpawnTime()", {
result: _cachedSpawnTimeMs,
measurements,
});
return _cachedSpawnTimeMs;
}
//# sourceMappingURL=_chai.spec.js.map