485 lines
22 KiB
JavaScript
485 lines
22 KiB
JavaScript
"use strict";
|
|
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 __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.exiftool = exports.ExifTool = exports.WriteTaskOptionFields = exports.DefaultWriteTaskOptions = exports.offsetMinutesToZoneName = exports.defaultVideosToUTC = exports.UnsetZoneOffsetMinutes = exports.UnsetZoneName = exports.UnsetZone = exports.TimezoneOffsetTagnames = exports.DefaultReadTaskOptions = exports.parseJSON = exports.isGeolocationTag = exports.GeolocationTagNames = exports.exiftoolPath = exports.ExifToolTask = exports.ExifTime = exports.ExifDateTime = exports.ExifDate = exports.DefaultMaxProcs = exports.DefaultExiftoolArgs = exports.DefaultExifToolOptions = exports.CapturedAtTagNames = exports.BinaryField = void 0;
|
|
const bc = __importStar(require("batch-cluster"));
|
|
const _cp = __importStar(require("node:child_process"));
|
|
const _fs = __importStar(require("node:fs"));
|
|
const node_process_1 = __importDefault(require("node:process"));
|
|
const Array_1 = require("./Array");
|
|
const AsyncRetry_1 = require("./AsyncRetry");
|
|
const BinaryExtractionTask_1 = require("./BinaryExtractionTask");
|
|
const BinaryToBufferTask_1 = require("./BinaryToBufferTask");
|
|
const DefaultExifToolOptions_1 = require("./DefaultExifToolOptions");
|
|
const DeleteAllTagsArgs_1 = require("./DeleteAllTagsArgs");
|
|
const ExifToolOptions_1 = require("./ExifToolOptions");
|
|
const ExiftoolPath_1 = require("./ExiftoolPath");
|
|
const IsWin32_1 = require("./IsWin32");
|
|
const Lazy_1 = require("./Lazy");
|
|
const Object_1 = require("./Object");
|
|
const Pick_1 = require("./Pick");
|
|
const ReadRawTask_1 = require("./ReadRawTask");
|
|
const ReadTask_1 = require("./ReadTask");
|
|
const RewriteAllTagsTask_1 = require("./RewriteAllTagsTask");
|
|
const String_1 = require("./String");
|
|
const VersionTask_1 = require("./VersionTask");
|
|
const Which_1 = require("./Which");
|
|
const WriteTask_1 = require("./WriteTask");
|
|
var BinaryField_1 = require("./BinaryField");
|
|
Object.defineProperty(exports, "BinaryField", { enumerable: true, get: function () { return BinaryField_1.BinaryField; } });
|
|
var CapturedAtTagNames_1 = require("./CapturedAtTagNames");
|
|
Object.defineProperty(exports, "CapturedAtTagNames", { enumerable: true, get: function () { return CapturedAtTagNames_1.CapturedAtTagNames; } });
|
|
var DefaultExifToolOptions_2 = require("./DefaultExifToolOptions");
|
|
Object.defineProperty(exports, "DefaultExifToolOptions", { enumerable: true, get: function () { return DefaultExifToolOptions_2.DefaultExifToolOptions; } });
|
|
var DefaultExiftoolArgs_1 = require("./DefaultExiftoolArgs");
|
|
Object.defineProperty(exports, "DefaultExiftoolArgs", { enumerable: true, get: function () { return DefaultExiftoolArgs_1.DefaultExiftoolArgs; } });
|
|
var DefaultMaxProcs_1 = require("./DefaultMaxProcs");
|
|
Object.defineProperty(exports, "DefaultMaxProcs", { enumerable: true, get: function () { return DefaultMaxProcs_1.DefaultMaxProcs; } });
|
|
var ExifDate_1 = require("./ExifDate");
|
|
Object.defineProperty(exports, "ExifDate", { enumerable: true, get: function () { return ExifDate_1.ExifDate; } });
|
|
var ExifDateTime_1 = require("./ExifDateTime");
|
|
Object.defineProperty(exports, "ExifDateTime", { enumerable: true, get: function () { return ExifDateTime_1.ExifDateTime; } });
|
|
var ExifTime_1 = require("./ExifTime");
|
|
Object.defineProperty(exports, "ExifTime", { enumerable: true, get: function () { return ExifTime_1.ExifTime; } });
|
|
var ExifToolTask_1 = require("./ExifToolTask");
|
|
Object.defineProperty(exports, "ExifToolTask", { enumerable: true, get: function () { return ExifToolTask_1.ExifToolTask; } });
|
|
var ExiftoolPath_2 = require("./ExiftoolPath");
|
|
Object.defineProperty(exports, "exiftoolPath", { enumerable: true, get: function () { return ExiftoolPath_2.exiftoolPath; } });
|
|
var GeolocationTags_1 = require("./GeolocationTags");
|
|
Object.defineProperty(exports, "GeolocationTagNames", { enumerable: true, get: function () { return GeolocationTags_1.GeolocationTagNames; } });
|
|
Object.defineProperty(exports, "isGeolocationTag", { enumerable: true, get: function () { return GeolocationTags_1.isGeolocationTag; } });
|
|
var JSON_1 = require("./JSON");
|
|
Object.defineProperty(exports, "parseJSON", { enumerable: true, get: function () { return JSON_1.parseJSON; } });
|
|
var ReadTask_2 = require("./ReadTask");
|
|
Object.defineProperty(exports, "DefaultReadTaskOptions", { enumerable: true, get: function () { return ReadTask_2.DefaultReadTaskOptions; } });
|
|
var Timezones_1 = require("./Timezones");
|
|
Object.defineProperty(exports, "TimezoneOffsetTagnames", { enumerable: true, get: function () { return Timezones_1.TimezoneOffsetTagnames; } });
|
|
Object.defineProperty(exports, "UnsetZone", { enumerable: true, get: function () { return Timezones_1.UnsetZone; } });
|
|
Object.defineProperty(exports, "UnsetZoneName", { enumerable: true, get: function () { return Timezones_1.UnsetZoneName; } });
|
|
Object.defineProperty(exports, "UnsetZoneOffsetMinutes", { enumerable: true, get: function () { return Timezones_1.UnsetZoneOffsetMinutes; } });
|
|
Object.defineProperty(exports, "defaultVideosToUTC", { enumerable: true, get: function () { return Timezones_1.defaultVideosToUTC; } });
|
|
Object.defineProperty(exports, "offsetMinutesToZoneName", { enumerable: true, get: function () { return Timezones_1.offsetMinutesToZoneName; } });
|
|
var WriteTask_2 = require("./WriteTask");
|
|
Object.defineProperty(exports, "DefaultWriteTaskOptions", { enumerable: true, get: function () { return WriteTask_2.DefaultWriteTaskOptions; } });
|
|
Object.defineProperty(exports, "WriteTaskOptionFields", { enumerable: true, get: function () { return WriteTask_2.WriteTaskOptionFields; } });
|
|
/**
|
|
* This is the hardcoded path in the exiftool shebang line
|
|
*/
|
|
const PERL = "/usr/bin/perl";
|
|
/**
|
|
* Is the #!/usr/bin/perl shebang line in exiftool-vendored.pl going to fail? If
|
|
* so, we need to find `perl` ourselves, and ignore the shebang line.
|
|
*/
|
|
const _ignoreShebang = (0, Lazy_1.lazy)(() => !(0, IsWin32_1.isWin32)() && !_fs.existsSync(PERL));
|
|
const whichPerl = (0, Lazy_1.lazy)(async () => {
|
|
const result = await (0, Which_1.which)(PERL);
|
|
if (result == null) {
|
|
throw new Error("Perl must be installed. Please add perl to your $PATH and try again.");
|
|
}
|
|
return result;
|
|
});
|
|
/**
|
|
* Manages delegating calls to a cluster of ExifTool child processes.
|
|
*
|
|
* **NOTE: Instances are expensive!**
|
|
*
|
|
* * use either the default exported singleton instance of this class,
|
|
* {@link exiftool}, or your own singleton
|
|
*
|
|
* * make sure you await {@link ExifTool.end} when you're done with an instance
|
|
* to clean up subprocesses
|
|
*
|
|
* * review the {@link ExifToolOptions} for configuration options--the default
|
|
* values are conservative to avoid overwhelming your system.
|
|
*
|
|
* @see https://photostructure.github.io/exiftool-vendored.js/ for more documentation.
|
|
*/
|
|
class ExifTool {
|
|
options;
|
|
batchCluster;
|
|
constructor(options = {}) {
|
|
if (options != null && typeof options !== "object") {
|
|
throw new Error("Please update caller to the new ExifTool constructor API");
|
|
}
|
|
const o = (0, ExifToolOptions_1.handleDeprecatedOptions)({
|
|
...DefaultExifToolOptions_1.DefaultExifToolOptions,
|
|
...options,
|
|
});
|
|
const ignoreShebang = o.ignoreShebang ?? _ignoreShebang();
|
|
const env = { ...o.exiftoolEnv, LANG: "C" };
|
|
if ((0, String_1.notBlank)(node_process_1.default.env.EXIFTOOL_HOME) && (0, String_1.blank)(env.EXIFTOOL_HOME)) {
|
|
env.EXIFTOOL_HOME = node_process_1.default.env.EXIFTOOL_HOME;
|
|
}
|
|
const spawnOpts = {
|
|
stdio: "pipe",
|
|
shell: false,
|
|
detached: false, // < no orphaned exiftool procs, please
|
|
env,
|
|
};
|
|
const processFactory = async () => ignoreShebang
|
|
? _cp.spawn(await whichPerl(), [await this.exiftoolPath(), ...o.exiftoolArgs], spawnOpts)
|
|
: _cp.spawn(await this.exiftoolPath(), o.exiftoolArgs, spawnOpts);
|
|
this.options = {
|
|
...o,
|
|
ignoreShebang,
|
|
processFactory,
|
|
};
|
|
this.batchCluster = new bc.BatchCluster(this.options);
|
|
}
|
|
exiftoolPath = (0, Lazy_1.lazy)(async () => {
|
|
const o = await this.options.exiftoolPath;
|
|
if ((0, String_1.isString)(o) && (0, String_1.notBlank)(o))
|
|
return o;
|
|
if ((0, Object_1.isFunction)(o))
|
|
return o(this.options.logger());
|
|
return (0, ExiftoolPath_1.exiftoolPath)(this.options.logger());
|
|
});
|
|
#taskOptions = (0, Lazy_1.lazy)(() => (0, Pick_1.pick)(this.options, "ignoreMinorErrors"));
|
|
/**
|
|
* Register life cycle event listeners. Delegates to BatchProcess.
|
|
*/
|
|
on = (event, listener) => this.batchCluster.on(event, listener);
|
|
/**
|
|
* Unregister life cycle event listeners. Delegates to BatchProcess.
|
|
*/
|
|
off = (event, listener) => this.batchCluster.off(event, listener);
|
|
/**
|
|
* @return a promise holding the version number of the vendored ExifTool
|
|
*/
|
|
version() {
|
|
return this.enqueueTask(() => new VersionTask_1.VersionTask(this.options));
|
|
}
|
|
read(file, argsOrOptions, options) {
|
|
const opts = {
|
|
...(0, Pick_1.pick)(this.options, ...ReadTask_1.ReadTaskOptionFields),
|
|
...((0, Object_1.isObject)(argsOrOptions) ? argsOrOptions : options),
|
|
};
|
|
opts.readArgs =
|
|
(0, Array_1.ifArray)(argsOrOptions) ?? (0, Array_1.ifArray)(opts.readArgs) ?? this.options.readArgs;
|
|
return this.enqueueTask(() => ReadTask_1.ReadTask.for(file, opts)); // < no way to know at compile time if we're going to get back a T!
|
|
}
|
|
/**
|
|
* Read the tags from `file`, without any post-processing of ExifTool values.
|
|
*
|
|
* **You probably want `read`, not this method. READ THE REST OF THIS COMMENT
|
|
* CAREFULLY.**
|
|
*
|
|
* If you want to extract specific tag values from a file, you may want to use
|
|
* this, but all data validation and inference heuristics provided by `read`
|
|
* will be skipped.
|
|
*
|
|
* Note that performance will be very similar to `read`, and will actually be
|
|
* worse if you don't include `-fast` or `-fast2` (as the most expensive bit
|
|
* is the perl interpreter and scanning the file on disk).
|
|
*
|
|
* @param args any additional arguments other than the file path. Note that
|
|
* "-json", and the Windows unicode filename handler flags, "-charset
|
|
* filename=utf8", will be added automatically.
|
|
*
|
|
* @return Note that the return value will be similar to `Tags`, but with no
|
|
* date, time, or other rich type parsing that you get from `.read()`. The
|
|
* field values will be `string | number | string[]`.
|
|
*
|
|
* @see https://github.com/photostructure/exiftool-vendored.js/issues/44 for
|
|
* typing details.
|
|
*/
|
|
readRaw(file, args = []) {
|
|
return this.enqueueTask(() => ReadRawTask_1.ReadRawTask.for(file, args, this.#taskOptions()));
|
|
}
|
|
/**
|
|
* Write the given `tags` to `file`.
|
|
*
|
|
* **NOTE: no input validation is done by this library.** ExifTool, however,
|
|
* is strict about tag names and values in the context of the format of file
|
|
* being written to.
|
|
*
|
|
* @param file an existing file to write `tags` to
|
|
*
|
|
* @param tags the tags to write to `file`.
|
|
*
|
|
* @param options overrides to the default ExifTool options provided to the
|
|
* ExifTool constructor.
|
|
*
|
|
* @returns Either the promise will be resolved if the tags are written to
|
|
* successfully, or the promise will be rejected if there are errors or
|
|
* warnings.
|
|
*
|
|
* @see https://exiftool.org/exiftool_pod.html#overwrite_original
|
|
*/
|
|
write(file, tags, writeArgsOrOptions, options) {
|
|
const opts = {
|
|
...(0, Pick_1.pick)(this.options, ...WriteTask_1.WriteTaskOptionFields),
|
|
...((0, Object_1.isObject)(writeArgsOrOptions) ? writeArgsOrOptions : options),
|
|
};
|
|
opts.writeArgs =
|
|
(0, Array_1.ifArray)(writeArgsOrOptions) ??
|
|
(0, Array_1.ifArray)(opts.writeArgs) ??
|
|
this.options.writeArgs;
|
|
// don't retry because writes might not be idempotent (e.g. incrementing
|
|
// timestamps by an hour)
|
|
const retriable = false;
|
|
return this.enqueueTask(() => WriteTask_1.WriteTask.for(file, tags, opts), retriable);
|
|
}
|
|
/**
|
|
* This will strip `file` of all metadata tags. The original file (with the
|
|
* name `${FILENAME}_original`) will be retained. Note that some tags, like
|
|
* stat information and image dimensions, are intrinsic to the file and will
|
|
* continue to exist if you re-`read` the file.
|
|
*
|
|
* @param {string} file the file to strip of metadata
|
|
*
|
|
* @param {(keyof Tags | string)[]} opts.retain optional. If provided, this is
|
|
* a list of metadata keys to **not** delete.
|
|
*/
|
|
deleteAllTags(file, opts) {
|
|
const writeArgs = [...DeleteAllTagsArgs_1.DeleteAllTagsArgs];
|
|
for (const ea of opts?.retain ?? []) {
|
|
writeArgs.push(`-${ea}<${ea}`);
|
|
}
|
|
return this.write(file, {}, { ...(0, Object_1.omit)(opts ?? {}, "retain"), writeArgs });
|
|
}
|
|
/**
|
|
* Extract the low-resolution thumbnail in `path/to/image.jpg` and write it to
|
|
* `path/to/thumbnail.jpg`.
|
|
*
|
|
* Note that these images can be less than .1 megapixels in size.
|
|
*
|
|
* @return a `Promise<void>`
|
|
*
|
|
* @throws if the file could not be read or the output not written
|
|
*/
|
|
extractThumbnail(imageFile, thumbnailFile, opts) {
|
|
return this.extractBinaryTag("ThumbnailImage", imageFile, thumbnailFile, opts);
|
|
}
|
|
/**
|
|
* Extract the "preview" image in `path/to/image.jpg` and write it to
|
|
* `path/to/preview.jpg`.
|
|
*
|
|
* The size of these images varies widely, and is present in dSLR images.
|
|
* Canon, Fuji, Olympus, and Sony use this tag.
|
|
*
|
|
* @return a `Promise<void>`
|
|
*
|
|
* @throws if the file could not be read or the output not written
|
|
*/
|
|
extractPreview(imageFile, previewFile, opts) {
|
|
return this.extractBinaryTag("PreviewImage", imageFile, previewFile, opts);
|
|
}
|
|
/**
|
|
* Extract the "JpgFromRaw" image in `path/to/image.jpg` and write it to
|
|
* `path/to/fromRaw.jpg`.
|
|
*
|
|
* This size of these images varies widely, and is not present in all RAW
|
|
* images. Nikon and Panasonic use this tag.
|
|
*
|
|
* @return a `Promise<void>`
|
|
*
|
|
* @throws if the file could not be read or the output not written.
|
|
*/
|
|
extractJpgFromRaw(imageFile, outputFile, opts) {
|
|
return this.extractBinaryTag("JpgFromRaw", imageFile, outputFile, opts);
|
|
}
|
|
/**
|
|
* Extract a given binary value from "tagname" tag associated to
|
|
* `path/to/image.jpg` and write it to `dest` (which cannot exist and whose
|
|
* directory must already exist).
|
|
*
|
|
* @return a `Promise<void>`
|
|
*
|
|
* @throws if the binary output not be written to `dest`.
|
|
*/
|
|
async extractBinaryTag(tagname, src, dest, opts) {
|
|
// BinaryExtractionTask returns a stringified error if the output indicates
|
|
// the task should not be retried.
|
|
const maybeError = await this.enqueueTask(() => BinaryExtractionTask_1.BinaryExtractionTask.for(tagname, src, dest, {
|
|
...this.#taskOptions(),
|
|
...opts,
|
|
}));
|
|
if (maybeError != null) {
|
|
throw new Error(maybeError);
|
|
}
|
|
}
|
|
/**
|
|
* Extract a given binary value from "tagname" tag associated to
|
|
* `path/to/image.jpg` as a `Buffer`. This has the advantage of not writing to
|
|
* a file, but if the payload associated to `tagname` is large, this can cause
|
|
* out-of-memory errors.
|
|
*
|
|
* @return a `Promise<Buffer>`
|
|
*
|
|
* @throws if the file or tag is missing.
|
|
*/
|
|
async extractBinaryTagToBuffer(tagname, imageFile, opts) {
|
|
const result = await this.enqueueTask(() => BinaryToBufferTask_1.BinaryToBufferTask.for(tagname, imageFile, {
|
|
...this.#taskOptions(),
|
|
...opts,
|
|
}));
|
|
if (Buffer.isBuffer(result)) {
|
|
return result;
|
|
}
|
|
else if (result instanceof Error) {
|
|
throw result;
|
|
}
|
|
else {
|
|
throw new Error("Unexpected result from BinaryToBufferTask: " + JSON.stringify(result));
|
|
}
|
|
}
|
|
/**
|
|
* Attempt to fix metadata problems in JPEG images by deleting all metadata
|
|
* and rebuilding from scratch. After repairing an image you should be able to
|
|
* write to it without errors, but some metadata from the original image may
|
|
* be lost in the process.
|
|
*
|
|
* This should only be applied as a last resort to images whose metadata is
|
|
* not readable via {@link ExifTool.read}.
|
|
*
|
|
* @see https://exiftool.org/faq.html#Q20
|
|
*
|
|
* @param {string} inputFile the path to the problematic image
|
|
* @param {string} outputFile the path to write the repaired image
|
|
* @param {boolean} opts.allowMakerNoteRepair if there are problems with MakerNote
|
|
* tags, allow ExifTool to apply heuristics to recover corrupt tags. See
|
|
* exiftool's `-F` flag.
|
|
* @return {Promise<void>} resolved after the outputFile has been written.
|
|
*/
|
|
rewriteAllTags(inputFile, outputFile, opts) {
|
|
return this.enqueueTask(() => RewriteAllTagsTask_1.RewriteAllTagsTask.for(inputFile, outputFile, {
|
|
allowMakerNoteRepair: false,
|
|
...this.#taskOptions(),
|
|
...opts,
|
|
}));
|
|
}
|
|
/**
|
|
* Shut down running ExifTool child processes. No subsequent requests will be
|
|
* accepted.
|
|
*
|
|
* This may need to be called in `after` or `finally` clauses in tests or
|
|
* scripts for them to exit cleanly.
|
|
*/
|
|
end(gracefully = true) {
|
|
return this.batchCluster.end(gracefully).promise;
|
|
}
|
|
/**
|
|
* @return true if `.end()` has been invoked
|
|
*/
|
|
get ended() {
|
|
return this.batchCluster.ended;
|
|
}
|
|
// calling whichPerl through this lazy() means we only do that task once per
|
|
// instance.
|
|
#checkForPerl = (0, Lazy_1.lazy)(async () => {
|
|
if (this.options.checkPerl) {
|
|
await whichPerl(); // < throws if perl is missing
|
|
}
|
|
});
|
|
/**
|
|
* Most users will not need to use `enqueueTask` directly. This method
|
|
* supports submitting custom `BatchCluster` tasks.
|
|
*
|
|
* @param task is a thunk to support retries by providing new instances on retries.
|
|
*
|
|
* @see BinaryExtractionTask for an example task implementation
|
|
*/
|
|
enqueueTask(task, retriable = true) {
|
|
const f = async () => {
|
|
await this.#checkForPerl();
|
|
return this.batchCluster.enqueueTask(task());
|
|
};
|
|
return retriable ? (0, AsyncRetry_1.retryOnReject)(f, this.options.taskRetries) : f();
|
|
}
|
|
/**
|
|
* @return the currently running ExifTool processes. Note that on Windows,
|
|
* these are only the process IDs of the directly-spawned ExifTool wrapper,
|
|
* and not the actual perl vm. This should only really be relevant for
|
|
* integration tests that verify processes are cleaned up properly.
|
|
*/
|
|
get pids() {
|
|
return this.batchCluster.pids();
|
|
}
|
|
/**
|
|
* @return the number of pending (not currently worked on) tasks
|
|
*/
|
|
get pendingTasks() {
|
|
return this.batchCluster.pendingTaskCount;
|
|
}
|
|
/**
|
|
* @return the total number of child processes created by this instance
|
|
*/
|
|
get spawnedProcs() {
|
|
return this.batchCluster.spawnedProcCount;
|
|
}
|
|
/**
|
|
* @return the current number of child processes currently servicing tasks
|
|
*/
|
|
get busyProcs() {
|
|
return this.batchCluster.busyProcCount;
|
|
}
|
|
/**
|
|
* @return report why child processes were recycled
|
|
*/
|
|
childEndCounts() {
|
|
return this.batchCluster.childEndCounts;
|
|
}
|
|
/**
|
|
* Shut down any currently-running child processes. New child processes will
|
|
* be started automatically to handle new tasks.
|
|
*/
|
|
closeChildProcesses(gracefully = true) {
|
|
return this.batchCluster.closeChildProcesses(gracefully);
|
|
}
|
|
}
|
|
exports.ExifTool = ExifTool;
|
|
/**
|
|
* Use this singleton rather than instantiating new {@link ExifTool} instances
|
|
* in order to leverage a single running ExifTool process.
|
|
*
|
|
* As of v3.0, its {@link ExifToolOptions.maxProcs} is set to the number of
|
|
* CPUs on the current system; no more than `maxProcs` instances of `exiftool`
|
|
* will be spawned. You may want to experiment with smaller or larger values
|
|
* for `maxProcs`, depending on CPU and disk speed of your system and
|
|
* performance tradeoffs.
|
|
*
|
|
* Note that each child process consumes between 10 and 50 MB of RAM. If you
|
|
* have limited system resources you may want to use a smaller `maxProcs`
|
|
* value.
|
|
*
|
|
* See the source of {@link DefaultExifToolOptions} for more details about how
|
|
* this instance is configured.
|
|
*/
|
|
exports.exiftool = new ExifTool();
|
|
//# sourceMappingURL=ExifTool.js.map
|