"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.zoneToShortOffset = exports.validTzOffsetMinutes = exports.ValidTimezoneOffsets = exports.UnsetZoneOffsetMinutes = exports.UnsetZoneName = exports.UnsetZone = exports.TimezoneOffsetTagnames = exports.TimezoneOffsetRE = exports.parseTimezoneOffsetToMinutes = exports.parseTimezoneOffsetMatch = exports.offsetMinutesToZoneName = exports.normalizeZone = exports.isZoneValid = exports.isZoneUnset = exports.isZone = exports.isUTC = exports.inferLikelyOffsetMinutes = exports.extractZone = exports.extractTzOffsetFromUTCOffset = exports.extractTzOffsetFromTags = exports.equivalentZones = exports.defaultVideosToUTC = exports.ArchaicTimezoneOffsets = exports.TagDescriptions = exports.strEnum = exports.Settings = exports.Setting = exports.DefaultReadTaskOptions = exports.DefaultReadRawTaskOptions = 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 = exports.retryOnReject = 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 Settings_1 = require("./Settings");
const String_1 = require("./String");
const VersionTask_1 = require("./VersionTask");
const Which_1 = require("./Which");
const WriteTask_1 = require("./WriteTask");
var AsyncRetry_2 = require("./AsyncRetry");
Object.defineProperty(exports, "retryOnReject", { enumerable: true, get: function () { return AsyncRetry_2.retryOnReject; } });
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 ReadRawTask_2 = require("./ReadRawTask");
Object.defineProperty(exports, "DefaultReadRawTaskOptions", { enumerable: true, get: function () { return ReadRawTask_2.DefaultReadRawTaskOptions; } });
var ReadTask_2 = require("./ReadTask");
Object.defineProperty(exports, "DefaultReadTaskOptions", { enumerable: true, get: function () { return ReadTask_2.DefaultReadTaskOptions; } });
var Settings_2 = require("./Settings");
Object.defineProperty(exports, "Setting", { enumerable: true, get: function () { return Settings_2.Setting; } });
Object.defineProperty(exports, "Settings", { enumerable: true, get: function () { return Settings_2.Settings; } });
var StrEnum_1 = require("./StrEnum");
Object.defineProperty(exports, "strEnum", { enumerable: true, get: function () { return StrEnum_1.strEnum; } });
var TagDescriptions_1 = require("./TagDescriptions");
Object.defineProperty(exports, "TagDescriptions", { enumerable: true, get: function () { return TagDescriptions_1.TagDescriptions; } });
var Timezones_1 = require("./Timezones");
Object.defineProperty(exports, "ArchaicTimezoneOffsets", { enumerable: true, get: function () { return Timezones_1.ArchaicTimezoneOffsets; } });
Object.defineProperty(exports, "defaultVideosToUTC", { enumerable: true, get: function () { return Timezones_1.defaultVideosToUTC; } });
Object.defineProperty(exports, "equivalentZones", { enumerable: true, get: function () { return Timezones_1.equivalentZones; } });
Object.defineProperty(exports, "extractTzOffsetFromTags", { enumerable: true, get: function () { return Timezones_1.extractTzOffsetFromTags; } });
Object.defineProperty(exports, "extractTzOffsetFromUTCOffset", { enumerable: true, get: function () { return Timezones_1.extractTzOffsetFromUTCOffset; } });
Object.defineProperty(exports, "extractZone", { enumerable: true, get: function () { return Timezones_1.extractZone; } });
Object.defineProperty(exports, "inferLikelyOffsetMinutes", { enumerable: true, get: function () { return Timezones_1.inferLikelyOffsetMinutes; } });
Object.defineProperty(exports, "isUTC", { enumerable: true, get: function () { return Timezones_1.isUTC; } });
Object.defineProperty(exports, "isZone", { enumerable: true, get: function () { return Timezones_1.isZone; } });
Object.defineProperty(exports, "isZoneUnset", { enumerable: true, get: function () { return Timezones_1.isZoneUnset; } });
Object.defineProperty(exports, "isZoneValid", { enumerable: true, get: function () { return Timezones_1.isZoneValid; } });
Object.defineProperty(exports, "normalizeZone", { enumerable: true, get: function () { return Timezones_1.normalizeZone; } });
Object.defineProperty(exports, "offsetMinutesToZoneName", { enumerable: true, get: function () { return Timezones_1.offsetMinutesToZoneName; } });
Object.defineProperty(exports, "parseTimezoneOffsetMatch", { enumerable: true, get: function () { return Timezones_1.parseTimezoneOffsetMatch; } });
Object.defineProperty(exports, "parseTimezoneOffsetToMinutes", { enumerable: true, get: function () { return Timezones_1.parseTimezoneOffsetToMinutes; } });
Object.defineProperty(exports, "TimezoneOffsetRE", { enumerable: true, get: function () { return Timezones_1.TimezoneOffsetRE; } });
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, "ValidTimezoneOffsets", { enumerable: true, get: function () { return Timezones_1.ValidTimezoneOffsets; } });
Object.defineProperty(exports, "validTzOffsetMinutes", { enumerable: true, get: function () { return Timezones_1.validTzOffsetMinutes; } });
Object.defineProperty(exports, "zoneToShortOffset", { enumerable: true, get: function () { return Timezones_1.zoneToShortOffset; } });
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 (#!/usr/bin/perl).
 *
 * ExifTool's vendored Perl script uses this standard shebang path, which works
 * on most Unix-like systems. However, this may fail on systems where Perl is
 * installed elsewhere (e.g., via Homebrew on macOS: /opt/homebrew/bin/perl).
 *
 * When this hardcoded path doesn't exist, the library automatically falls back
 * to using `which perl` to locate the Perl interpreter and ignores the shebang
 * line by explicitly invoking Perl with the script as an argument.
 *
 * @see _ignoreShebang for the fallback logic
 * @see whichPerl for the dynamic Perl detection
 */
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,
        });
        // Backward compatibility: if options.logger is provided, set it as the global logger
        const options_logger = options.logger;
        if (options_logger != null) {
            const providedLogger = (0, Object_1.isFunction)(options_logger)
                ? options_logger
                : () => options_logger;
            Settings_1.Settings.logger.value = providedLogger;
        }
        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,
        };
        // Allow callers to provide a custom processFactory (e.g., for Flatpak
        // sandboxes that need flatpak-spawn --host). If not provided, use the
        // default factory that spawns perl directly.
        const processFactory = options.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 BatchCluster.
     */
    on = (event, listener) => this.batchCluster.on(event, listener);
    /**
     * Unregister life cycle event listeners. Delegates to BatchCluster.
     */
    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.
     * @deprecated use {@link ExifTool.readRaw(file: string, options?: ReadRawTaskOptions)}
     */
    readRaw(file, argsOrOptions, options) {
        const opts = {
            ...(0, Pick_1.pick)(this.options, ...ReadRawTask_1.ReadRawTaskOptionFields),
            ...((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(() => ReadRawTask_1.ReadRawTask.for(file, opts));
    }
    /**
     * 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.
     *
     * **IMPORTANT:** Partial dates (year-only or year-month) are only supported
     * for XMP tags. Use group-prefixed tag names like `"XMP:CreateDate"` for
     * partial date support. EXIF tags require complete dates.
     *
     * @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 cannot 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 a thunk to support retries by providing new instances on retries
     * @param retriable whether to retry the task on failure (default: true)
     * @returns a Promise resolving to the task result
     *
     * @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);
    }
    /**
     * Implements the Disposable interface for automatic cleanup with the `using` keyword.
     * This allows ExifTool instances to be automatically cleaned up when they go out of scope.
     *
     * Note: This is a synchronous disposal method that initiates graceful cleanup but doesn't
     * wait for completion. If graceful cleanup times out, forceful cleanup is attempted.
     * For guaranteed cleanup completion, use `await using` with the async disposal method instead.
     *
     * @example
     * ```typescript
     * {
     *   using et = new ExifTool();
     *   const tags = await et.read("photo.jpg");
     *   // ExifTool cleanup will be initiated when this block exits
     * }
     * ```
     */
    [Symbol.dispose]() {
        if (!this.ended) {
            // Start with graceful cleanup, but use timeout for safety since this is sync disposal
            const cleanup = this.end(true);
            // Set up a timeout to force process exit if graceful cleanup hangs
            // This is necessary because synchronous disposal can't wait for async operations
            const timeoutMs = this.options.disposalTimeoutMs ?? 1000;
            const timeoutHandle = setTimeout(() => {
                const logger = this.options.logger();
                logger.error(`ExifTool synchronous disposal timeout after ${timeoutMs}ms, forcing cleanup`);
                // Force immediate termination of child processes if they exist
                try {
                    this.batchCluster.closeChildProcesses(false);
                }
                catch (err) {
                    logger.error("Error during forced child process cleanup during sync disposal:", err);
                }
            }, timeoutMs);
            cleanup
                .catch((err) => {
                // Log error but don't throw, as disposal should not fail
                const logger = this.options.logger();
                logger.error("ExifTool synchronous disposal error:", err);
            })
                .finally(() => {
                if (timeoutHandle != null)
                    clearTimeout(timeoutHandle);
            });
        }
    }
    /**
     * Implements the AsyncDisposable interface for automatic async cleanup with the `await using` keyword.
     * This allows ExifTool instances to be automatically cleaned up when they go out of scope.
     *
     * This method provides robust cleanup with timeout protection to prevent hanging.
     * If graceful cleanup times out, forceful cleanup is attempted automatically.
     *
     * @example
     * ```typescript
     * {
     *   await using et = new ExifTool();
     *   const tags = await et.read("photo.jpg");
     *   // ExifTool will be automatically ended when this block exits
     * }
     * ```
     */
    async [Symbol.asyncDispose]() {
        if (!this.ended) {
            // Set up a timeout to prevent hanging indefinitely during async disposal
            const timeoutMs = this.options.asyncDisposalTimeoutMs ?? 5000;
            let timeoutHandle = undefined;
            const timeoutPromise = new Promise((_, reject) => {
                timeoutHandle = setTimeout(() => {
                    reject(new Error(`ExifTool async disposal timeout after ${timeoutMs}ms`));
                }, timeoutMs);
            });
            try {
                // Race between graceful cleanup and timeout
                await Promise.race([this.end(true), timeoutPromise]);
            }
            catch (err) {
                const logger = this.options.logger();
                if (err instanceof Error && err.message.includes("timeout")) {
                    logger.error(`ExifTool async disposal timed out after ${timeoutMs}ms, attempting forceful cleanup`);
                    // Attempt forceful cleanup on timeout
                    try {
                        await this.end(false);
                    }
                    catch (forcefulErr) {
                        logger.error("ExifTool forceful cleanup during async disposal also failed:", forcefulErr);
                        throw err; // Re-throw the original timeout error
                    }
                }
                else {
                    logger.error("ExifTool async disposal error:", err);
                    throw err;
                }
            }
            finally {
                if (timeoutHandle != null)
                    clearTimeout(timeoutHandle);
            }
        }
    }
}
exports.ExifTool = ExifTool;
/**
 * This is a convenience singleton.
 *
 * As of v3.0, its {@link ExifToolOptions.maxProcs} is set to 1/4 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