New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

exiftool-vendored

Package Overview
Dependencies
Maintainers
1
Versions
252
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

exiftool-vendored - npm Package Compare versions

Comparing version 15.12.1 to 16.0.0

63

CHANGELOG.md

@@ -7,3 +7,3 @@ # Changelog/Versioning

features or bugfixes arise and using ExifTool's version number is at odds with
eachother, so this library follows [Semver](https://semver.org/), and the
each other, so this library follows [Semver](https://semver.org/), and the
vendored versions of ExifTool match the version they vendor.

@@ -29,2 +29,63 @@

### v16.0.0
- 💔/🐞 Timezone extraction has been adjusted: if there is a GPS location, we'll
prefer that `tzlookup` as the authoritative timezone offset. If there isn't
GPS lat/lon, we'll use `Timezone`, `OffsetTime`, or `TimeZoneOffset`. If those
are missing, we'll infer the offset from UTC offsets.
Prior builds would defer to the offset in `Timezone`, `OffsetTime`, or
`TimeZoneOffset`, but GPS is more reliable, and results in a proper time zone
(like `America/Los_Angeles`). Zone names work correctly even when times are
adjusted across daylight savings offset boundaries.
- 💔/🐞 Timezone application is now has been improved: if a timezone can be
extracted for a given file, `readTags` will now make all `ExifDateTime`
entries match that timezone. The timestamps should refer to the same
timestamp/seconds-from-common-epoch, but "local time" may be different as
we've adjusted the timezone accordingly.
Metadata sometimes includes a timezone offset, and sometimes it doesn't, and
it's all pretty inconsistent, but worse, prior versions would sometimes
inherit the current system timezone for an arbitrary subset of tags. This
version should remove the system timezone "leaking" into your metadata values.
As an example, if you took a photo with GPS information from Rome (CET,
UTC+1), and your computer is in California with `TZ=America/Los_Angeles`,
prior versions could return `CreateDate: 2022-02-02 02:02:22-07:00`. This
version will translate that time into `CreateDate: 2022-02-02 11:02:22+01:00`.
Note that this fix results in `readTags` rendering different `ExifDateTime`
values from prior versions, so I bumped the major version to highlight this
change.
- 💔 `Tags` is automatically generated by `mktags`, which now has a set of
"required" tags with type and group metadata to ensure a core set of tags
don't disappear or change types.
As a reminder, the `Tags` interface is only a subset of fields returned, due
to TypeScript limitations. `readTags` still returns all values that ExifTool
provides.
- 🐞 Fixed a bunch of broken API links in the README due to `typedoc` changing
URLs. Harumph.
- 🐞 Prior versions of `ExifDateTime.parseISO` would accept just time or date
strings.
- 🐞/📦 `TimeStamp` tags may now be properly parsed as `ExifDateTime`.
- 📦 Added performance section to the README.
- 📦 Timezone offset formatting changed slightly: the hour offset is no longer
zero-padded, which better matches the Luxon implementation we use internally.
- 📦 `ExifDateTime` caches the result of `toDateTime` now, which may save a
couple extra objects fed to the GC.
- 📦 Updated dependencies, including batch-cluster
[v10.3.2](https://github.com/photostructure/batch-cluster.js/blob/main/CHANGELOG.md#v1032)),
which fixed several race conditions and added several process performance
improvements including support for zero-wait multi-process launches.
### v15.12.1

@@ -31,0 +92,0 @@

7

dist/ExifDateTime.d.ts

@@ -1,2 +0,2 @@

import { DateTime, ToISOTimeOptions } from "luxon";
import { DateTime, ToISOTimeOptions, Zone, ZoneOptions } from "luxon";
import { Maybe } from "./Maybe";

@@ -7,2 +7,3 @@ /**

export declare class ExifDateTime {
#private;
readonly year: number;

@@ -18,3 +19,3 @@ readonly month: number;

readonly zoneName?: string | undefined;
static fromISO(iso: string, zone?: Maybe<string>, rawValue?: string): Maybe<ExifDateTime>;
static fromISO(iso: string, zone?: Maybe<string>): Maybe<ExifDateTime>;
/**

@@ -38,3 +39,5 @@ * Try to parse a date-time string from EXIF. If there is not both a date and

get zone(): Maybe<string>;
setZone(zone?: string | Zone, opts?: ZoneOptions): ExifDateTime;
toDateTime(): DateTime;
toEpochSeconds(): number;
toDate(): Date;

@@ -41,0 +44,0 @@ toISOString(options?: ToISOTimeOptions): Maybe<string>;

"use strict";
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var _ExifDateTime_dt;
Object.defineProperty(exports, "__esModule", { value: true });

@@ -24,10 +36,20 @@ exports.ExifDateTime = void 0;

this.zoneName = zoneName;
_ExifDateTime_dt.set(this, void 0);
}
static fromISO(iso, zone, rawValue) {
static fromISO(iso, zone) {
if ((0, String_1.blank)(iso) || null != iso.match(/^\d+$/))
return undefined;
return this.fromDateTime(luxon_1.DateTime.fromISO(iso, {
setZone: true,
zone: zone !== null && zone !== void 0 ? zone : Timezones_1.UnsetZone,
}), rawValue !== null && rawValue !== void 0 ? rawValue : iso);
// Unfortunately, DateTime.fromISO() is happy to parse a date with no time,
// so we have to do this ourselves:
return this.fromPatterns(iso, [
// if it specifies a zone, use it:
{ fmt: "y-M-d'T'H:m:s.uZZ" },
{ fmt: "y-M-d'T'H:m:sZZ" },
// if it specifies UTC, use it:
{ fmt: "y-M-d'T'H:m:s.u'Z'", zone: "utc" },
{ fmt: "y-M-d'T'H:m:s'Z'", zone: "utc" },
// Otherwise use the default zone:
{ fmt: "y-M-d'T'H:m:s.u", zone },
{ fmt: "y-M-d'T'H:m:s", zone },
]);
}

@@ -71,5 +93,6 @@ /**

static fromExifStrict(text, zone) {
var _a;
if ((0, String_1.blank)(text))
return undefined;
return this.fromPatterns(text, [
return ((_a = this.fromPatterns(text, [
// if it specifies a zone, use it:

@@ -85,12 +108,3 @@ { fmt: "y:M:d H:m:s.uZZ" },

// Not found yet? Maybe it's in ISO format? See https://github.com/photostructure/exiftool-vendored.js/issues/71
// if it specifies a zone, use it:
{ fmt: "y-M-d'T'H:m:s.uZZ" },
{ fmt: "y-M-d'T'H:m:sZZ" },
// if it specifies UTC, use it:
{ fmt: "y-M-d'T'H:m:s.u'Z'", zone: "utc" },
{ fmt: "y-M-d'T'H:m:s'Z'", zone: "utc" },
// Otherwise use the default zone:
{ fmt: "y-M-d'T'H:m:s.u", zone },
{ fmt: "y-M-d'T'H:m:s", zone },
]);
])) !== null && _a !== void 0 ? _a : this.fromISO(text, zone));
}

@@ -133,4 +147,17 @@ static fromExifLoose(text, defaultZone) {

}
setZone(zone, opts) {
// This is a bit tricky... We want to keep the local time and just _say_ it was in the zone of the image **if we don't already have a zone.**
// If we _do_ have a zone, assume it was already converted by ExifTool into (probably the system) timezone, which means _don't_ keepLocalTime.
const result = ExifDateTime.fromDateTime(this.toDateTime().setZone(zone, {
keepLocalTime: !this.hasZone,
...opts,
}), this.rawValue);
// We know this will be defined: this is valid, so changing the zone will
// also be valid.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return result;
}
toDateTime() {
return luxon_1.DateTime.fromObject({
var _a;
return (__classPrivateFieldSet(this, _ExifDateTime_dt, (_a = __classPrivateFieldGet(this, _ExifDateTime_dt, "f")) !== null && _a !== void 0 ? _a : luxon_1.DateTime.fromObject({
year: this.year,

@@ -145,4 +172,7 @@ month: this.month,

zone: this.zone,
});
}), "f"));
}
toEpochSeconds() {
return this.toDateTime().toUnixInteger();
}
toDate() {

@@ -187,2 +217,3 @@ return this.toDateTime().toJSDate();

exports.ExifDateTime = ExifDateTime;
_ExifDateTime_dt = new WeakMap();
//# sourceMappingURL=ExifDateTime.js.map
/// <reference types="node" />
import * as bc from "batch-cluster";
import { ApplicationRecordTags } from "./ApplicationRecordTags";
import { ExifDate } from "./ExifDate";
import { ExifDateTime } from "./ExifDateTime";
import { ExifToolTask } from "./ExifToolTask";
import { ICCProfileTags } from "./ICCProfileTags";
import { Maybe } from "./Maybe";
import { PreviewTag } from "./PreviewTag";
import { Struct } from "./Struct";
import { Tags } from "./Tags";
import { APP12Tags, APP14Tags, APP1Tags, APP4Tags, APP5Tags, APP6Tags, CompositeTags, EXIFTags, ExifToolTags, FileTags, FlashPixTags, IPTCTags, JFIFTags, MakerNotesTags, MetaTags, MPFTags, PanasonicRawTags, PhotoshopTags, PrintIMTags, QuickTimeTags, RAFTags, RIFFTags, Tags, XMPTags } from "./Tags";
export { ExifDate } from "./ExifDate";

@@ -16,3 +18,3 @@ export { ExifDateTime } from "./ExifDateTime";

export { offsetMinutesToZoneName, UnsetZone, UnsetZoneName, UnsetZoneOffsetMinutes, } from "./Timezones";
export type { AdditionalWriteTags, ExpandedDateTags, Maybe, Omit, Struct, Tags };
export type { AdditionalWriteTags, APP12Tags, APP14Tags, APP1Tags, APP4Tags, APP5Tags, APP6Tags, ApplicationRecordTags, CompositeTags, EXIFTags, ExifToolTags, ExpandedDateTags, FileTags, FlashPixTags, ICCProfileTags, IPTCTags, JFIFTags, MakerNotesTags, Maybe, MetaTags, MPFTags, Omit, PanasonicRawTags, PhotoshopTags, PrintIMTags, QuickTimeTags, RAFTags, RIFFTags, Struct, Tags, XMPTags, };
export declare const DefaultExifToolPath: string;

@@ -34,6 +36,2 @@ export declare const DefaultExiftoolArgs: string[];

"Orientation#"?: number;
/**
* Included because it's so rare, it doesn't always make the Tags build:
*/
TimeZoneOffset?: number | string;
};

@@ -43,5 +41,5 @@ declare type ExpandedDateTags = {

};
declare type NotUndefined<T> = T extends undefined ? never : T;
export declare type Defined<T> = T extends undefined ? never : T;
export declare type DefinedOrNullValued<T> = {
[P in keyof T]: NotUndefined<T[P]> | null;
[P in keyof T]: Defined<T[P]> | null;
};

@@ -328,2 +326,3 @@ export declare type WriteTags = DefinedOrNullValued<ShortcutTags & AdditionalWriteTags & ExpandedDateTags>;

childEndCounts(): {
startError: number;
broken: number;

@@ -330,0 +329,0 @@ closed: number;

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[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) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[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) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

@@ -0,2 +1,4 @@

import { Maybe } from "./Maybe";
export declare function keys<T extends object, K extends string & keyof T>(o: T): K[];
export declare function isFunction(obj: any): obj is () => any;
export declare function fromEntries(arr: Maybe<[Maybe<string>, any]>[], obj?: any): any;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isFunction = exports.keys = void 0;
exports.fromEntries = exports.isFunction = exports.keys = void 0;
// eslint-disable-next-line @typescript-eslint/ban-types

@@ -15,2 +15,20 @@ function keys(o) {

exports.isFunction = isFunction;
function fromEntries(arr, obj) {
if (arr == null || arr.length === 0)
return obj;
// don't use Object.create(null), json stringify will break!
for (const ea of arr.filter((ea) => ea != null)) {
if (ea != null && Array.isArray(ea)) {
const [k, v] = ea;
// allow NULL fields:
if (k != null && v !== undefined) {
if (typeof obj !== "object")
obj = {};
obj[k] = v;
}
}
}
return obj;
}
exports.fromEntries = fromEntries;
//# sourceMappingURL=Object.js.map
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[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) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

@@ -27,3 +27,4 @@ import { ExifToolTask } from "./ExifToolTask";

private extractTzOffset;
private normalizeDateTime;
private parseTag;
}
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[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) {

@@ -117,5 +121,8 @@ if (k2 === undefined) k2 = k;

this.extractTzOffset();
Object.keys(this._raw).forEach((key) => (this.tags[key] = this.parseTag(key, this._raw[key])));
(0, Maybe_1.map)(this.tz, (ea) => (this.tags.tz = ea));
(0, Maybe_1.map)(this.tzSource, (ea) => (this.tags.tzSource = ea));
const t = this.tags; // tsc hack :(
for (const key of Object.keys(this._raw)) {
t[key] = this.parseTag(key, this._raw[key]);
}
if (this.errors.length > 0)

@@ -159,4 +166,5 @@ this.tags.errors = this.errors;

extractTzOffset() {
// tzlookup will be the "best" tz, as it will be a proper Zone name (like
// "America/New_York"), rather than just an hour offset.
(0, Maybe_1.map)((0, Maybe_1.firstDefinedThunk)([
() => (0, Timezones_1.extractTzOffsetFromTags)(this._tags),
() => {

@@ -176,5 +184,16 @@ if (!this.invalidLatLon && this.lat != null && this.lon != null) {

},
() => (0, Timezones_1.extractTzOffsetFromTags)(this._tags),
() => (0, Timezones_1.extractTzOffsetFromUTCOffset)(this._tags),
]), (ea) => ({ tz: this.tz, src: this.tzSource } = ea));
}
normalizeDateTime(tagName, value) {
if (tagName.startsWith("File") ||
!(value instanceof ExifDateTime_1.ExifDateTime) ||
tagIsInUTC(tagName) ||
this.tz == null) {
return value;
}
// ExifTool may have put this in the current system time, instead of the timezone of the file.
return value.zone !== this.tz ? value.setZone(this.tz) : value;
}
parseTag(tagNameWithGroup, value) {

@@ -195,20 +214,23 @@ var _a, _b, _c, _d, _e, _f;

}
const tz = tagName.includes("UTC") || tagName.startsWith("GPS") ? "UTC" : this.tz;
if (typeof value === "string" && tagName.includes("DateTime")) {
const d = (_a = ExifDateTime_1.ExifDateTime.fromExifStrict(value, tz)) !== null && _a !== void 0 ? _a : ExifDateTime_1.ExifDateTime.fromISO(value, tz);
if (d != null) {
return d;
if (typeof value === "string") {
const tz = tagIsInUTC(tagName) ? "UTC" : undefined;
if (tagName.includes("DateTime") ||
tagName.toLowerCase().includes("timestamp")) {
const d = (_a = ExifDateTime_1.ExifDateTime.fromExifStrict(value, tz)) !== null && _a !== void 0 ? _a : ExifDateTime_1.ExifDateTime.fromISO(value, tz);
if (d != null) {
return this.normalizeDateTime(tagName, d);
}
}
}
if (typeof value === "string" && tagName.includes("Date")) {
const d = (_f = (_e = (_d = (_c = (_b = ExifDateTime_1.ExifDateTime.fromExifStrict(value, tz)) !== null && _b !== void 0 ? _b : ExifDateTime_1.ExifDateTime.fromISO(value, tz)) !== null && _c !== void 0 ? _c : ExifDateTime_1.ExifDateTime.fromExifLoose(value, tz)) !== null && _d !== void 0 ? _d : ExifDate_1.ExifDate.fromExifStrict(value)) !== null && _e !== void 0 ? _e : ExifDate_1.ExifDate.fromISO(value)) !== null && _f !== void 0 ? _f : ExifDate_1.ExifDate.fromExifLoose(value);
if (d != null) {
return d;
if (tagName.includes("Date")) {
const d = (_f = (_e = (_d = (_c = (_b = ExifDateTime_1.ExifDateTime.fromExifStrict(value, tz)) !== null && _b !== void 0 ? _b : ExifDateTime_1.ExifDateTime.fromISO(value, tz)) !== null && _c !== void 0 ? _c : ExifDateTime_1.ExifDateTime.fromExifLoose(value, tz)) !== null && _d !== void 0 ? _d : ExifDate_1.ExifDate.fromExifStrict(value)) !== null && _e !== void 0 ? _e : ExifDate_1.ExifDate.fromISO(value)) !== null && _f !== void 0 ? _f : ExifDate_1.ExifDate.fromExifLoose(value);
if (d != null) {
return this.normalizeDateTime(tagName, d);
}
}
if (tagName.includes("Time")) {
const t = ExifTime_1.ExifTime.fromEXIF(value);
if (t != null)
return t;
}
}
if (typeof value === "string" && tagName.includes("Time")) {
const t = ExifTime_1.ExifTime.fromEXIF(value);
if (t != null)
return t;
}
// Trust that ExifTool rendered the value with the correct type in JSON:

@@ -224,2 +246,5 @@ return value;

exports.ReadTask = ReadTask;
function tagIsInUTC(tagName) {
return tagName.includes("UTC") || tagName.startsWith("GPS");
}
//# sourceMappingURL=ReadTask.js.map
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[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) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

@@ -34,3 +34,3 @@ import { FixedOffsetZone } from "luxon";

}): Maybe<TzSrc>;
export declare function inferLikelyOffsetMinutes(deltaMs: number): number;
export declare function inferLikelyOffsetMinutes(deltaMinutes: number): number;
export declare function extractTzOffsetFromUTCOffset(t: {

@@ -41,2 +41,3 @@ DateTimeUTC?: string;

GPSTimeStamp?: string;
GPSDateTimeStamp?: string;
SubSecDateTimeOriginal?: string;

@@ -43,0 +44,0 @@ DateTimeOriginal?: string;

@@ -6,3 +6,2 @@ "use strict";

const Array_1 = require("./Array");
const DateTime_1 = require("./DateTime");
const ExifDateTime_1 = require("./ExifDateTime");

@@ -40,12 +39,6 @@ const Maybe_1 = require("./Maybe");

const minutes = Math.abs(absMinutes % 60);
return (`UTC${sign}` +
(minutes === 0 ? `${(0, String_1.pad2)(hours)}` : `${(0, String_1.pad2)(hours)}:${(0, String_1.pad2)(minutes)}`));
// luxon now renders simple hour offsets without padding:
return `UTC${sign}` + hours + (minutes === 0 ? "" : `:${(0, String_1.pad2)(minutes)}`);
}
exports.offsetMinutesToZoneName = offsetMinutesToZoneName;
function dtToMs(s, defaultZone) {
return (0, Maybe_1.map)(ExifDateTime_1.ExifDateTime.fromExifStrict(s, defaultZone), (dt) => dt.toDate().getTime());
}
function utcToMs(s) {
return dtToMs(s, "UTC");
}
function tzHourToOffset(n) {

@@ -94,5 +87,2 @@ return (0, Number_1.isNumber)(n) && reasonableTzOffsetMinutes(n * 60)

exports.extractTzOffsetFromTags = extractTzOffsetFromTags;
function firstUtcMs(tags, tagNames) {
return (0, Maybe_1.first)(tagNames, (tagName) => (0, Maybe_1.map)(utcToMs(tags[tagName]), (utcMs) => ({ tagName, utcMs })));
}
// timezone offsets may be on a 15 minute boundary, but if GPS acquisition is

@@ -103,15 +93,27 @@ // old, this can be spurious. We get less mistakes with a larger multiple, so

const TzBoundaryMinutes = 30;
function inferLikelyOffsetMinutes(deltaMs) {
return TzBoundaryMinutes * Math.floor(deltaMs / DateTime_1.MinuteMs / TzBoundaryMinutes);
function inferLikelyOffsetMinutes(deltaMinutes) {
return TzBoundaryMinutes * Math.floor(deltaMinutes / TzBoundaryMinutes);
}
exports.inferLikelyOffsetMinutes = inferLikelyOffsetMinutes;
function extractTzOffsetFromUTCOffset(t) {
var _a;
const gpsStamps = (0, Array_1.compact)([t.GPSDateStamp, t.GPSTimeStamp]);
const GPSDateTimeStamp = gpsStamps.length === 2 ? gpsStamps.join(" ") : undefined;
const utc = firstUtcMs({ ...t, GPSDateTimeStamp }, [
"GPSDateTime",
"DateTimeUTC",
"GPSDateTimeStamp",
]);
const dt = firstUtcMs(t, [
if (gpsStamps.length === 2) {
(_a = t.GPSDateTimeStamp) !== null && _a !== void 0 ? _a : (t.GPSDateTimeStamp = gpsStamps.join(" "));
}
// We can always assume these are in UTC:
const utc = (0, Maybe_1.first)(["GPSDateTime", "DateTimeUTC", "GPSDateTimeStamp"], (tagName) => {
const edt = ExifDateTime_1.ExifDateTime.fromExifStrict(t[tagName]);
return edt != null && (edt.zone == null || edt.zone === "UTC")
? {
tagName,
s: edt.setZone("UTC", { keepLocalTime: true }).toEpochSeconds(),
}
: undefined;
});
if (utc == null)
return;
// If we can find any of these without a zone, the timezone should be the
// offset between this time and the GPS time.
const dt = (0, Maybe_1.first)([
"SubSecDateTimeOriginal",

@@ -124,7 +126,16 @@ "DateTimeOriginal",

"DateTimeCreated",
]);
if (utc == null || dt == null)
], (tagName) => {
const edt = ExifDateTime_1.ExifDateTime.fromExifStrict(t[tagName]);
return edt != null && edt.zone == null
? {
tagName,
s: edt.setZone("UTC", { keepLocalTime: true }).toEpochSeconds(),
}
: undefined;
});
if (dt == null)
return;
// By flooring
const offsetMinutes = inferLikelyOffsetMinutes(dt.utcMs - utc.utcMs);
const diffSeconds = dt.s - utc.s;
const offsetMinutes = inferLikelyOffsetMinutes(diffSeconds / 60);
return (0, Maybe_1.map)(offsetMinutesToZoneName(offsetMinutes), (tz) => ({

@@ -131,0 +142,0 @@ tz,

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[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) {

@@ -6,0 +10,0 @@ if (k2 === undefined) k2 = k;

{
"name": "exiftool-vendored",
"version": "15.12.1",
"version": "16.0.0",
"description": "Efficient, cross-platform access to ExifTool",

@@ -95,8 +95,8 @@ "main": "./dist/ExifTool.js",

"@types/xmldom": "^0.1.31",
"@typescript-eslint/eslint-plugin": "^5.12.1",
"@typescript-eslint/parser": "^5.12.1",
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.14.0",
"chai": "^4.3.6",
"chai-as-promised": "^7.1.1",
"chai-subset": "^1.6.0",
"eslint": "^8.9.0",
"eslint": "^8.10.0",
"eslint-plugin-import": "^2.25.4",

@@ -116,4 +116,4 @@ "eslint-plugin-node": "^11.1.0",

"tmp": "^0.2.1",
"typedoc": "^0.22.12",
"typescript": "^4.5.5",
"typedoc": "^0.22.13",
"typescript": "~4.6.2",
"@xmldom/xmldom": "^0.8.1",

@@ -123,4 +123,4 @@ "xpath": "^0.0.32"

"dependencies": {
"@types/luxon": "^2.0.9",
"batch-cluster": "^10.3.0",
"@types/luxon": "^2.3.0",
"batch-cluster": "^10.3.2",
"he": "^1.2.0",

@@ -127,0 +127,0 @@ "luxon": "^2.3.1",

@@ -24,15 +24,12 @@ # exiftool-vendored

- [reading tags](https://photostructure.github.io/exiftool-vendored.js/classes/exiftool.html#read)
- extracting embedded binaries, like [thumbnail](https://photostructure.github.io/exiftool-vendored.js/classes/exiftool.html#extractthumbnail) and [preview](https://photostructure.github.io/exiftool-vendored.js/classes/exiftool.html#extractpreview) images
- [writing tags](https://photostructure.github.io/exiftool-vendored.js/classes/exiftool.html#write)
- [rescuing metadata](https://photostructure.github.io/exiftool-vendored.js/classes/exiftool.html#rewritealltags)
- [reading tags](https://photostructure.github.io/exiftool-vendored.js/classes/ExifTool.html#read)
- extracting embedded binaries, like [thumbnail](https://photostructure.github.io/exiftool-vendored.js/classes/ExifTool.html#extractThumbnail) and [preview](https://photostructure.github.io/exiftool-vendored.js/classes/ExifTool.html#extractPreview) images
- [writing tags](https://photostructure.github.io/exiftool-vendored.js/classes/ExifTool.html#write)
- [rescuing metadata](https://photostructure.github.io/exiftool-vendored.js/classes/ExifTool.html#rewriteAllTags)
1. **[Robust type definitions](#tags)** of the top 99.5% tags used by over 6,000
different camera makes and models (see an [example](interfaces/exiftags.html))
different camera makes and models (see an [example](https://photostructure.github.io/exiftool-vendored.js/interfaces/EXIFTags.html))
1. **Auditable ExifTool source code** (the vendored code is
[checksum verified](http://owl.phy.queensu.ca/~phil/exiftool/checksums.txt))
1. **Automated updates** to ExifTool ([as new versions come out
monthly](http://www.sno.phy.queensu.ca/~phil/exiftool/history.html))
monthly](https://exiftool.org/history.html))

@@ -63,3 +60,3 @@ 1. **Robust test coverage**, performed with on [macOS, Linux, and

See the
[CHANGELOG](https://github.com/mceachen/exiftool-vendored.js/blob/main/CHANGELOG.md)
[CHANGELOG](https://github.com/photostructure/exiftool-vendored.js/blob/main/CHANGELOG.md)
for breaking changes since you last updated.

@@ -101,3 +98,3 @@

If the default [ExifTool constructor
parameters](https://photostructure.github.io/exiftool-vendored.js/interfaces/exiftooloptions.html)
parameters](https://photostructure.github.io/exiftool-vendored.js/interfaces/ExifToolOptions.html)
wont' work for you, it's just a class that takes an options hash:

@@ -116,3 +113,3 @@

`ExifTool.read()` returns a Promise to a [Tags](https://photostructure.github.io/exiftool-vendored.js/interfaces/tags.html) instance. Note
`ExifTool.read()` returns a Promise to a [Tags](https://photostructure.github.io/exiftool-vendored.js/interfaces/Tags.html) instance. Note
that errors may be returned either by rejecting the promise, or for less

@@ -130,5 +127,5 @@ severe problems, via the `errors` field.

Instead, we build a corpus of "commonly seen" tags from over 5,000 different
Instead, we build a corpus of "commonly seen" tags from over 10,000 different
digital camera makes and models, many from the [ExifTool metadata
repository](https://exiftool.org/sample_images.html).
repository](https://exiftool.org/sample_images.html) and <raw.pixls.us>.

@@ -164,3 +161,3 @@ Here are some example fields:

unknown fields, in other words. It's up to you and your code to look for other
fields you expect, and cast to a more relevant interface.
fields you expect and cast to a more relevant interface.

@@ -179,3 +176,3 @@ ### Errors and Warnings

[`rejectTaskOnStderr`](interfaces/exiftooloptions.html#rejecttaskonstderr).
Either of these parameters are provided to the `ExifTool` constructor.
Either of these parameters is provided to the `ExifTool` constructor.

@@ -185,10 +182,5 @@ ### Logging and events

To enable trace, debug, info, warning, or error logging from this library and
the underlying `batch-cluster` library,
use[`setLogger`](globals.html#setlogger). Example
code can be found
[here](https://github.com/photostructure/batch-cluster.js/blob/main/src/_chai.spec.ts#L20).
the underlying `batch-cluster` library, provide a [Logger](https://photostructure.github.io/batch-cluster.js/interfaces/Logger.html) instance to the `ExifTool` constructor options.
ExifTool instances emits events for "startError", "taskError", "endError",
"beforeEnd", and "end" that you can register listeners for, using
[on](https://batch-cluster.js.org/classes/batchcluster.html#on).
ExifTool instances emits [many lifecycle and error events](https://photostructure.github.io/batch-cluster.js/interfaces/BatchClusterEvents.html#beforeEnd) via `batch-cluster`.

@@ -240,4 +232,4 @@ ### Reading tags

Note that only a portion of tags are writable. Refer to [the
documentation](https://sno.phy.queensu.ca/~phil/exiftool/TagNames/index.html)
Note that only a portion of tags is writable. Refer to [the
documentation](https://exiftool.org/TagNames/index.html)
and look under the "Writable" column.

@@ -248,3 +240,3 @@

Only string and numeric primitive are supported as values to the object.
Only string and numeric primitives are supported as values to the object.

@@ -259,3 +251,3 @@ To write a comment to the given file so it shows up in the Windows Explorer

To change the DateTimeOriginal, CreateDate and ModifyDate tags (using the
[AllDates](https://sno.phy.queensu.ca/~phil/exiftool/TagNames/Shortcuts.html)
[AllDates](https://exiftool.org/TagNames/Shortcuts.html)
shortcut) to 4:56pm UTC on February 6, 2016:

@@ -282,3 +274,3 @@

The above example removes any value associated to the `UserComment` tag.
The above example removes any value associated with the `UserComment` tag.

@@ -297,7 +289,7 @@ ### Always Beware: Timezones

You may find that some of your images have corrupt metadata, and that writing
You may find that some of your images have corrupt metadata and that writing
new dates, or editing the rotation information, for example, fails. ExifTool can
try to repair these images by rewriting all the metadata into a new file, along
with the original image content. See the
[documentation](http://owl.phy.queensu.ca/~phil/exiftool/faq.html#Q20) for more
[documentation](https://exiftool.org/faq.html#Q20) for more
details about this functionality.

@@ -317,6 +309,6 @@

1. Place your [user configuration
file](http://owl.phy.queensu.ca/~phil/exiftool/config.html) in your `HOME`
file](https://exiftool.org/config.html) in your `HOME`
directory
1. Set the `EXIFTOOL_HOME` environment variable to the fully-qualified path that
contains your user config.
contains your user configuration.
1. Specify the in the ExifTool constructor options:

@@ -333,3 +325,3 @@

If you run this in a docker image based off Alpine or Debian Slim, **this won't work properly unless you install the `procps` package.**
If you run this in a docker image based on Alpine or Debian Slim, **this won't work properly unless you install the `procps` package.**

@@ -347,3 +339,3 @@ [See `batch-cluster` for details.](https://github.com/photostructure/batch-cluster.js/issues/13)

You must explicitly call `.end()` on any used instance of `ExifTool` for `node`
to exit gracefully.
to exit gracefully.

@@ -375,5 +367,5 @@ This call cannot be in a `process.on("exit")` hook, as the `stdio` streams

the world, **this assumption will not be correct**. Parsing the same file in
different parts of the world results in a different times for the same file.
different parts of the world result in different times for the same file.
Prior to version 7, heuristic 1 and 3 was applied.
Prior to version 7, heuristic 1 and 3 were applied.

@@ -386,3 +378,3 @@ As of version 7.0.0, `exiftool-vendored` uses the following heuristics. The

If the [EXIF](https://sno.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html)
If the [EXIF](https://exiftool.org/TagNames/EXIF.html)
`TimeZoneOffset` tag is present it will be applied as per the spec to

@@ -407,3 +399,3 @@ `DateTimeOriginal`, and if there are two values, the `ModifyDate` tag as well.

Because datetimes have this optionally-set timezone, and some tags only specify
Because date-times have this optionally-set timezone, and some tags only specify
the date, this library returns classes that encode the date, the time of day, or

@@ -415,3 +407,3 @@ both, **with an optional timezone and an optional tzoffset**: `ExifDateTime` and

Note also that some smartphones record timestamps with microsecond precision
(not just millis!), and both `ExifDateTime` and `ExifTime` have floating point
(not just milliseconds!), and both `ExifDateTime` and `ExifTime` have floating point
milliseconds.

@@ -433,8 +425,8 @@

Tags marked with "★★★★", like
[MIMEType](https://photostructure.github.io/exiftool-vendored.js/interfaces/tags.html#mimetype),
[MIMEType](https://photostructure.github.io/exiftool-vendored.js/interfaces/FileTags.html#MIMEType),
should be found in most files. Of the several thousand metadata tags, realize
less than 50 are found generally. You'll need to do your own research to
less than 50 are found generally. You'll need to do your research to
determine which tags are valid for your uses.
Note that if parsing fails (for, example, a datetime string), the raw string
Note that if parsing fails (for, example, a date-time string), the raw string
will be returned. Consuming code should verify both existence and type as

@@ -463,25 +455,29 @@ reasonable for safety.

The `npm run mktags` target reads all tags found in a batch of sample images and
parses the results.
The default [exiftool]() singleton is intentionally throttled. If full system
utilization is acceptable:
Using `exiftool-vendored`:
1. set
[`maxProcs`](https://photostructure.github.io/batch-cluster.js/classes/BatchClusterOptions.html#maxProcs)
higher
```sh
Read 2236 unique tags from 3011 files.
Parsing took 16s (5.4ms / file) # windows 10, core i7, maxProcs 4
Parsing took 27s (9.0ms / file) # ubuntu 18.04, core i3, maxProcs 1
Parsing took 13s (4.2ms / file) # ubuntu 18.04, core i3, maxProcs 4
2. consider setting
[`minDelayBetweenSpawnMillis`](https://photostructure.github.io/batch-cluster.js/classes/BatchClusterOptions.html#minDelayBetweenSpawnMillis)
to 0
# September 2020 update with > 2x more files and faster CPU:
Read 3100 unique tags from 8028 files.
Parsing took 16s (2.0ms / file) # ubuntu 20.04, AMD Ryzen 9 3900X, maxProcs 24
```
3. On a performant linux box, a smaller value of `streamFlushMillis` may work as
well: if you see [`noTaskData`
events](https://photostructure.github.io/batch-cluster.js/interfaces/BatchClusterEvents.html#noTaskData),
you need to bump the value up.
Using the `exiftool` npm package takes 7-10x longer, and doesn't work on Windows.
## Benchmarking
```sh
Reading 3011 files...
Parsing took 86s (28.4ms / file) # ubuntu, core i3
```
The `yarn mktags ../path/to/examples` target reads all tags found in a directory
hierarchy of sample images and videos, and parses the results.
`exiftool-vendored` v16.0.0 on a 2019 AMD Ryzen 3900X running Ubuntu 20.04 on an
SSD can process 20+ files per second, per thread, or 500+ files per second
utilizing all CPU threads.
It can read, parse,
### Batch mode

@@ -488,0 +484,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc