exiftool-vendored
Version:
Efficient, cross-platform access to ExifTool
227 lines • 9.68 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;
};
})();
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
;