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 0.2.0 to 0.3.0

lib/deferred.d.ts

12

lib/datetime.d.ts
export declare function compact<T>(array: T[]): T[];
export declare function pad2(...numbers: number[]): string[];
export declare function pad3(...numbers: number[]): string[];
export declare abstract class Base {
protected pad2(...numbers: number[]): string[];
protected tz(tzoffsetMinutes: number | undefined): string;
}
export declare class ExifTime extends Base {
export declare class ExifTime {
readonly hour: number;

@@ -16,10 +17,10 @@ readonly minute: number;

}
export declare class ExifDate extends Base {
export declare class ExifDate {
readonly year: number;
readonly month: number;
readonly day: number;
readonly tzoffsetMinutes: number;
static regex: RegExp;
constructor(year: number, month: number, day: number, tzoffsetMinutes?: number);
constructor(year: number, month: number, day: number);
toString(): string;
toDate(): Date;
}

@@ -38,3 +39,2 @@ export declare class ExifDateTime extends Base {

toDate(): Date;
utcToLocalOffsetMinutes(datetime: ExifDateTime): number;
toISOString(): string;

@@ -41,0 +41,0 @@ }

@@ -6,6 +6,22 @@ "use strict";

exports.compact = compact;
function pad2(...numbers) {
return numbers.map(i => {
const s = i.toString(10);
return (s.length >= 2) ? s : '0' + s;
});
}
exports.pad2 = pad2;
function pad3(...numbers) {
return numbers.map(i => {
if (i < 0) {
const s = Math.abs(i).toString(10);
return '-' + ((s.length >= 2) ? s : '0' + s);
}
else {
return `000${i}`.slice(Math.min(-3, -(Math.ceil(Math.log10(i)))));
}
});
}
exports.pad3 = pad3;
class Base {
pad2(...numbers) {
return numbers.map(i => `${i >= 10 ? '' : '0'}${i}`);
}
tz(tzoffsetMinutes) {

@@ -19,7 +35,7 @@ if (tzoffsetMinutes === undefined) {

else {
const sign = (tzoffsetMinutes > 0) ? '+' : '-';
const sign = (tzoffsetMinutes >= 0) ? '+' : '-';
const tzoff = Math.abs(tzoffsetMinutes);
const hours = Math.floor(tzoff / 60);
const mins = tzoff - (hours * 60);
return `${sign}${this.pad2(hours, mins).join(':')}`;
return `${sign}${pad2(hours)}:${pad2(mins)}`;
}

@@ -29,5 +45,4 @@ }

exports.Base = Base;
class ExifTime extends Base {
class ExifTime {
constructor(hour, minute, second, secondFraction, tzoffsetMinutes) {
super();
this.hour = hour;

@@ -40,18 +55,19 @@ this.minute = minute;

toString() {
return this.pad2(this.hour, this.minute, this.second).join(':');
return pad2(this.hour, this.minute, this.second).join(':');
}
}
ExifTime.regex = /^(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/;
ExifTime.regex = /^(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,6}))?$/;
exports.ExifTime = ExifTime;
class ExifDate extends Base {
constructor(year, month, day, tzoffsetMinutes) {
super();
class ExifDate {
constructor(year, month, day) {
this.year = year;
this.month = month;
this.day = day;
this.tzoffsetMinutes = tzoffsetMinutes;
}
toString() {
return `${this.year}-${this.pad2(this.month, this.day).join('-')}`;
return `${this.year}-${pad2(this.month)}-${pad2(this.day)}`;
}
toDate() {
return new Date(this.year, this.month - 1, this.day);
}
}

@@ -79,2 +95,5 @@ ExifDate.regex = /^(\d{4}):(\d{2}):(\d{2})$/;

}
else if (this.tzoffsetMinutes === 0) {
return new Date(Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, this.millis));
}
else {

@@ -84,11 +103,8 @@ return new Date(this.toISOString());

}
utcToLocalOffsetMinutes(datetime) {
return (this.toDate().getTime() - datetime.toDate().getTime()) / (1000 * 60);
}
toISOString() {
const [mo, da, ho, mi, se] = this.pad2(this.month, this.day, this.hour, this.minute, this.second);
return `${this.year}-${mo}-${da}T${ho}:${mi}:${se}${this.tz(this.tzoffsetMinutes)}`;
const [mo, da, ho, mi, se] = pad2(this.month, this.day, this.hour, this.minute, this.second);
return `${this.year}-${mo}-${da}T${ho}:${mi}:${se}.${pad3(this.millis)}${this.tz(this.tzoffsetMinutes)}`;
}
}
ExifDateTime.regex = /^(\d{4})[ :]+(\d{2})[ :]+(\d{2})[ :]+(\d{2})[ :]+(\d{2})[ :]+(\d{2})(?:\.(\d{1,3}))?$/;
ExifDateTime.regex = /^(\d{4})[ :]+(\d{2})[ :]+(\d{2})[ :]+(\d{2})[ :]+(\d{2})[ :]+(\d{2})(?:\.(\d{1,6}))?$/;
exports.ExifDateTime = ExifDateTime;

@@ -149,3 +165,3 @@ function _new(re, ctor) {

const newDate = _new(ExifDate.regex, (a) => {
return new ExifDate(a[0], a[1], a[2], a[3]);
return new ExifDate(a[0], a[1], a[2]);
});

@@ -152,0 +168,0 @@ const newTime = _new(ExifTime.regex, (a) => {

@@ -17,3 +17,3 @@ "use strict";

it('.toISOString', () => {
chai_1.expect(dt.toISOString()).to.eql('2016-08-12T07:28:50');
chai_1.expect(dt.toISOString()).to.eql('2016-08-12T07:28:50.900');
});

@@ -36,3 +36,3 @@ it('Renders a Date assuming the current timezone offset', () => {

it('.toISOString', () => {
chai_1.expect(dt.toISOString()).to.eql('2011-01-23T18:19:20Z');
chai_1.expect(dt.toISOString()).to.eql('2011-01-23T18:19:20.000Z');
});

@@ -60,3 +60,3 @@ it('Renders a Date assuming the current timezone offset', () => {

it('.toISOString', () => {
chai_1.expect(dt.toISOString()).to.eql('2013-12-30T11:04:15-05:00');
chai_1.expect(dt.toISOString()).to.eql('2013-12-30T11:04:15.000-05:00');
});

@@ -108,2 +108,35 @@ it('Renders a Date assuming the forced timezone offset', () => {

});
describe('pad2', () => {
it('renders positive values', () => {
chai_1.expect(_dt.pad2(0)).to.eql(['00']);
chai_1.expect(_dt.pad2(1)).to.eql(['01']);
chai_1.expect(_dt.pad2(9)).to.eql(['09']);
chai_1.expect(_dt.pad2(10)).to.eql(['10']);
chai_1.expect(_dt.pad2(11)).to.eql(['11']);
chai_1.expect(_dt.pad2(99)).to.eql(['99']);
chai_1.expect(_dt.pad2(100)).to.eql(['100']);
});
it('renders negative values', () => {
chai_1.expect(_dt.pad2(-1)).to.eql(['-1']);
chai_1.expect(_dt.pad2(-10)).to.eql(['-10']);
});
});
describe('pad3', () => {
it('renders positive values', () => {
chai_1.expect(_dt.pad3(0)).to.eql(['000']);
chai_1.expect(_dt.pad3(1)).to.eql(['001']);
chai_1.expect(_dt.pad3(9)).to.eql(['009']);
chai_1.expect(_dt.pad3(10)).to.eql(['010']);
chai_1.expect(_dt.pad3(11)).to.eql(['011']);
chai_1.expect(_dt.pad3(99)).to.eql(['099']);
chai_1.expect(_dt.pad3(100)).to.eql(['100']);
});
it('renders negative values', () => {
chai_1.expect(_dt.pad3(-1)).to.eql(['-01']);
chai_1.expect(_dt.pad3(-9)).to.eql(['-09']);
chai_1.expect(_dt.pad3(-10)).to.eql(['-10']);
chai_1.expect(_dt.pad3(-99)).to.eql(['-99']);
chai_1.expect(_dt.pad3(-100)).to.eql(['-100']);
});
});
//# sourceMappingURL=datetime.spec.js.map

@@ -10,2 +10,3 @@ import { Task } from './task';

private _ended;
private _closedDeferred;
private readonly proc;

@@ -17,2 +18,4 @@ private buff;

readonly ended: boolean;
readonly closed: boolean;
readonly closedPromise: Promise<void>;
readonly idle: boolean;

@@ -19,0 +22,0 @@ workIfIdle(): void;

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

const process = require('process');
const deferred_1 = require('./deferred');
const exiftool_1 = require('./exiftool');

@@ -21,2 +22,3 @@ const isWin32 = process.platform === 'win32';

this._ended = false;
this._closedDeferred = new deferred_1.Deferred();
this.buff = '';

@@ -28,4 +30,4 @@ this.proc = cp.spawn(exiftoolPath, ['-stay_open', 'True', '-@', '-']);

this.proc.on('close', (code) => {
exiftool_1.logger.log(`ExifTool exited with code ${code}`);
this._ended = true;
this._closedDeferred.resolve();
});

@@ -36,3 +38,2 @@ process.on('beforeExit', () => this.end());

end() {
exiftool_1.logger.info('end()');
if (!this._ended) {

@@ -47,2 +48,8 @@ this._ended = true;

}
get closed() {
return this._closedDeferred.fulfilled;
}
get closedPromise() {
return this._closedDeferred.promise;
}
get idle() {

@@ -49,0 +56,0 @@ return this.currentTask === undefined;

@@ -7,5 +7,2 @@ "use strict";

chai.use(chaiAsPromised);
function delay(millis, value) {
return new Promise((resolve) => setTimeout(resolve(value), 100));
}
describe('ExifToolProcess', () => {

@@ -16,5 +13,5 @@ it('ends properly', () => {

chai_1.expect(etp.ended).to.eql(true);
return chai_1.expect(delay(300).then(() => etp.ended)).to.become(true);
return chai_1.expect(etp.closedPromise.then(() => etp.closed)).to.become(true);
});
});
//# sourceMappingURL=exiftool_process.spec.js.map

@@ -9,2 +9,3 @@ /// <reference types="node" />

read(file: string): Promise<Tags>;
end(): void;
}

@@ -19,11 +20,14 @@ export interface Logger {

export declare class ExifTool implements ExifToolAPI {
private _proc;
readonly maxProcs: number;
private _procs;
private _tasks;
constructor(maxProcs?: number);
version(): Promise<string>;
read(file: string): Promise<Tags>;
end(): Promise<void>;
enqueueTask<T>(task: Task<T>): Task<T>;
dequeueTask(): Task<any> | undefined;
end(): void;
private proc();
private dequeueTask();
private procs();
private workIfIdle();
}
export declare const exiftool: ExifToolAPI;

@@ -11,5 +11,7 @@ "use strict";

exports.logger = console;
exports.ExifToolVendoredVersion = '0.2.0';
exports.ExifToolVendoredVersion = '0.3.0';
class ExifTool {
constructor() {
constructor(maxProcs = 1) {
this.maxProcs = maxProcs;
this._procs = [];
this._tasks = [];

@@ -23,5 +25,9 @@ }

}
end() {
this._procs.forEach(p => p.end());
return Promise.all(this._procs.map(p => p.closedPromise));
}
enqueueTask(task) {
this._tasks.push(task);
this.proc().workIfIdle();
this.workIfIdle();
return task;

@@ -32,12 +38,12 @@ }

}
end() {
this._proc.end();
procs() {
return this._procs = this._procs.filter(p => !p.ended);
}
proc() {
if (this._proc === undefined || this._proc.ended) {
this._proc = new exiftool_process_1.ExifToolProcess(() => this.dequeueTask());
return this.proc();
workIfIdle() {
const idle = this._procs.find(p => !p.ended && p.idle);
if (idle) {
idle.workIfIdle();
}
else {
return this._proc;
else if (this.procs().length < this.maxProcs) {
this._procs.push(new exiftool_process_1.ExifToolProcess(() => this.dequeueTask()));
}

@@ -44,0 +50,0 @@ }

@@ -9,14 +9,54 @@ "use strict";

describe('ExifTool', () => {
after(() => exiftool_1.exiftool.end());
const exiftool = new exiftool_1.ExifTool(2);
const truncated = _path.join(__dirname, '..', 'test', 'truncated.jpg');
const noexif = _path.join(__dirname, '..', 'test', 'noexif.jpg');
const img = _path.join(__dirname, '..', 'test', 'img.jpg');
function runningProcs() {
return exiftool['procs']().length;
}
it('returns the correct version', () => {
return chai_1.expect(exiftool_1.exiftool.version()).to.become('10.31');
return chai_1.expect(exiftool.version()).to.become('10.31');
});
it('returns error for missing file', () => {
return chai_1.expect(exiftool_1.exiftool.read('bogus')).to.eventually.be.rejectedWith(/File not found/);
return chai_1.expect(exiftool.read('bogus')).to.eventually.be.rejectedWith(/File not found/);
});
it('returns expected results for a given file', () => {
const img = _path.join(__dirname, '..', 'test', 'img.jpg');
return chai_1.expect(exiftool_1.exiftool.read(img).then(tags => tags.Model)).to.eventually.eql('iPhone 7 Plus');
return chai_1.expect(exiftool.read(img).then(tags => tags.Model)).to.eventually.eql('iPhone 7 Plus');
});
it('returns warning for a truncated file', () => {
return chai_1.expect(exiftool.read(truncated).then(tags => tags.Warning)).to.eventually.eql('JPEG format error');
});
function normalize(tagNames) {
return tagNames.filter(i => i !== "FileInodeChangeDate" && i !== 'FileCreateDate').sort();
}
it('returns no exif metadata for an image with no headers', () => {
return chai_1.expect(exiftool.read(noexif).then(tags => normalize(Object.keys(tags)))).to.become(normalize([
'BitsPerSample',
'ColorComponents',
'Directory',
'EncodingProcess',
'ExifToolVersion',
'FileAccessDate',
'FileModifyDate',
'FileName',
'FilePermissions',
'FileSize',
'FileType',
'FileTypeExtension',
'ImageHeight',
'ImageSize',
'ImageWidth',
'Megapixels',
'MIMEType',
'SourceFile',
'YCbCrSubSampling',
'errors'
]));
});
it('ends with multiple procs', () => {
const promises = [exiftool.read(img), exiftool.read(img)];
chai_1.expect(runningProcs()).to.eql(2);
return chai_1.expect(Promise.all(promises).then(() => exiftool.end()).then(() => runningProcs())).to.become(0);
});
});
//# sourceMappingURL=exiftool.spec.js.map

@@ -10,3 +10,3 @@ import { Tags } from './tags';

static for(filename: string, optionalArgs?: string[]): TagsTask;
protected parse(data: string): Tags;
parse(data: string): Tags;
private addError(msg);

@@ -13,0 +13,0 @@ private extractTzoffset();

@@ -40,6 +40,8 @@ "use strict";

else {
const gps = _dt.parse('GPSDateTime', this.rawTags.GPSDateTime, undefined);
const local = _dt.parse('DateTimeOriginal', this.rawTags.DateTimeOriginal, undefined);
const gps = _dt.parse('GPSDateTime', this.rawTags.GPSDateTime, 0);
const local = _dt.parse('DateTimeOriginal', this.rawTags.DateTimeOriginal, 0);
if (gps && local) {
this.tzoffset = gps.utcToLocalOffsetMinutes(local);
const gpsToHalfHour = gps.toDate().getTime() / (30 * 60 * 1000);
const localToHalfHour = local.toDate().getTime() / (30 * 60 * 1000);
this.tzoffset = 30 * Math.round(localToHalfHour - gpsToHalfHour);
}

@@ -77,3 +79,4 @@ }

else {
const sorw = ref.trim().toLowerCase().startsWith('w') || ref.startsWith('s');
const direction = ref.trim().toLowerCase();
const sorw = direction.startsWith('w') || direction.startsWith('s');
return parseFloat(value) * (sorw ? -1 : 1);

@@ -80,0 +83,0 @@ }

@@ -199,3 +199,3 @@ import { ExifDate, ExifTime, ExifDateTime, ExifTimeZoneOffset } from './datetime';

AEBAutoCancel: string;
AEBBracketValue: string;
AEBBracketValue: number;
AEBSequence: string;

@@ -257,3 +257,3 @@ AEBSequenceAutoCancel: string;

AFPointsInFocus1D: string;
AFPointsSelected: string;
AFPointsSelected: number;
AFPointsUsed: string;

@@ -855,3 +855,3 @@ AFPredictor: number;

PictureStyleUserDef: string;
Pitch: string;
Pitch: number;
PitchAngle: number;

@@ -858,0 +858,0 @@ PlaybackMonitorOffTime: string;

@@ -1,10 +0,7 @@

export declare abstract class Task<T> {
import { Deferred } from './deferred';
export declare abstract class Task<T> extends Deferred<T> {
readonly args: string[];
readonly promise: Promise<T>;
private _resolve;
private _reject;
constructor(args: string[]);
reject(reason?: any): void;
onData(data: string): void;
protected abstract parse(data: string): T;
}
"use strict";
class Task {
const deferred_1 = require('./deferred');
class Task extends deferred_1.Deferred {
constructor(args) {
super();
this.args = args;
this.promise = new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
});
}
reject(reason) { this._reject(reason); }
onData(data) {
try {
this._resolve(this.parse(data));
this.resolve(this.parse(data));
}
catch (e) {
this._reject(e);
this.reject(e);
}

@@ -18,0 +15,0 @@ }

@@ -132,3 +132,3 @@ "use strict";

const saneTagRe = /^[a-z0-9_]+:[a-z0-9_]+$/i;
const exiftool = new exiftool_1.ExifTool();
const exiftool = new exiftool_1.ExifTool(4);
const start = Date.now();

@@ -135,0 +135,0 @@ Promise.all(files.map(file => {

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

@@ -5,0 +5,0 @@ "main": "./lib/exiftool.js",

# exiftool-vendored
Efficient, cross-platform [node](https://nodejs.org/) access to [ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/).
**Blazing-fast, cross-platform [node](https://nodejs.org/) access to [ExifTool](http://www.sno.phy.queensu.ca/~phil/exiftool/).**

@@ -9,23 +9,25 @@ [![npm version](https://badge.fury.io/js/exiftool-vendored.svg)](https://badge.fury.io/js/exiftool-vendored)

## Unique Features
## Features
1. Uses `-stay_open` mode by default, which can be up to 60x faster than other packages[*](#stay_open)
1. **High performance** via [`-stay_open`](#stay_open) and [multithreading](#parallelism), with [7-300x faster](#performance) than competing packages
1. Parsing of
- dates (even though EXIF doesn't include [timezone offset data](#dates))
- latitudes & longitudes into proper floats (where negative values indicate W or S of the meridian)
1. Proper extraction of
- **dates** with [correct timezone offset encoding, when available](#dates))
- **latitudes & longitudes** as floats (where negative values indicate W or S of the meridian)
1. Robust [type definitions](#tags) of the top 99.5% of tags used by over 3,000 different camera makes and models
1. Robust **[type definitions](#tags)** of the top 99.5% of tags used by over 3,000 different camera makes and models
1. Auditable ExifTool source code (the "vendored" code is [verifiable](http://owl.phy.queensu.ca/~phil/exiftool/checksums.txt))
1. **Auditable ExifTool source code** (the "vendored" code is [verifiable](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))
1. **Automated updates** to ExifTool ([as new versions come out monthly](http://www.sno.phy.queensu.ca/~phil/exiftool/history.html))
1. Tested on node v6+ on Linux, Mac, & Windows.
1. **Robust test suite**, performed with node v6+ on [Linux, Mac,](https://travis-ci.org/mceachen/exiftool-vendored) & [Windows](https://ci.appveyor.com/project/mceachen/exiftool-vendored/branch/master).
## Installation
npm install --save exiftool-vendored
```sh
npm install --save exiftool-vendored
```
The vendored version of ExifTool relevant for your platform will be installed via [platform-dependent-modules](https://www.npmjs.com/package/platform-dependent-modules).
The vendored version of ExifTool relevant for your platform will be installed via [platform-dependent-modules](https://www.npmjs.com/package/platform-dependent-modules). You shouldn't include either the `exiftool-vendored.exe` or `exiftool-vendored.pl` as direct dependencies to your project.

@@ -35,6 +37,4 @@ ## Usage

```js
// `exiftool` is a singleton instance of the `ExifTool` class
import { exiftool } from "exiftool-vendored"
// ExifTool.read() returns a Promise<Tags>
exiftool.read("path/to/file.jpg").then(tags => {
import { exiftool, Tags } from "exiftool-vendored"
exiftool.read("path/to/file.jpg").then((tags /*: Tags */) => {
console.log(`Make: ${metadata.Make}, Model: ${metadata.Model}`)

@@ -44,14 +44,24 @@ })

Official [EXIF](http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf) tag names are [PascalCased](https://en.wikipedia.org/wiki/PascalCase), like `AFPointSelected` and `ISO`. ("Fixing" the field names to be camelCase, would result in ungainly `aFPointSelected` and `iSO` atrocities).
## Performance
## Dates
With the `npm run mktags` target, > 3000 sample images, and `maxProcs` set to 4, reading tags on my laptop takes ~6 ms per image:
Generally, EXIF tags encode dates and times with **no timezone offset.** Presumably the time is captured in local time, but this means parsing the same file in different parts of the world results in a different *absolute* timestamp for the same file.
```sh
Read 2236 unique tags from 3011 files.
Parsing took 16191ms (5.4ms / file) # win32, core i7, maxProcs 4
Parsing took 27141ms (9.0ms / file) # ubuntu, core i3, maxProcs 1
Parsing took 12545ms (4.2ms / file) # ubuntu, core i3, maxProcs 4
```
Rather than returning a [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) which always includes a timezone, this library returns classes that encode the date, the time of day, or both, with an optional tzoffset. It's up to you, then, to do what's right.
For reference, using the `exiftool` npm package (which doesn't work on Windows) took 85 seconds (almost 7x the time):
In many cases, though, **a tzoffset can be determined**, either by the composite TimeZone tag, or by looking at the GPS UTC timestamp (which is present in most smartphone images). If a tzoffset can be determined, it is encoded in all related `ExifDateTime` tags for those files.
```sh
Reading 3011 files...
Parsing took 85654ms (28.4ms / file) # ubuntu, core i3
```
## stay_open
This package is so much faster due to `ExifTool` child process reuse, as well as delegation to > 1 child processes.
### stay_open
Starting the perl version of ExifTool is expensive, and is *especially* expensive on the Windows version of ExifTool.

@@ -63,6 +73,29 @@

### Parallelism
The `exiftool` singleton is configured with a `maxProcs` of 1;
no more than 1 child process of ExifTool will be spawned, even if there are many read requests outstanding.
If you want higher throughput, instantiate your own singleton reference of `ExifTool` with a higher maxProcs. Note that exceeding your cpu count won't increase throughput, and that each child process consumes between 10 and 50 MB of RAM.
You may want to call `.end()` on your singleton reference when your script terminates. This gracefully shuts down all child processes.
## Dates
Generally, EXIF tags encode dates and times with **no timezone offset.** Presumably the time is captured in local time, but this means parsing the same file in different parts of the world results in a different *absolute* timestamp for the same file.
Rather than returning a [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) which always includes a timezone, this library returns classes that encode the date, the time of day, or both, with an optional tzoffset. It's up to you, then, to do what's right.
In many cases, though, **a tzoffset can be determined**, either by the composite `TimeZone` tag, or by looking at the difference between the local `DateTimeOriginal` and `GPSDateTime` tags. `GPSDateTime` is present in most smartphone images.
If a tzoffset can be determined, it is encoded in all related `ExifDateTime` tags for those files.
## Tags
The `tags.ts` file is autogenerated by parsing through images of more than 3,000 different camera makes and models taken from the ExifTool site. It groups tags, their type, frequency, and example values such that your IDE can autocomplete
Official [EXIF](http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf) tag names are [PascalCased](https://en.wikipedia.org/wiki/PascalCase), like `AFPointSelected` and `ISO`. ("Fixing" the field names to be camelCase, would result in ungainly `aFPointSelected` and `iSO` atrocities).
The `tags.ts` file is autogenerated by parsing through images of more than 3,000 different camera makes and models taken from the ExifTool site. It groups tags, their type, frequency, and example values such that your IDE can autocomplete.
Note that tag existence and types is **not guaranteed**. If parsing fails (for, example, and datetime string), the raw string will be returned. Consuming code should verify both existence and type as reasonable for safety.
## Versioning

@@ -83,2 +116,11 @@

v1.0.0 with a stable API will be released in winter of 2016.
### v0.3.0
* Multithreading support with the `maxProcs` ctor param
* Added tests for reading images with truncated or missing EXIF headers
* Added tests for timezone offset extraction and rendering
* Subsecond resolution from the Google Pixel has 8 significant digits(!!), added support for that.
### v0.2.0

@@ -85,0 +127,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 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