UNPKG

exiftool-vendored

Version:
227 lines 9.68 kB
"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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.WriteTask = exports.DefaultWriteTaskOptions = exports.WriteTaskOptionFields = void 0; exports.htmlEncode = htmlEncode; const he_1 = require("he"); const _path = __importStar(require("node:path")); const Array_1 = require("./Array"); const DateTime_1 = require("./DateTime"); const DefaultExifToolOptions_1 = require("./DefaultExifToolOptions"); const ErrorsAndWarnings_1 = require("./ErrorsAndWarnings"); const ExifToolTask_1 = require("./ExifToolTask"); const FilenameCharsetArgs_1 = require("./FilenameCharsetArgs"); const Number_1 = require("./Number"); const Object_1 = require("./Object"); const Pick_1 = require("./Pick"); const String_1 = require("./String"); const Struct_1 = require("./Struct"); const sep = String.fromCharCode(31); // < unit separator // this is private because it's very special-purpose for just encoding ExifTool // WriteTask args: function htmlEncode(s) { return ( // allowUnsafeSymbols is true because ExifTool doesn't care about &, <, >, ", ', * and ` (0, he_1.encode)(s, { decimal: true, allowUnsafeSymbols: true }) // `he` doesn't encode whitespaces (like newlines), but we need that: .replace(/\s/g, (m) => (m === " " ? " " : `&#${m.charCodeAt(0)};`))); } function enc(o, structValue = false) { if (o == null) { return ""; } else if ((0, Number_1.isNumber)(o)) { return String(o); } else if (typeof o === "boolean") { return o ? "True" : "False"; } else if ((0, String_1.isString)(o)) { // Structs need their own escaping here. // See https://exiftool.org/struct.html#Serialize return htmlEncode(structValue ? o.replace(/[,[\]{}|]/g, (ea) => "|" + ea) : o); } else if ((0, DateTime_1.isDateOrTime)(o)) { return (0, DateTime_1.toExifString)(o); } else if (Array.isArray(o)) { const primitiveArray = o.every((ea) => (0, String_1.isString)(ea) || (0, Number_1.isNumber)(ea)); return primitiveArray ? `${o.map((ea) => enc(ea)).join(sep)}` : `[${o.map((ea) => enc(ea)).join(",")}]`; } else if ((0, Struct_1.isStruct)(o)) { // See https://exiftool.org/struct.html#Serialize return `{${(0, Object_1.keys)(o) .map((k) => enc(k, true) + "=" + enc(o[k], true)) .join(",")}}`; } else { throw new Error("cannot encode " + JSON.stringify(o)); } } exports.WriteTaskOptionFields = [ "useMWG", "struct", "ignoreMinorErrors", "writeArgs", ]; exports.DefaultWriteTaskOptions = { ...(0, Pick_1.pick)(DefaultExifToolOptions_1.DefaultExifToolOptions, ...exports.WriteTaskOptionFields), }; class WriteTask extends ExifToolTask_1.ExifToolTask { sourceFile; args; options; constructor(sourceFile, args, options) { super(args, options); this.sourceFile = sourceFile; this.args = args; this.options = options; } static for(filename, tags, options) { const sourceFile = _path.resolve(filename); const args = [ // ensure exiftool thinks this is a write command: ...FilenameCharsetArgs_1.Utf8FilenameCharsetArgs, `-sep`, `${sep}`, "-E", // < html encoding https://exiftool.org/faq.html#Q10 ]; // "undef" doesn't work: but undef works the same as 2 args.push("-api", "struct=" + ((0, Number_1.isNumber)(options?.struct) ? options.struct : "2")); if (options?.useMWG ?? exports.DefaultWriteTaskOptions.useMWG) { args.push("-use", "MWG"); } // Special handling for GPSLatitude and GPSLongitude (due to differences // in EXIF, XMP, and MIE encodings). See // https://exiftool.org/forum/index.php?topic=14488.0 and // https://github.com/photostructure/exiftool-vendored.js/issues/131 // See https://exiftool.org/TagNames/GPS.html if ((0, Number_1.isNumber)(tags.GPSLatitude)) { // ExifTool will also accept a number when writing GPSLatitudeRef, // positive for north latitudes or negative for south, or a string // containing N, North, S or South) tags.GPSLatitudeRef ??= tags.GPSLatitude; } else if (tags.GPSLatitude === null) { tags.GPSLatitudeRef ??= null; } if ((0, Number_1.isNumber)(tags.GPSLongitude)) { // ExifTool will also accept a number when writing this tag, positive // for east longitudes or negative for west, or a string containing E, // East, W or West tags.GPSLongitudeRef ??= tags.GPSLongitude; } else if (tags.GPSLongitude === null) { tags.GPSLongitudeRef ??= null; } if ((0, Number_1.isNumber)(tags.GPSLatitude) && (0, Number_1.isNumber)(tags.GPSLongitude)) { // Also set GPSPosition if both GPSLatitude and GPSLongitude are set to // give ExifTool a hint that we're happy with it figuring out the correct // group for this metadata tags.GPSPosition = tags.GPSLatitude + "," + tags.GPSLongitude; } if ((0, Number_1.isNumber)(tags.GPSAltitude)) { // ExifTool will also accept number when writing this tag, with negative // numbers indicating below sea level tags.GPSAltitudeRef ??= tags.GPSAltitude; } else if (tags.GPSAltitude === null) { tags.GPSAltitudeRef ??= null; } const fieldsToSet = []; for (const key of (0, Object_1.keys)(tags)) { const val = tags[key]; fieldsToSet.push(`-${key}=${enc(val)}`); } if (fieldsToSet.length === 0) { // This is a hack to prevent ExifTool from thinking it should be in "read" // mode, and not "write" mode. It _should_ be a no-op, but there are some // cases that this can cause ExifTool to behave oddly, so we only apply it // if we have no other fields to set. fieldsToSet.push("-FileName<FileName"); } args.push(...fieldsToSet, ...(0, Array_1.toArray)(options.writeArgs), sourceFile); return new WriteTask(sourceFile, args, options); } toString() { return "WriteTask(" + this.sourceFile + ")"; } // we're handling the stderr output ourselves, so we tell ExifToolTask that // all stderr output is not ignorable so we can capture the warnings parse(data, error) { if (error != null) throw error; let created = 0; let updated = 0; let unchanged = 0; for (const line of (0, String_1.splitLines)(data)) { const m_created = CreatedRE.exec(line)?.groups?.count; if (m_created != null) { created += (0, Number_1.toInt)(m_created) ?? 0; continue; } // careful! we need to apply UnchangedRE before UpdateRE, as both match // "updated" const m_unchanged = UnchangedRE.exec(line)?.groups?.count; if (m_unchanged != null) { unchanged += (0, Number_1.toInt)(m_unchanged) ?? 0; continue; } const m_updated = UpdatedRE.exec(line)?.groups?.count; if (m_updated != null) { updated += (0, Number_1.toInt)(m_updated) ?? 0; continue; } // if we get here, we didn't match any of the expected patterns. this.warnings.push("Unexpected output from ExifTool: " + JSON.stringify(line)); } const w = (0, ErrorsAndWarnings_1.errorsAndWarnings)(this).warnings ?? []; return { created, updated, unchanged, ...(w.length === 0 ? {} : { warnings: w }), }; } } exports.WriteTask = WriteTask; // "0 files created" | "0 file created" | " 1 image files created" const CreatedRE = /(?<count>\d{1,5}) [\w ]{1,12}\bcreated$/i; const UnchangedRE = /(?<count>\d{1,5}) [\w ]{1,12}\b(?:weren't updated|unchanged)$/i; // "1 image files updated" const UpdatedRE = /(?<count>\d{1,5}) [\w ]{1,12}\bupdated$/i; //# sourceMappingURL=WriteTask.js.map