Initial commit
This commit is contained in:
commit
e49726988c
5445 files changed
+1089914
No files matched your search
+1928
File diff suppressed because it is too large.
Load diff
+131
@@ -0,0 +1,131 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Overview
|
||||
|
||||
This is exiftool-vendored.js, a Node.js library that provides cross-platform access to ExifTool for reading and writing metadata in photos and videos. The library includes comprehensive TypeScript type definitions for metadata tags and is used by PhotoStructure and 500+ other projects.
|
||||
|
||||
## Key Commands
|
||||
|
||||
### Building & Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Compile TypeScript to JavaScript
|
||||
npm run compile
|
||||
|
||||
# Watch mode for development
|
||||
npm run compile:watch
|
||||
|
||||
# Clean build artifacts
|
||||
npm run clean
|
||||
```
|
||||
|
||||
### Linting & Formatting
|
||||
|
||||
```bash
|
||||
# Run ESLint with TypeScript configuration
|
||||
npm run lint
|
||||
|
||||
# Format all TypeScript and other files with Prettier
|
||||
npm run fmt
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run all tests (requires compilation first)
|
||||
npm test
|
||||
|
||||
# Note: Tests use Mocha and execute the compiled JavaScript files in dist/
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
```bash
|
||||
# Generate TypeDoc documentation
|
||||
npm run docs
|
||||
|
||||
# This runs the typedoc tool and serves the documentation locally
|
||||
```
|
||||
|
||||
### Tag Generation
|
||||
|
||||
```bash
|
||||
# Regenerate src/Tags.ts from sample images
|
||||
npm run mktags ../path/to/images
|
||||
|
||||
# This requires a directory of sample images to analyze and extract metadata tags
|
||||
```
|
||||
|
||||
## Architecture & Key Concepts
|
||||
|
||||
### Core Components
|
||||
|
||||
1. **ExifTool Class** (`src/ExifTool.ts`): Main entry point that wraps ExifTool process management using batch-cluster. Provides methods for reading metadata, writing tags, extracting binaries, and rewriting tags.
|
||||
|
||||
2. **Tags Interface** (`src/Tags.ts`): Auto-generated TypeScript interface containing ~2000 metadata fields commonly found in photo/video files. Generated by analyzing thousands of sample files to avoid TypeScript union type limits. Fields include popularity ratings and example values.
|
||||
|
||||
3. **Date/Time Handling**: Custom classes for handling EXIF dates with timezone complexities:
|
||||
|
||||
- `ExifDate`: Date without time
|
||||
- `ExifTime`: Time without date
|
||||
- `ExifDateTime`: Date and time with optional timezone
|
||||
|
||||
4. **Task System**: Various task classes for different operations:
|
||||
|
||||
- `ReadTask`: Read metadata from files
|
||||
- `WriteTask`: Write metadata to files
|
||||
- `BinaryExtractionTask`: Extract thumbnails/previews
|
||||
- `RewriteAllTagsTask`: Repair corrupt metadata
|
||||
|
||||
5. **Platform Support**: Includes platform-specific ExifTool binaries via optional dependencies:
|
||||
- `exiftool-vendored.exe` for Windows
|
||||
- `exiftool-vendored.pl` for Unix-like systems
|
||||
|
||||
### Timezone Handling
|
||||
|
||||
The library implements sophisticated timezone heuristics to handle underspecified date metadata:
|
||||
|
||||
1. Uses explicit timezone metadata if available
|
||||
2. Falls back to GPS location for timezone inference
|
||||
3. Uses UTC timestamp deltas to infer timezone offsets
|
||||
4. Handles edge cases like daylight saving time transitions
|
||||
|
||||
### Tag Generation Process
|
||||
|
||||
The `mktags.ts` script analyzes sample images to generate TypeScript interfaces:
|
||||
|
||||
1. Scans image directories for various formats (JPEG, RAW, video)
|
||||
2. Extracts metadata using ExifTool
|
||||
3. Counts tag frequencies and value types
|
||||
4. Generates strongly-typed interfaces for the most common ~2000 tags
|
||||
5. Includes popularity ratings (★★★★ = >50% of samples)
|
||||
|
||||
### Testing Approach
|
||||
|
||||
Tests use Mocha with Chai assertions:
|
||||
|
||||
- Unit tests for individual components (dates, parsers, etc.)
|
||||
- Integration tests for ExifTool operations
|
||||
- Test images in the `test/` directory cover various edge cases
|
||||
- Includes non-English filename tests
|
||||
|
||||
## Important Notes
|
||||
|
||||
- Always run `npm run compile` before testing
|
||||
- The singleton `exiftool` instance must be properly ended with `.end()` to clean up child processes
|
||||
- Tag interfaces are not comprehensive - less common tags may exist in returned objects
|
||||
- The library uses batch processing for performance, managing process pools automatically
|
||||
- TypeScript's union type limits require careful tag selection to avoid compilation errors
|
||||
|
||||
## Important TypeScript Hygiene
|
||||
|
||||
- You MUST use `if (x != null)` to test for `null` or `undefined` values.
|
||||
DO NOT use `if (x)`: this is problematic (especially when handling boolean values).
|
||||
|
||||
- You MUST use `??` for nullish coalescing.
|
||||
DO NOT use `||`: this is problematic (especially when handling boolean values).
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016-2025 Matthew McEachen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+518
@@ -0,0 +1,518 @@
|
||||
# exiftool-vendored
|
||||
|
||||
**Fast, cross-platform [Node.js](https://nodejs.org/) access to [ExifTool](https://exiftool.org/). Built and supported by [PhotoStructure](https://photostructure.com).**
|
||||
|
||||
[](https://www.npmjs.com/package/exiftool-vendored)
|
||||
[](https://github.com/photostructure/exiftool-vendored.js/actions/workflows/node.js.yml)
|
||||
[](https://github.com/photostructure/exiftool-vendored.js/issues)
|
||||
[](https://snyk.io/test/github/photostructure/exiftool-vendored.js?targetFile=package.json)
|
||||
|
||||
## Features
|
||||
|
||||
1. **Best-of-class cross-platform performance and reliability**.
|
||||
|
||||
This library enables [PhotoStructure](https://photostructure.com) and [500+](https://github.com/photostructure/exiftool-vendored.js/network/dependents?package_id=UGFja2FnZS0xNjYxNjY2MQ%3D%3D) other projects to read and write metadata in photos and videos.
|
||||
|
||||
Expect [an order of magnitude faster performance](#performance) than other Node.js ExifTool modules.
|
||||
|
||||
Thanks to being based on [ExifTool](https://exiftool.org/), it's the state of the art in high quality metadata extraction for thousands of file types.
|
||||
|
||||
1. Best-effort extraction of
|
||||
|
||||
- **dates** with [correct timezone offset encoding](#dates)
|
||||
- **latitudes & longitudes** as floats (where negative values indicate west or south of the meridian)
|
||||
|
||||
1. Support for
|
||||
|
||||
- [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](https://photostructure.github.io/exiftool-vendored.js/interfaces/EXIFTags.html#CreateDate))
|
||||
|
||||
1. **Automated updates** to ExifTool ([as new versions come out
|
||||
frequently](https://exiftool.org/history.html))
|
||||
|
||||
1. **Robust test coverage**, performed with on [macOS, Linux, and
|
||||
Windows](https://github.com/photostructure/exiftool-vendored.js/actions?query=workflow%3A%22Node.js+CI%22)
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
npm install --save exiftool-vendored
|
||||
```
|
||||
|
||||
### Debug logging
|
||||
|
||||
If anything doesn't work, the first thing to try is enabling the logger.
|
||||
|
||||
You can provide a [Logger implementation](https://photostructure.github.io/batch-cluster.js/interfaces/Logger.html) via [`ExifToolOptions.logger`](https://photostructure.github.io/exiftool-vendored.js/interfaces/ExifToolOptions.html#logger), or set the environment variable `NODE_DEBUG=exiftool-vendored`. [See the debuglog() documentation](https://nodejs.org/docs/latest/api/util.html#utildebuglogsection-callback) for more details.
|
||||
|
||||
### Regarding use within Electron
|
||||
|
||||
Due to how different every Electron application setup is, and how new versions
|
||||
frequently have breaking changes, **do not ask for help by opening a github
|
||||
issue on this project.**
|
||||
|
||||
Please seek help via StackOverflow, the Electron discord, or other channels.
|
||||
|
||||
### Electron-builder support
|
||||
|
||||
Add the following pattern to `electron-builder.yml`'s `asarUnpack`:
|
||||
|
||||
```yaml
|
||||
- "node_modules/exiftool-vendored.*/**/*"
|
||||
```
|
||||
|
||||
The default `exiftoolPath` implementation will detect `app.asar` in your `require`
|
||||
path and replace it with `app.asar.unpacked` automatically.
|
||||
|
||||
### Electron-forge support
|
||||
|
||||
Version 25.0 of this library added experimental support for `electron-forge`:
|
||||
add the following element to your `ForgeConfig.packagerConfig.extraResource`
|
||||
string array, and things should "just work" **for the main process**.
|
||||
|
||||
```ts
|
||||
"./node_modules/exiftool-vendored." +
|
||||
(process.platform === "win32" ? "exe" : "pl");
|
||||
```
|
||||
|
||||
**If your main process forks any node subprocesses, `process.resourcesPath` _will
|
||||
not be set_ in those subprocesses, and the default `exiftoolPath` won't work.**
|
||||
|
||||
If this is your case, you must provide a correct implementation of
|
||||
[ExifToolOptions.exiftoolPath](https://photostructure.github.io/exiftool-vendored.js/interfaces/ExifToolOptions.html#exiftoolPath),
|
||||
either by passing through `resourcesPath` via `process.env`, or some other
|
||||
method.
|
||||
|
||||
### Installation notes
|
||||
|
||||
- `exiftool-vendored` provides an installation of ExifTool relevant for your
|
||||
local platform through
|
||||
[optionalDependencies](https://docs.npmjs.com/files/package.json#optionaldependencies).
|
||||
|
||||
- You shouldn't include either [exiftool-vendored.exe](https://github.com/photostructure/exiftool-vendored.exe) or
|
||||
[exiftool-vendored.pl](https://github.com/photostructure/exiftool-vendored.pl) as direct dependencies to your project, unless you know
|
||||
what you're doing.
|
||||
|
||||
- If you're installing on a minimal Linux distribution, you may need to install `perl`. On Alpine, run `apk add perl`.
|
||||
|
||||
- Node.js's `-slim` docker images don't include a working `perl` build. Use the non-slim image instead. [See the issue report for details.](https://github.com/photostructure/exiftool-vendored.js/issues/168)
|
||||
|
||||
- If the platform-correct vendor module (`exiftool-vendored.exe` or `exiftool-vendored.pl`) is not found, `exiftool` is searched for on your `PATH`. Note that _very_ old versions of `exiftool` are found on currently-supported Linux distributions which this library will not work correctly with.
|
||||
|
||||
## Upgrading
|
||||
|
||||
See the
|
||||
[CHANGELOG](https://github.com/photostructure/exiftool-vendored.js/blob/main/CHANGELOG.md)
|
||||
for breaking changes since you last updated.
|
||||
|
||||
### Major version bumps
|
||||
|
||||
I bump the major version if there's a **chance** existing code might be
|
||||
affected.
|
||||
|
||||
I've been bit too many times by my code breaking when I pull in minor or patch
|
||||
upgrades with other libraries. I think it's better to be pessimistic in code
|
||||
change impact analysis: "over-promise and under-deliver" your breaking-code
|
||||
changes.
|
||||
|
||||
When you upgrade to a new major version, please take a bit more care in
|
||||
validating your own systems, but don't be surprised when everything still works.
|
||||
|
||||
## Usage
|
||||
|
||||
There are many configuration options to ExifTool, but all values have (more or
|
||||
less sensible) defaults.
|
||||
|
||||
Those defaults have been used to create the
|
||||
[`exiftool`](https://photostructure.github.io/exiftool-vendored.js/modules.html#exiftool) singleton.
|
||||
Note that if you _don't_ use the default singleton, you don't need to `.end()`
|
||||
it.
|
||||
|
||||
```js
|
||||
// We're using the singleton here for convenience:
|
||||
const exiftool = require("exiftool-vendored").exiftool;
|
||||
|
||||
// And to verify everything is working:
|
||||
exiftool
|
||||
.version()
|
||||
.then((version) => console.log(`We're running ExifTool v${version}`));
|
||||
```
|
||||
|
||||
If the default [ExifTool constructor
|
||||
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:
|
||||
|
||||
```js
|
||||
const ExifTool = require("exiftool-vendored").ExifTool;
|
||||
const exiftool = new ExifTool({ taskTimeoutMillis: 5000 });
|
||||
```
|
||||
|
||||
You should only use the exported default `exiftool` singleton, or only create one instance of `ExifTool` as a singleton.
|
||||
|
||||
Remember to `.end()` whichever singleton you use.
|
||||
|
||||
### General API
|
||||
|
||||
`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
|
||||
severe problems, via the `errors` field.
|
||||
|
||||
All other public ExifTool methods return `Promise<void>`, and will reject
|
||||
the promise if the operation is not successful.
|
||||
|
||||
### `Tags` types
|
||||
|
||||
ExifTool knows how to extract _several thousand_ different tag fields.
|
||||
|
||||
Unfortunately, TypeScript crashes with `error TS2590: Expression produces a union type that is too complex to represent` if the `Tags` interface was comprehensive.
|
||||
|
||||
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) and <raw.pixls.us>.
|
||||
|
||||
Here are some example fields:
|
||||
|
||||
```ts
|
||||
/** ★☆☆☆ ✔ Example: 200 */
|
||||
ISO?: number
|
||||
|
||||
/** ★★★★ ✔ Example: 1920 */
|
||||
ImageHeight?: number
|
||||
|
||||
/** ★★★★ ✔ Example: 1080 */
|
||||
ImageWidth?: number
|
||||
|
||||
/** ★★★★ ✔ Example: "image/jpeg" */
|
||||
MIMEType?: string
|
||||
```
|
||||
|
||||
The stars represent how common that field has a value in the example corpus. ★★★★ fields are found in > 50% of the examples.
|
||||
☆☆☆☆ fields are found in < 1% of examples.
|
||||
|
||||
The checkmark denotes if the field is found in "popular" cameras (like recent
|
||||
Nikon, Canon, Sony, and Apple devices).
|
||||
|
||||
### Caveats with `Tags`
|
||||
|
||||
**The fields in `Tags` are not comprehensive.**
|
||||
|
||||
Just because a field is missing from the Tags interface **does not mean the
|
||||
field doesn't exist in the returned object**. This library doesn't exclude
|
||||
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.
|
||||
|
||||
### Logging and events
|
||||
|
||||
To enable trace, debug, info, warning, or error logging from this library and
|
||||
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 [many lifecycle and error events](https://photostructure.github.io/batch-cluster.js/interfaces/BatchClusterEvents.html#beforeEnd) via `batch-cluster`.
|
||||
|
||||
### Reading tags
|
||||
|
||||
```js
|
||||
exiftool
|
||||
.read("path/to/image.jpg")
|
||||
.then((tags /*: Tags */) =>
|
||||
console.log(
|
||||
`Make: ${tags.Make}, Model: ${tags.Model}, Errors: ${tags.errors}`,
|
||||
),
|
||||
)
|
||||
.catch((err) => console.error("Something terrible happened: ", err));
|
||||
```
|
||||
|
||||
### Extracting embedded images
|
||||
|
||||
Extract the low-resolution thumbnail in `path/to/image.jpg`, write it to
|
||||
`path/to/thumbnail.jpg`, and return a `Promise<void>` that is fulfilled
|
||||
when the image is extracted:
|
||||
|
||||
```js
|
||||
exiftool.extractThumbnail("path/to/image.jpg", "path/to/thumbnail.jpg");
|
||||
```
|
||||
|
||||
Extract the `Preview` image (only found in some images):
|
||||
|
||||
```js
|
||||
exiftool.extractPreview("path/to/image.jpg", "path/to/preview.jpg");
|
||||
```
|
||||
|
||||
Extract the `JpgFromRaw` image (found in some RAW images):
|
||||
|
||||
```js
|
||||
exiftool.extractJpgFromRaw("path/to/image.cr2", "path/to/fromRaw.jpg");
|
||||
```
|
||||
|
||||
Extract the binary value from "tagname" tag in `path/to/image.jpg`
|
||||
and write it to `dest.bin` (which cannot exist already
|
||||
and whose parent directory must already exist):
|
||||
|
||||
```js
|
||||
exiftool.extractBinaryTag("tagname", "path/to/file.exf", "path/to/dest.bin");
|
||||
```
|
||||
|
||||
### Writing tags
|
||||
|
||||
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.
|
||||
|
||||
If you apply malformed values or ask to write to tags that aren't
|
||||
supported, the returned `Promise` will be rejected.
|
||||
|
||||
Only string and numeric primitives are supported as values to the object.
|
||||
|
||||
To write a comment to the given file so it shows up in the Windows Explorer
|
||||
Properties panel:
|
||||
|
||||
```js
|
||||
exiftool.write("path/to/file.jpg", { XPComment: "this is a test comment" });
|
||||
```
|
||||
|
||||
To change the DateTimeOriginal, CreateDate and ModifyDate tags (using the
|
||||
[AllDates](https://exiftool.org/TagNames/Shortcuts.html)
|
||||
shortcut) to 4:56pm UTC on February 6, 2016:
|
||||
|
||||
```js
|
||||
exiftool.write("path/to/file.jpg", { AllDates: "2016-02-06T16:56:00" });
|
||||
```
|
||||
|
||||
To write to a specific metadata group's tag, just prefix the tag name with the group.
|
||||
(TypeScript users: you'll need to cast to make this compile).
|
||||
|
||||
```js
|
||||
exiftool.write("path/to/file.jpg", {
|
||||
"IPTC:CopyrightNotice": "© 2021 PhotoStructure, Inc.",
|
||||
});
|
||||
```
|
||||
|
||||
To delete a tag, use `null` as the value.
|
||||
|
||||
```js
|
||||
exiftool.write("path/to/file.jpg", { UserComment: null });
|
||||
```
|
||||
|
||||
The above example removes any value associated with the `UserComment` tag.
|
||||
|
||||
### Always Beware: Timezones
|
||||
|
||||
If you edit a timestamp tag, realize that the difference between the
|
||||
changed timestamp tag and the GPS value is used by `exiftool-vendored` to
|
||||
infer the timezone.
|
||||
|
||||
In other words, if you only edit the `CreateDate` and don't edit the `GPS`
|
||||
timestamps, your timezone will either be incorrect or missing. See the
|
||||
section about [Dates](#dates) below for more information.
|
||||
|
||||
### Rewriting tags
|
||||
|
||||
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](https://exiftool.org/faq.html#Q20) for more
|
||||
details about this functionality.
|
||||
|
||||
`rewriteAllTags` returns a void Promise that will be rejected if there are any
|
||||
errors.
|
||||
|
||||
```js
|
||||
exiftool.rewriteAllTags("problematic.jpg", "rewritten.jpg");
|
||||
```
|
||||
|
||||
### ExifTool configuration support (`.ExifTool_config`)
|
||||
|
||||
ExifTool has an [extensive user configuration system](http://owl.phy.queensu.ca/~phil/exiftool/config.html). There are several ways to use one:
|
||||
|
||||
1. Place your [user configuration
|
||||
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 configuration.
|
||||
1. Specify the in the ExifTool constructor options:
|
||||
|
||||
```js
|
||||
new ExifTool({ exiftoolEnv: { EXIFTOOL_HOME: resolve("path", "to", "config", "dir") }
|
||||
```
|
||||
|
||||
## Resource hygiene
|
||||
|
||||
**Call `ExifTool.end()` when you're done**
|
||||
|
||||
You must explicitly call
|
||||
[`.end()`](https://photostructure.github.io/exiftool-vendored.js/classes/ExifTool.html#end)
|
||||
on any used instance of `ExifTool` to allow `node` to exit gracefully.
|
||||
|
||||
ExifTool child processes consume system resources, and [prevents `node` from
|
||||
exiting due to the way Node.js streams
|
||||
work](https://github.com/photostructure/exiftool-vendored.js/issues/106).
|
||||
|
||||
Note that you can't call cannot be in a `process.on("exit")` hook, as the `stdio` streams
|
||||
attached to the child process cannot be `unref`'ed. (If there's a solution to
|
||||
this, please post to the above issue!)
|
||||
|
||||
### Mocha v4.0.0
|
||||
|
||||
If you use [mocha](https://mochajs.org/) v4 or later, and you don't call
|
||||
`exiftool.end()`, you will find that your test suite hangs. [The relevant
|
||||
change is described here](https://github.com/mochajs/mocha/issues/3044),
|
||||
and can be solved by adding an `after` block that shuts down the instance
|
||||
of ExifTool that your tests are using:
|
||||
|
||||
```js
|
||||
after(() => exiftool.end()); // assuming your singleton is called `exiftool`
|
||||
```
|
||||
|
||||
## Dates
|
||||
|
||||
**The date metadata in all your images and videos are, most likely,
|
||||
underspecified.**
|
||||
|
||||
Images and videos rarely specify a time zone in their dates. If all your files
|
||||
were captured in your current time zone, defaulting to the local time zone is a
|
||||
safe assumption, but if you have files that were captured in different parts of
|
||||
the world, **this assumption will not be correct**. Parsing the same file in
|
||||
different parts of the world result in different times for the same file.
|
||||
|
||||
Prior to version 7, heuristic 1 and 3 were applied.
|
||||
|
||||
As of version 7.0.0, `exiftool-vendored` uses the following heuristics. The
|
||||
highest-priority heuristic to return a value will be used as the timezone offset
|
||||
for all datetime tags that don't already have a specified timezone.
|
||||
|
||||
### Heuristic 1: explicit metadata
|
||||
|
||||
If the [EXIF](https://exiftool.org/TagNames/EXIF.html)
|
||||
`TimeZoneOffset` tag is present it will be applied as per the spec to
|
||||
`DateTimeOriginal`, and if there are two values, the `ModifyDate` tag as well.
|
||||
`OffsetTime`, `OffsetTimeOriginal`, and `OffsetTimeDigitized` are also
|
||||
respected, if present (but are very rarely set).
|
||||
|
||||
### Heuristic 2: GPS location
|
||||
|
||||
If GPS latitude and longitude is present and valid (the value of `0, 0` is
|
||||
considered invalid), the `tz-lookup` library will be used to determine the time
|
||||
zone name for that location.
|
||||
|
||||
### Heuristic 3: UTC timestamps
|
||||
|
||||
If `GPSDateTime` or `DateTimeUTC` is present, the delta with the dates found
|
||||
within the file, as long as the delta is valid, is used as the timezone offset.
|
||||
Deltas of > 14 hours are considered invalid.
|
||||
|
||||
### ExifDate and ExifDateTime
|
||||
|
||||
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
|
||||
both, **with an optional timezone and an optional tzoffset**: `ExifDateTime` and
|
||||
`ExifTime`. It's up to you, then, to determine what's correct for your
|
||||
situation.
|
||||
|
||||
Note also that some smartphones record timestamps with microsecond precision
|
||||
(not just milliseconds!), and both `ExifDateTime` and `ExifTime` have floating point
|
||||
milliseconds.
|
||||
|
||||
## Tags
|
||||
|
||||
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](https://photostructure.github.io/exiftool-vendored.js/interfaces/Tags.html) interface is
|
||||
auto-generated by the `mktags` script, which parses through over 6,000 unique
|
||||
camera make and model images, in large part sourced from the ExifTool site.
|
||||
`mktags` groups tags, extracts their type, popularity, and example values such
|
||||
that your IDE can autocomplete.
|
||||
|
||||
Tags marked with "★★★★", like
|
||||
[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 research to
|
||||
determine which tags are valid for your uses.
|
||||
|
||||
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
|
||||
reasonable for safety.
|
||||
|
||||
## Serialization
|
||||
|
||||
The `Tags` object returned by `ExifTool.read()` can be serialized to JSON with `JSON.stringify`.
|
||||
|
||||
To reconstitute, use the `parseJSON()` method.
|
||||
|
||||
```ts
|
||||
import { exiftool, parseJSON } from "exiftool-vendored";
|
||||
|
||||
const tags: Tags = await exiftool.read("/path/to/file.jpg");
|
||||
const str: string = JSON.stringify(tags);
|
||||
|
||||
// parseJSON doesn't validate the input, so we don't assert that it's a Tags
|
||||
// instance, but you can cast it (unsafely...)
|
||||
|
||||
const tags2: Tags = parseJSON(str) as Tags;
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
The default [exiftool]() singleton is intentionally throttled. If full system
|
||||
utilization is acceptable:
|
||||
|
||||
1. set
|
||||
[`maxProcs`](https://photostructure.github.io/batch-cluster.js/classes/BatchClusterOptions.html#maxProcs)
|
||||
higher
|
||||
|
||||
2. consider setting
|
||||
[`minDelayBetweenSpawnMillis`](https://photostructure.github.io/batch-cluster.js/classes/BatchClusterOptions.html#minDelayBetweenSpawnMillis)
|
||||
to 0
|
||||
|
||||
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.
|
||||
|
||||
## Benchmarking
|
||||
|
||||
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 when
|
||||
utilizing all CPU threads.
|
||||
|
||||
### Batch mode
|
||||
|
||||
Using ExifTool's `-stay_open` batch mode means we can reuse a single
|
||||
instance of ExifTool across many requests, dropping response latency
|
||||
dramatically as well as reducing system load.
|
||||
|
||||
### Parallelism
|
||||
|
||||
To avoid overwhelming your system, the `exiftool` singleton is configured with a
|
||||
`maxProcs` set to a quarter the number of CPUs on the current system (minimally
|
||||
1); no more than `maxProcs` instances of `exiftool` will be spawned. If the
|
||||
system is CPU constrained, however, you may want a smaller value. If you have
|
||||
very fast disk IO, you may see a speed increase with larger values of
|
||||
`maxProcs`, but note that each child process can consume 100 MB of RAM.
|
||||
|
||||
## Author
|
||||
|
||||
- [Matthew McEachen](https://github.com/mceachen)
|
||||
|
||||
## Contributors 🎉
|
||||
|
||||
- [Joshua Harris](https://github.com/Circuit8)
|
||||
- [Anton Mokrushin](https://github.com/amokrushin)
|
||||
- [Luca Ban](https://github.com/mesqueeb)
|
||||
- [Demiurga](https://github.com/apolkingg8)
|
||||
- [David Randler](https://github.com/draity)
|
||||
|
||||
## CHANGELOG
|
||||
|
||||
See the
|
||||
[CHANGELOG](https://github.com/mceachen/exiftool-vendored.js/blob/main/CHANGELOG.md) on github.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Releasing new versions of `exiftool-vendored`
|
||||
|
||||
1. `git clone` this repo into `~/src`
|
||||
|
||||
2. On a POSIX machine, clone
|
||||
[exiftool-vendored.pl](https://github.com/photostructure/exiftool-vendored.pl)
|
||||
into `~/src` (or another common subdirectory)
|
||||
|
||||
3. On a Windows machine, clone
|
||||
[exiftool-vendored.exe](https://github.com/photostructure/exiftool-vendored.exe)
|
||||
|
||||
4. On POSIX, in `../exiftool-vendored.pl`:
|
||||
|
||||
1. Run the "check-updates" GitHub Action. If there is a pending update, a PR is generated.
|
||||
1. Verify and merge the PR
|
||||
1. Run the "release" GitHub Action.
|
||||
|
||||
5. On Windows, in `...\exiftool-vendored.exe\`:
|
||||
(The terminal built into vscode plays with `ncu` and `release-it` a bit nicer than CMD or Windows for Git's terminal)
|
||||
|
||||
1. `git stash -u && git fetch && git checkout main && npm install && npm run update && npm run test`
|
||||
1. Verify diffs are reasonable, and commit
|
||||
1. `npx release-it --only-version`
|
||||
|
||||
6. Finally, back on the POSIX box, release `exiftool-vendored`:
|
||||
|
||||
1. `cd ../exiftool-vendored.js`
|
||||
1. `npm run u`
|
||||
1. `npm run mktags ../test-images` # < assumes `../test-images` has the full ExifTool sample image suite
|
||||
1. `npm run fmt && npm run lint && npm run docs`
|
||||
1. Verify docs were rebuilt successfully at <http://localhost:3000/index.html>
|
||||
1. `npm run test`
|
||||
1. Verify diffs are reasonable, `git commit` and `git push`
|
||||
1. Verify [](https://github.com/photostructure/exiftool-vendored.js/actions/workflows/node.js.yml)
|
||||
1. Update the [CHANGELOG.md](https://github.com/photostructure/exiftool-vendored.js/blob/main/CHANGELOG.md)
|
||||
1. `npx release-it --only-version`
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the latest version of this library is supported.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you find a vulnerability, _or even think you have_, please send an email to
|
||||
the author. Each signed git release by `mceachen` contains a monitored email
|
||||
address.
|
||||
|
||||
If you prefer, you may [DM @mrm on the PhotoStructure forum](https://forum.photostructure.com/new-message?username=mrm&title=Security+concern).
|
||||
|
||||
Expect a reply in under 36 hours. Please try an alternative contact method if
|
||||
you don't get a response.
|
||||
|
||||
Thank you!
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { IPTCApplicationRecordTags } from "./IPTCApplicationRecordTags";
|
||||
/**
|
||||
* @deprecated Use {@link IPTCApplicationRecordTags} instead
|
||||
*/
|
||||
export type ApplicationRecordTags = IPTCApplicationRecordTags;
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=ApplicationRecordTags.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ApplicationRecordTags.js","sourceRoot":"","sources":["../src/ApplicationRecordTags.ts"],"names":[],"mappings":""}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { Maybe, Nullable } from "./Maybe";
|
||||
export declare function isIterable(obj: unknown): obj is Iterable<unknown>;
|
||||
export declare function ifArray<T = unknown>(arr: T[] | unknown): Maybe<T[]>;
|
||||
export declare function toArray<T>(arr: undefined | null | T[] | T | Iterable<T>): T[];
|
||||
export declare function compact<T>(array: Nullable<T>[]): T[];
|
||||
/**
|
||||
* Remove all elements from the given array that return false from the given
|
||||
* predicate `filter`.
|
||||
*/
|
||||
export declare function filterInPlace<T>(arr: T[], filter: (t: T) => boolean): T[];
|
||||
export declare function uniq<T>(arr: T[]): T[];
|
||||
export declare function shallowArrayEql(a: unknown[], b: unknown[]): boolean;
|
||||
type Comparable = number | string | boolean;
|
||||
/**
|
||||
* Returns a copy of arr, stable sorted by the given constraint. Note that false
|
||||
* < true, and that `f` may return an array for sort priorities, or undefined if
|
||||
* the item should be skipped from the returned result.
|
||||
*
|
||||
* Note: localeSort() thinks lower case should come before upper case (!!)
|
||||
*/
|
||||
export declare function sortBy<T>(arr: Iterable<Maybe<T>> | Maybe<T>[], f: (t: T) => Maybe<Comparable>): T[];
|
||||
export declare function leastBy<T>(haystack: T[], f: (t: T) => Maybe<Comparable>): Maybe<T>;
|
||||
export {};
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isIterable = isIterable;
|
||||
exports.ifArray = ifArray;
|
||||
exports.toArray = toArray;
|
||||
exports.compact = compact;
|
||||
exports.filterInPlace = filterInPlace;
|
||||
exports.uniq = uniq;
|
||||
exports.shallowArrayEql = shallowArrayEql;
|
||||
exports.sortBy = sortBy;
|
||||
exports.leastBy = leastBy;
|
||||
const Object_1 = require("./Object");
|
||||
const String_1 = require("./String");
|
||||
function isIterable(obj) {
|
||||
return ((0, Object_1.isObject)(obj) && Symbol.iterator in obj) || Array.isArray(obj);
|
||||
}
|
||||
function ifArray(arr) {
|
||||
return Array.isArray(arr) ? arr : undefined;
|
||||
}
|
||||
function toArray(arr) {
|
||||
return Array.isArray(arr) // < strings are not arrays
|
||||
? arr
|
||||
: arr == null
|
||||
? []
|
||||
: (0, String_1.isString)(arr) // < don't rely on isIterable rejecting Strings
|
||||
? [arr]
|
||||
: isIterable(arr)
|
||||
? Array.from(arr)
|
||||
: [arr];
|
||||
}
|
||||
function compact(array) {
|
||||
return array.filter((elem) => elem != null);
|
||||
}
|
||||
/**
|
||||
* Remove all elements from the given array that return false from the given
|
||||
* predicate `filter`.
|
||||
*/
|
||||
function filterInPlace(arr, filter) {
|
||||
let j = 0;
|
||||
arr.forEach((ea, i) => {
|
||||
if (filter(ea)) {
|
||||
if (i !== j)
|
||||
arr[j] = ea;
|
||||
j++;
|
||||
}
|
||||
});
|
||||
arr.length = j;
|
||||
return arr;
|
||||
}
|
||||
function uniq(arr) {
|
||||
return arr.reduce((acc, ea) => {
|
||||
if (acc.indexOf(ea) === -1)
|
||||
acc.push(ea);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
// terrible implementation only for internal use
|
||||
function shallowArrayEql(a, b) {
|
||||
return (a != null &&
|
||||
b != null &&
|
||||
a.length === b.length &&
|
||||
a.every((ea, idx) => ea === b[idx]));
|
||||
}
|
||||
/**
|
||||
* Returns a copy of arr, stable sorted by the given constraint. Note that false
|
||||
* < true, and that `f` may return an array for sort priorities, or undefined if
|
||||
* the item should be skipped from the returned result.
|
||||
*
|
||||
* Note: localeSort() thinks lower case should come before upper case (!!)
|
||||
*/
|
||||
function sortBy(arr, f) {
|
||||
return toArray(arr).filter((ea) => ea != null)
|
||||
.map((item) => ({
|
||||
item,
|
||||
cmp: f(item),
|
||||
}))
|
||||
.filter((ea) => ea.cmp != null)
|
||||
.sort((a, b) => cmp(a.cmp, b.cmp))
|
||||
.map((ea) => ea.item);
|
||||
}
|
||||
function cmp(a, b) {
|
||||
// undefined == undefined:
|
||||
if (a == null && b == null)
|
||||
return 0;
|
||||
// undefined should be < defined. We can't use typeof here because typeof null
|
||||
// is "object" and typeof undefined = "undefined".
|
||||
if (a == null)
|
||||
return -1;
|
||||
if (b == null)
|
||||
return 1;
|
||||
const aType = typeof a;
|
||||
const bType = typeof b;
|
||||
if ((aType === "string" || aType === "symbol") &&
|
||||
(bType === "string" || bType === "symbol")) {
|
||||
// in German, ä sorts before z, in Swedish, ä sorts after z
|
||||
return String(a).localeCompare(String(b));
|
||||
}
|
||||
return a > b ? 1 : a < b ? -1 : 0;
|
||||
}
|
||||
function leastBy(haystack, f) {
|
||||
let min;
|
||||
let result;
|
||||
for (const ea of haystack) {
|
||||
const val = f(ea);
|
||||
if (val != null && (min == null || val < min)) {
|
||||
min = val;
|
||||
result = ea;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
//# sourceMappingURL=Array.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Array.js","sourceRoot":"","sources":["../src/Array.ts"],"names":[],"mappings":";;AAIA,gCAEC;AAED,0BAEC;AAED,0BAUC;AAED,0BAEC;AAMD,sCAUC;AAED,oBAKC;AAGD,0CAOC;AAWD,wBAYC;AAwBD,0BAcC;AAvHD,qCAAoC;AACpC,qCAAoC;AAEpC,SAAgB,UAAU,CAAC,GAAY;IACrC,OAAO,CAAC,IAAA,iBAAQ,EAAC,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AACzE,CAAC;AAED,SAAgB,OAAO,CAAc,GAAkB;IACrD,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9C,CAAC;AAED,SAAgB,OAAO,CAAI,GAA6C;IACtE,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,2BAA2B;QACnD,CAAC,CAAE,GAAW;QACd,CAAC,CAAC,GAAG,IAAI,IAAI;YACX,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,IAAA,iBAAQ,EAAC,GAAG,CAAC,CAAC,+CAA+C;gBAC7D,CAAC,CAAC,CAAC,GAAQ,CAAC;gBACZ,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;oBACf,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;oBACjB,CAAC,CAAC,CAAC,GAAQ,CAAC,CAAC;AACvB,CAAC;AAED,SAAgB,OAAO,CAAI,KAAoB;IAC7C,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,IAAI,CAAQ,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAAI,GAAQ,EAAE,MAAyB;IAClE,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE;QACpB,IAAI,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC;gBAAE,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YACzB,CAAC,EAAE,CAAC;QACN,CAAC;IACH,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACf,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAgB,IAAI,CAAI,GAAQ;IAC9B,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE;QAC5B,IAAI,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzC,OAAO,GAAG,CAAC;IACb,CAAC,EAAE,EAAS,CAAC,CAAC;AAChB,CAAC;AAED,gDAAgD;AAChD,SAAgB,eAAe,CAAC,CAAY,EAAE,CAAY;IACxD,OAAO,CACL,CAAC,IAAI,IAAI;QACT,CAAC,IAAI,IAAI;QACT,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QACrB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CACpC,CAAC;AACJ,CAAC;AAID;;;;;;GAMG;AACH,SAAgB,MAAM,CACpB,GAAoC,EACpC,CAA8B;IAE9B,OAAQ,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,IAAI,CAAS;SACpD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACd,IAAI;QACJ,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC;KACb,CAAC,CAAC;SACF,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,IAAI,IAAI,CAAC;SAC9B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,GAAI,EAAE,CAAC,CAAC,GAAI,CAAC,CAAC;SACnC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,GAAG,CAAC,CAAoB,EAAE,CAAoB;IACrD,0BAA0B;IAC1B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,CAAC,CAAC;IAErC,8EAA8E;IAC9E,kDAAkD;IAClD,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,CAAC,CAAC,CAAC;IACzB,IAAI,CAAC,IAAI,IAAI;QAAE,OAAO,CAAC,CAAC;IAExB,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC;IACvB,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC;IAEvB,IACE,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,QAAQ,CAAC;QAC1C,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,QAAQ,CAAC,EAC1C,CAAC;QACD,2DAA2D;QAC3D,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpC,CAAC;AAED,SAAgB,OAAO,CACrB,QAAa,EACb,CAA8B;IAE9B,IAAI,GAAsB,CAAC;IAC3B,IAAI,MAAgB,CAAC;IACrB,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QAClB,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,GAAG,GAAG,GAAG,CAAC,EAAE,CAAC;YAC9C,GAAG,GAAG,GAAG,CAAC;YACV,MAAM,GAAG,EAAE,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,MAAO,CAAC;AACjB,CAAC"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare function retryOnReject<T>(f: () => T | Promise<T>, maxRetries: number): Promise<T>;
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.retryOnReject = retryOnReject;
|
||||
function retryOnReject(f, maxRetries) {
|
||||
let retries = 0;
|
||||
const g = async () => {
|
||||
try {
|
||||
return await f();
|
||||
}
|
||||
catch (err) {
|
||||
if (retries < maxRetries) {
|
||||
retries++;
|
||||
return g();
|
||||
}
|
||||
else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
};
|
||||
return g();
|
||||
}
|
||||
//# sourceMappingURL=AsyncRetry.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AsyncRetry.js","sourceRoot":"","sources":["../src/AsyncRetry.ts"],"names":[],"mappings":";;AAAA,sCAkBC;AAlBD,SAAgB,aAAa,CAC3B,CAAuB,EACvB,UAAkB;IAElB,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,CAAC,GAAqB,KAAK,IAAI,EAAE;QACrC,IAAI,CAAC;YACH,OAAO,MAAM,CAAC,EAAE,CAAC;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,OAAO,GAAG,UAAU,EAAE,CAAC;gBACzB,OAAO,EAAE,CAAC;gBACV,OAAO,CAAC,EAAE,CAAC;YACb,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;IACH,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC;AACb,CAAC"}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { ExifToolOptions } from "./ExifToolOptions";
|
||||
import { ExifToolTask } from "./ExifToolTask";
|
||||
import { Maybe } from "./Maybe";
|
||||
export type BinaryExtractionTaskOptions = Pick<ExifToolOptions, "ignoreMinorErrors"> & Partial<Pick<ExifToolOptions, "forceWrite">>;
|
||||
/**
|
||||
* Task that returns an error string (to prevent retries), or undefined if
|
||||
* everything seems to have worked.
|
||||
*/
|
||||
export declare class BinaryExtractionTask extends ExifToolTask<Maybe<string>> {
|
||||
private constructor();
|
||||
static for(tagname: string, imgSrc: string, imgDest: string, options?: BinaryExtractionTaskOptions): BinaryExtractionTask;
|
||||
parse(stdout: string, err?: Error): Maybe<string>;
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BinaryExtractionTask = void 0;
|
||||
const node_fs_1 = require("node:fs");
|
||||
const node_path_1 = __importDefault(require("node:path"));
|
||||
const ExifToolTask_1 = require("./ExifToolTask");
|
||||
const FilenameCharsetArgs_1 = require("./FilenameCharsetArgs");
|
||||
const String_1 = require("./String");
|
||||
const StdoutRe = /\b(\d+) output files? created/i;
|
||||
/**
|
||||
* Task that returns an error string (to prevent retries), or undefined if
|
||||
* everything seems to have worked.
|
||||
*/
|
||||
class BinaryExtractionTask extends ExifToolTask_1.ExifToolTask {
|
||||
constructor(args, options) {
|
||||
super(args, options);
|
||||
}
|
||||
static for(tagname, imgSrc, imgDest, options) {
|
||||
// Ensure the destination directory exists:
|
||||
(0, node_fs_1.mkdirSync)(node_path_1.default.dirname(imgDest), { recursive: true });
|
||||
const forceWrite = options?.forceWrite ?? false;
|
||||
const args = [
|
||||
...FilenameCharsetArgs_1.Utf8FilenameCharsetArgs,
|
||||
"-b", // -binary
|
||||
"-" + tagname,
|
||||
// Capital W allows destination files to not have a pattern. See
|
||||
// https://exiftool.org/exiftool_pod.html#W-FMT--tagOut
|
||||
//
|
||||
// Prior code used -w with %0f "to avoid shell expansion", but as this
|
||||
// command gets sent directly to exiftool thanks to stay_open mode, we
|
||||
// don't need to worry about shell expansion.
|
||||
//
|
||||
// I also tried `-out` instead of `-W`, and that didn't work at least on
|
||||
// ExifTool 13.17.
|
||||
forceWrite ? "-W!" : "-W",
|
||||
node_path_1.default.resolve(imgDest),
|
||||
node_path_1.default.resolve(imgSrc),
|
||||
];
|
||||
return new BinaryExtractionTask(args, options);
|
||||
}
|
||||
parse(stdout, err) {
|
||||
const s = (0, String_1.toS)(stdout).trim();
|
||||
const m = StdoutRe.exec(s);
|
||||
if (err != null) {
|
||||
throw err;
|
||||
}
|
||||
else if (m == null) {
|
||||
throw new Error("Missing expected status message (got " + stdout + ")");
|
||||
}
|
||||
else if (m[1] === "1") {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// Don't retry: the binary payload is missing, and retrying won't fix that.
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.BinaryExtractionTask = BinaryExtractionTask;
|
||||
//# sourceMappingURL=BinaryExtractionTask.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"BinaryExtractionTask.js","sourceRoot":"","sources":["../src/BinaryExtractionTask.ts"],"names":[],"mappings":";;;;;;AAAA,qCAAoC;AACpC,0DAA6B;AAE7B,iDAA8C;AAC9C,+DAAgE;AAEhE,qCAA+B;AAQ/B,MAAM,QAAQ,GAAG,gCAAgC,CAAC;AAElD;;;GAGG;AACH,MAAa,oBAAqB,SAAQ,2BAA2B;IACnE,YAAoB,IAAc,EAAE,OAAqC;QACvE,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,GAAG,CACR,OAAe,EACf,MAAc,EACd,OAAe,EACf,OAAqC;QAErC,2CAA2C;QAC3C,IAAA,mBAAS,EAAC,mBAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEtD,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,KAAK,CAAC;QAEhD,MAAM,IAAI,GAAG;YACX,GAAG,6CAAuB;YAC1B,IAAI,EAAE,UAAU;YAChB,GAAG,GAAG,OAAO;YACb,gEAAgE;YAChE,uDAAuD;YACvD,EAAE;YACF,sEAAsE;YACtE,sEAAsE;YACtE,6CAA6C;YAC7C,EAAE;YACF,wEAAwE;YACxE,kBAAkB;YAClB,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;YACzB,mBAAI,CAAC,OAAO,CAAC,OAAO,CAAC;YACrB,mBAAI,CAAC,OAAO,CAAC,MAAM,CAAC;SACrB,CAAC;QACF,OAAO,IAAI,oBAAoB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,MAAc,EAAE,GAAW;QAC/B,MAAM,CAAC,GAAG,IAAA,YAAG,EAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7B,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC3B,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;YAChB,MAAM,GAAG,CAAC;QACZ,CAAC;aAAM,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,uCAAuC,GAAG,MAAM,GAAG,GAAG,CAAC,CAAC;QAC1E,CAAC;aAAM,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACxB,OAAO;QACT,CAAC;aAAM,CAAC;YACN,2EAA2E;YAC3E,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;CACF;AAlDD,oDAkDC"}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { Maybe } from "./Maybe";
|
||||
/**
|
||||
* Represents a binary field in the Exif data.
|
||||
*
|
||||
* These fields can be used to store images (including thumbnails and full-sized
|
||||
* images), audio, and other binary data. The size of the field is specified in
|
||||
* bytes.
|
||||
*/
|
||||
export declare class BinaryField {
|
||||
readonly bytes: number;
|
||||
readonly rawValue: string;
|
||||
constructor(bytes: number, rawValue: string);
|
||||
toJSON(): {
|
||||
_ctor: string;
|
||||
bytes: number;
|
||||
rawValue: string;
|
||||
};
|
||||
static fromJSON(json: ReturnType<BinaryField["toJSON"]>): BinaryField;
|
||||
static fromRawValue(rawValue: string): Maybe<BinaryField>;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BinaryField = void 0;
|
||||
const Number_1 = require("./Number");
|
||||
// "(Binary data 2506078 bytes, use -b option to extract)"
|
||||
const BinaryFieldRE =
|
||||
// 1000000000 bytes is 1 GB. The largest binary field I've seen is ~5 MB (7
|
||||
// chars): 10 chars is absurdly large, and is just to avoid the
|
||||
// `js/polynomial-redos` eslint rule.
|
||||
/Binary(?: data)? (\d{1,10}) bytes/i;
|
||||
/**
|
||||
* Represents a binary field in the Exif data.
|
||||
*
|
||||
* These fields can be used to store images (including thumbnails and full-sized
|
||||
* images), audio, and other binary data. The size of the field is specified in
|
||||
* bytes.
|
||||
*/
|
||||
class BinaryField {
|
||||
bytes;
|
||||
rawValue;
|
||||
constructor(bytes, rawValue) {
|
||||
this.bytes = bytes;
|
||||
this.rawValue = rawValue;
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
_ctor: "BinaryField",
|
||||
bytes: this.bytes,
|
||||
rawValue: this.rawValue,
|
||||
};
|
||||
}
|
||||
static fromJSON(json) {
|
||||
return new BinaryField(json.bytes, json.rawValue);
|
||||
}
|
||||
static fromRawValue(rawValue) {
|
||||
const m = rawValue.match(BinaryFieldRE);
|
||||
if (m != null) {
|
||||
const bytes = (0, Number_1.toInt)(m[1]);
|
||||
if (bytes != null) {
|
||||
return new BinaryField(bytes, rawValue);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
exports.BinaryField = BinaryField;
|
||||
//# sourceMappingURL=BinaryField.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"BinaryField.js","sourceRoot":"","sources":["../src/BinaryField.ts"],"names":[],"mappings":";;;AACA,qCAAiC;AAEjC,0DAA0D;AAC1D,MAAM,aAAa;AACjB,2EAA2E;AAC3E,+DAA+D;AAC/D,qCAAqC;AACrC,oCAAoC,CAAC;AAEvC;;;;;;GAMG;AACH,MAAa,WAAW;IAEX;IACA;IAFX,YACW,KAAa,EACb,QAAgB;QADhB,UAAK,GAAL,KAAK,CAAQ;QACb,aAAQ,GAAR,QAAQ,CAAQ;IACxB,CAAC;IAEJ,MAAM;QACJ,OAAO;YACL,KAAK,EAAE,aAAa;YACpB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,IAAuC;QACrD,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,QAAgB;QAClC,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YACd,MAAM,KAAK,GAAG,IAAA,cAAK,EAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,OAAO,IAAI,WAAW,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YAC1C,CAAC;QACH,CAAC;QACD,OAAO;IACT,CAAC;CACF;AA5BD,kCA4BC"}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { ExifToolTask, ExifToolTaskOptions } from "./ExifToolTask";
|
||||
/**
|
||||
* Task that returns an error string (to prevent retries), or undefined if
|
||||
* everything seems to have worked.
|
||||
*/
|
||||
export declare class BinaryToBufferTask extends ExifToolTask<Buffer | Error> {
|
||||
readonly tagname: string;
|
||||
private constructor();
|
||||
static for(tagname: string, imgSrc: string, options?: ExifToolTaskOptions): BinaryToBufferTask;
|
||||
parse(data: string, err?: Error): Buffer | Error;
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.BinaryToBufferTask = void 0;
|
||||
const node_path_1 = __importDefault(require("node:path"));
|
||||
const ExifToolTask_1 = require("./ExifToolTask");
|
||||
const FilenameCharsetArgs_1 = require("./FilenameCharsetArgs");
|
||||
const String_1 = require("./String");
|
||||
/**
|
||||
* Task that returns an error string (to prevent retries), or undefined if
|
||||
* everything seems to have worked.
|
||||
*/
|
||||
class BinaryToBufferTask extends ExifToolTask_1.ExifToolTask {
|
||||
tagname;
|
||||
constructor(tagname, args, options) {
|
||||
super(args, options);
|
||||
this.tagname = tagname;
|
||||
}
|
||||
static for(tagname, imgSrc, options) {
|
||||
// NOTE TO FUTURE ME: we don't need to escape these arguments, because
|
||||
// ExifTool separates them via newlines.
|
||||
const args = [...FilenameCharsetArgs_1.Utf8FilenameCharsetArgs, "-json", "-b", "-" + tagname];
|
||||
args.push(node_path_1.default.resolve(imgSrc));
|
||||
return new BinaryToBufferTask(tagname, args, options);
|
||||
}
|
||||
parse(data, err) {
|
||||
try {
|
||||
const obj = JSON.parse(data)?.[0];
|
||||
// did they get the casing right?
|
||||
{
|
||||
const result = decode(obj[this.tagname]);
|
||||
if (result != null)
|
||||
return result;
|
||||
} // hmm, incorrect casing. Check the other keys.
|
||||
for (const k of Object.keys(obj)) {
|
||||
if (k.toLowerCase() === this.tagname.toLowerCase()) {
|
||||
const result = decode(obj[k]);
|
||||
if (result != null)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (caught) {
|
||||
err ??= (0, String_1.notBlank)(data)
|
||||
? new Error(data)
|
||||
: caught instanceof Error
|
||||
? caught
|
||||
: new Error(String(caught));
|
||||
}
|
||||
return err ?? new Error(this.tagname + " not found");
|
||||
}
|
||||
}
|
||||
exports.BinaryToBufferTask = BinaryToBufferTask;
|
||||
const B64Prefix = "base64:";
|
||||
function decode(data) {
|
||||
return data == null || !data.startsWith(B64Prefix)
|
||||
? undefined
|
||||
: Buffer.from(data.substring(B64Prefix.length), "base64");
|
||||
}
|
||||
//# sourceMappingURL=BinaryToBufferTask.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"BinaryToBufferTask.js","sourceRoot":"","sources":["../src/BinaryToBufferTask.ts"],"names":[],"mappings":";;;;;;AAAA,0DAA6B;AAC7B,iDAAmE;AACnE,+DAAgE;AAEhE,qCAAoC;AAEpC;;;GAGG;AACH,MAAa,kBAAmB,SAAQ,2BAA4B;IAEvD;IADX,YACW,OAAe,EACxB,IAAc,EACd,OAA6B;QAE7B,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAJZ,YAAO,GAAP,OAAO,CAAQ;IAK1B,CAAC;IAED,MAAM,CAAC,GAAG,CACR,OAAe,EACf,MAAc,EACd,OAA6B;QAE7B,sEAAsE;QACtE,wCAAwC;QACxC,MAAM,IAAI,GAAG,CAAC,GAAG,6CAAuB,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,GAAG,OAAO,CAAC,CAAC;QACxE,IAAI,CAAC,IAAI,CAAC,mBAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;QAChC,OAAO,IAAI,kBAAkB,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,IAAY,EAAE,GAAW;QAC7B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAClC,iCAAiC;YACjC,CAAC;gBACC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;gBACzC,IAAI,MAAM,IAAI,IAAI;oBAAE,OAAO,MAAM,CAAC;YACpC,CAAC,CAAC,+CAA+C;YACjD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjC,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;oBACnD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC9B,IAAI,MAAM,IAAI,IAAI;wBAAE,OAAO,MAAM,CAAC;gBACpC,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,MAAM,EAAE,CAAC;YAChB,GAAG,KAAK,IAAA,iBAAQ,EAAC,IAAI,CAAC;gBACpB,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC;gBACjB,CAAC,CAAC,MAAM,YAAY,KAAK;oBACvB,CAAC,CAAC,MAAM;oBACR,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,GAAG,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,GAAG,YAAY,CAAC,CAAC;IACvD,CAAC;CACF;AA5CD,gDA4CC;AAED,MAAM,SAAS,GAAG,SAAS,CAAC;AAE5B,SAAS,MAAM,CAAC,IAAmB;IACjC,OAAO,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAChD,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC9D,CAAC"}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
import { Maybe } from "./Maybe";
|
||||
export declare function toBoolean(value: unknown): Maybe<boolean>;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.toBoolean = toBoolean;
|
||||
const Truthy = ["true", "yes", "1", "on"];
|
||||
const Falsy = ["false", "no", "0", "off"];
|
||||
function toBoolean(value) {
|
||||
if (value == null)
|
||||
return undefined;
|
||||
if (typeof value === "boolean")
|
||||
return value;
|
||||
const s = String(value).trim().toLowerCase();
|
||||
return Truthy.includes(s) ? true : Falsy.includes(s) ? false : undefined;
|
||||
}
|
||||
//# sourceMappingURL=Boolean.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Boolean.js","sourceRoot":"","sources":["../src/Boolean.ts"],"names":[],"mappings":";;AAKA,8BAKC;AARD,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;AAC1C,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;AAE1C,SAAgB,SAAS,CAAC,KAAc;IACtC,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,SAAS,CAAC;IACpC,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7C,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7C,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3E,CAAC"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare const CapturedAtTagNames: readonly ["SubSecDateTimeOriginal", "SubSecCreateDate", "SubSecMediaCreateDate", "DateTimeOriginal", "CreateDate", "MediaCreateDate", "CreationDate", "DateTimeCreated", "TimeCreated"];
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CapturedAtTagNames = void 0;
|
||||
exports.CapturedAtTagNames = [
|
||||
"SubSecDateTimeOriginal",
|
||||
"SubSecCreateDate",
|
||||
"SubSecMediaCreateDate",
|
||||
"DateTimeOriginal",
|
||||
"CreateDate",
|
||||
"MediaCreateDate",
|
||||
"CreationDate", // < Found in some transcoded Apple movies
|
||||
"DateTimeCreated",
|
||||
"TimeCreated", // < may not have the date
|
||||
];
|
||||
//# sourceMappingURL=CapturedAtTagNames.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"CapturedAtTagNames.js","sourceRoot":"","sources":["../src/CapturedAtTagNames.ts"],"names":[],"mappings":";;;AAEa,QAAA,kBAAkB,GAAG;IAChC,wBAAwB;IACxB,kBAAkB;IAClB,uBAAuB;IACvB,kBAAkB;IAClB,YAAY;IACZ,iBAAiB;IACjB,cAAc,EAAE,0CAA0C;IAC1D,iBAAiB;IACjB,aAAa,EAAE,0BAA0B;CACC,CAAC"}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Found in newer Android Motion Photos
|
||||
*
|
||||
* @see https://github.com/photostructure/exiftool-vendored.js/pull/189
|
||||
*/
|
||||
export interface ContainerDirectoryItem {
|
||||
Item: {
|
||||
Length?: number;
|
||||
Mime?: string;
|
||||
Padding?: number;
|
||||
Semantic?: string;
|
||||
};
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=ContainerDirectoryItem.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ContainerDirectoryItem.js","sourceRoot":"","sources":["../src/ContainerDirectoryItem.ts"],"names":[],"mappings":""}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
import { Maybe } from "./Maybe";
|
||||
type Direction = "N" | "S" | "E" | "W";
|
||||
type CoordinateFormat = "DMS" | "DM" | "D";
|
||||
interface Coordinate {
|
||||
decimal: number;
|
||||
degrees: number;
|
||||
minutes: number | undefined;
|
||||
seconds: number | undefined;
|
||||
direction: Direction | undefined;
|
||||
format: CoordinateFormat;
|
||||
remainder: string | undefined;
|
||||
}
|
||||
export interface CoordinateResult {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}
|
||||
/**
|
||||
* Parses a string containing both latitude and longitude coordinates.
|
||||
* @param input - String containing both coordinates
|
||||
* @returns Object containing latitude and longitude in decimal degrees, or undefined if parsing fails
|
||||
* @throws CoordinateParseError if the input format is invalid
|
||||
*/
|
||||
export declare function parseCoordinates(input: string): CoordinateResult;
|
||||
/**
|
||||
* Parses a coordinate string in decimal degrees format.
|
||||
* @param input - String containing a single coordinate
|
||||
* @returns Object containing degrees and direction, or undefined if parsing fails
|
||||
* @throws CoordinateParseError if the format is not decimal degrees or direction is missing
|
||||
*/
|
||||
export declare function parseDecimalCoordinate(input: string): {
|
||||
decimal: number;
|
||||
direction: Direction;
|
||||
} | undefined;
|
||||
/**
|
||||
* Parses a single coordinate string into its components.
|
||||
* @param input - String containing a single coordinate
|
||||
* @param expectRemainders - If true, allow additional text after the coordinate
|
||||
* @returns Parsed coordinate object
|
||||
* @throws CoordinateParseError if the format is invalid
|
||||
*/
|
||||
export declare function parseCoordinate(input: string, expectRemainders?: boolean): Coordinate;
|
||||
export type CoordinateType = "Latitude" | "Longitude";
|
||||
export interface CoordinateConfig {
|
||||
value: number;
|
||||
ref: string | undefined;
|
||||
geoValue: number | undefined;
|
||||
expectedRefPositive: "N" | "E";
|
||||
expectedRefNegative: "S" | "W";
|
||||
max: 90 | 180;
|
||||
coordinateType: CoordinateType;
|
||||
}
|
||||
export declare function roundGpsDecimal(decimal: number): number;
|
||||
export declare function parsePosition(position: Maybe<string>): Maybe<[number, number]>;
|
||||
export declare function processCoordinate(config: CoordinateConfig, warnings: string[]): {
|
||||
value: number;
|
||||
ref: string;
|
||||
isInvalid: boolean;
|
||||
};
|
||||
export {};
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.parseCoordinates = parseCoordinates;
|
||||
exports.parseDecimalCoordinate = parseDecimalCoordinate;
|
||||
exports.parseCoordinate = parseCoordinate;
|
||||
exports.roundGpsDecimal = roundGpsDecimal;
|
||||
exports.parsePosition = parsePosition;
|
||||
exports.processCoordinate = processCoordinate;
|
||||
const Number_1 = require("./Number");
|
||||
const String_1 = require("./String");
|
||||
// Constants
|
||||
const MAX_LATITUDE_DEGREES = 90;
|
||||
const MAX_LONGITUDE_DEGREES = 180;
|
||||
class CoordinateParseError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "CoordinateParseError";
|
||||
}
|
||||
}
|
||||
// Regex to match simple decimal coordinates, like "37.5, -122.5"
|
||||
const DecimalCoordsRE = /^(-?\d+(?:\.\d+)?)[,\s]+(-?\d+(?:\.\d+)?)$/;
|
||||
/**
|
||||
* Parses a string containing both latitude and longitude coordinates.
|
||||
* @param input - String containing both coordinates
|
||||
* @returns Object containing latitude and longitude in decimal degrees, or undefined if parsing fails
|
||||
* @throws CoordinateParseError if the input format is invalid
|
||||
*/
|
||||
function parseCoordinates(input) {
|
||||
input = (0, String_1.toS)(input).trim();
|
||||
if (input.length === 0) {
|
||||
throw new CoordinateParseError("Input string cannot be empty");
|
||||
}
|
||||
if (DecimalCoordsRE.test(input)) {
|
||||
const split = input.split(/[\s,]+/);
|
||||
const [latitude, longitude] = split
|
||||
.map(Number_1.toFloat)
|
||||
.map((ea) => (ea == null ? null : roundGpsDecimal(ea)));
|
||||
if (latitude == null || longitude == null) {
|
||||
throw new CoordinateParseError("Failed to parse decimal coordinates");
|
||||
}
|
||||
return { latitude, longitude };
|
||||
}
|
||||
let latitude;
|
||||
let longitude;
|
||||
for (const coord of parseStringCoordinates(input)) {
|
||||
if (!coord.direction) {
|
||||
throw new CoordinateParseError("Direction is required for position parsing");
|
||||
}
|
||||
if (coord.direction === "S" || coord.direction === "N") {
|
||||
if (latitude !== undefined) {
|
||||
throw new CoordinateParseError("Multiple latitude values found");
|
||||
}
|
||||
latitude = toDecimalDegrees(coord);
|
||||
}
|
||||
else {
|
||||
if (longitude != null) {
|
||||
throw new CoordinateParseError("Multiple longitude values found");
|
||||
}
|
||||
longitude = toDecimalDegrees(coord);
|
||||
}
|
||||
}
|
||||
const missing = [];
|
||||
if (latitude == null)
|
||||
missing.push("latitude");
|
||||
if (longitude == null)
|
||||
missing.push("longitude");
|
||||
if (latitude == null || longitude == null) {
|
||||
throw new CoordinateParseError(`Missing ${missing.join(" and ")}`);
|
||||
}
|
||||
else {
|
||||
return { latitude, longitude };
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parses a string containing one or more coordinates.
|
||||
* @param input - String containing coordinates
|
||||
* @returns Array of parsed coordinates
|
||||
*/
|
||||
function parseStringCoordinates(input) {
|
||||
if (!input?.trim()) {
|
||||
throw new CoordinateParseError("Input string cannot be empty");
|
||||
}
|
||||
const lat = parseCoordinate(input, true);
|
||||
const remainders = lat.remainder;
|
||||
if ((0, String_1.blank)(remainders)) {
|
||||
throw new CoordinateParseError("Expected multiple coordinates");
|
||||
}
|
||||
return [lat, parseCoordinate(remainders)];
|
||||
}
|
||||
/**
|
||||
* Parses a coordinate string in decimal degrees format.
|
||||
* @param input - String containing a single coordinate
|
||||
* @returns Object containing degrees and direction, or undefined if parsing fails
|
||||
* @throws CoordinateParseError if the format is not decimal degrees or direction is missing
|
||||
*/
|
||||
function parseDecimalCoordinate(input) {
|
||||
if (!input?.trim()) {
|
||||
throw new CoordinateParseError("Input string cannot be empty");
|
||||
}
|
||||
const coord = parseCoordinate(input);
|
||||
if (coord.format !== "D") {
|
||||
throw new CoordinateParseError("Expected decimal degrees format");
|
||||
}
|
||||
if (!coord.direction) {
|
||||
throw new CoordinateParseError("Missing direction");
|
||||
}
|
||||
return { decimal: toDecimalDegrees(coord), direction: coord.direction };
|
||||
}
|
||||
const DecimalCoordRE = /^(-?\d+(?:\.\d+)?)$/;
|
||||
/**
|
||||
* Parses a single coordinate string into its components.
|
||||
* @param input - String containing a single coordinate
|
||||
* @param expectRemainders - If true, allow additional text after the coordinate
|
||||
* @returns Parsed coordinate object
|
||||
* @throws CoordinateParseError if the format is invalid
|
||||
*/
|
||||
function parseCoordinate(input, expectRemainders = false) {
|
||||
input = (0, String_1.toS)(input).trim();
|
||||
if (input.length === 0) {
|
||||
throw new CoordinateParseError("Input string cannot be empty");
|
||||
}
|
||||
if (DecimalCoordRE.test(input)) {
|
||||
const f = (0, Number_1.toFloat)(input);
|
||||
if (f == null) {
|
||||
throw new CoordinateParseError("Failed to parse decimal coordinate");
|
||||
}
|
||||
const r = roundGpsDecimal(f);
|
||||
return {
|
||||
degrees: r,
|
||||
decimal: r,
|
||||
format: "D",
|
||||
direction: undefined,
|
||||
minutes: undefined,
|
||||
seconds: undefined,
|
||||
remainder: "",
|
||||
};
|
||||
}
|
||||
const dmsPattern = /^(?<degrees>-?\d+)\s*(?:°|DEG)\s*(?<minutes>\d+)\s*['′]\s*(?<seconds>\d+(?:\.\d+)?)\s*["″]\s?(?<direction>[NSEW])?[\s,]{0,3}(?<remainder>.*)$/i;
|
||||
const dmPattern = /^(?<degrees>-?\d+)\s*(?:°|DEG)\s*(?<minutes>\d+(?:\.\d+)?)\s?['′]\s?(?<direction>[NSEW])?(?<remainder>.*)$/i;
|
||||
const dPattern = /^(?<degrees>-?\d+(?:\.\d+)?)\s*(?:°|DEG)\s?(?<direction>[NSEW])?(?<remainder>.*)$/i;
|
||||
const trimmedInput = input.trimStart();
|
||||
let match;
|
||||
let format = null;
|
||||
if ((match = trimmedInput.match(dmsPattern))) {
|
||||
format = "DMS";
|
||||
}
|
||||
else if ((match = trimmedInput.match(dmPattern))) {
|
||||
format = "DM";
|
||||
}
|
||||
else if ((match = trimmedInput.match(dPattern))) {
|
||||
format = "D";
|
||||
}
|
||||
if (match == null ||
|
||||
format == null ||
|
||||
(!expectRemainders && !(0, String_1.blank)(match?.groups?.remainder))) {
|
||||
throw new CoordinateParseError("Invalid coordinate format. Expected one of:\n" +
|
||||
" DDD° MM' SS.S\" k (deg/min/sec)\n" +
|
||||
" DDD° MM.MMM' k (deg/decimal minutes)\n" +
|
||||
" DDD.DDDDD° (decimal degrees)\n" +
|
||||
" (where k indicates direction: N, S, E, or W)");
|
||||
}
|
||||
if (!match.groups) {
|
||||
throw new CoordinateParseError("Failed to parse coordinate components");
|
||||
}
|
||||
const { degrees: degreesStr, minutes: minutesStr, seconds: secondsStr, direction: directionStr, remainder, } = match.groups;
|
||||
const direction = directionStr?.toUpperCase();
|
||||
const degrees = parseFloat(degreesStr);
|
||||
let minutes;
|
||||
let seconds;
|
||||
if (format === "DMS") {
|
||||
minutes = parseInt(minutesStr, 10);
|
||||
seconds = parseFloat(secondsStr);
|
||||
if (minutes >= 60) {
|
||||
throw new CoordinateParseError("Minutes must be between 0 and 59");
|
||||
}
|
||||
if (seconds >= 60) {
|
||||
throw new CoordinateParseError("Seconds must be between 0 and 59.999...");
|
||||
}
|
||||
}
|
||||
else if (format === "DM") {
|
||||
minutes = parseFloat(minutesStr);
|
||||
if (minutes >= 60) {
|
||||
throw new CoordinateParseError("Minutes must be between 0 and 59.999...");
|
||||
}
|
||||
}
|
||||
const maxDegrees = direction === "N" || direction === "S"
|
||||
? MAX_LATITUDE_DEGREES
|
||||
: MAX_LONGITUDE_DEGREES;
|
||||
if (Math.abs(degrees) > maxDegrees) {
|
||||
throw new CoordinateParseError(`Degrees must be between -${maxDegrees} and ${maxDegrees} for ${direction} direction`);
|
||||
}
|
||||
const coords = {
|
||||
degrees,
|
||||
minutes,
|
||||
seconds,
|
||||
direction,
|
||||
format,
|
||||
remainder: remainder?.trim(),
|
||||
};
|
||||
const decimal = toDecimalDegrees(coords);
|
||||
return {
|
||||
...coords,
|
||||
decimal,
|
||||
};
|
||||
}
|
||||
function toDecimalDegrees(coord) {
|
||||
const degrees = (0, Number_1.toFloat)(coord.degrees) ?? 0;
|
||||
const sign = Math.sign(degrees);
|
||||
let decimal = Math.abs(degrees);
|
||||
decimal += Math.abs((0, Number_1.toFloat)(coord.minutes) ?? 0) / 60.0;
|
||||
decimal += Math.abs((0, Number_1.toFloat)(coord.seconds) ?? 0) / 3600.0;
|
||||
if (coord.direction === "S" || coord.direction === "W" || sign < 0) {
|
||||
decimal = -decimal;
|
||||
}
|
||||
const maxDegrees = coord.direction === "N" || coord.direction === "S"
|
||||
? MAX_LATITUDE_DEGREES
|
||||
: MAX_LONGITUDE_DEGREES;
|
||||
const axis = coord.direction === "N" || coord.direction === "S"
|
||||
? "latitude"
|
||||
: "longitude";
|
||||
if (Math.abs(decimal) > maxDegrees) {
|
||||
throw new CoordinateParseError(`Degrees must be between -${maxDegrees} and ${maxDegrees} for ${axis}`);
|
||||
}
|
||||
// Round to 6 decimal places
|
||||
// Most consumer devices can only resolve 4-5 decimal places (1m resolution)
|
||||
return roundGpsDecimal(decimal);
|
||||
}
|
||||
const MAX_LAT_LON_DIFF = 1;
|
||||
function roundGpsDecimal(decimal) {
|
||||
return (0, Number_1.roundToDecimalPlaces)(decimal, 6);
|
||||
}
|
||||
function parsePosition(position) {
|
||||
if ((0, String_1.blank)(position))
|
||||
return;
|
||||
const [lat, lon] = (0, String_1.toS)(position).split(/[, ]+/).map(Number_1.toFloat);
|
||||
return lat != null && lon != null ? [lat, lon] : undefined;
|
||||
}
|
||||
function processCoordinate(config, warnings) {
|
||||
let { value, ref } = config;
|
||||
const { geoValue, coordinateType } = config;
|
||||
const { expectedRefPositive, expectedRefNegative, max } = config;
|
||||
let isInvalid = false;
|
||||
// Validate ref is reasonable -- it should either start with
|
||||
// expectedRefPositive or expectedRefNegative:
|
||||
ref = (0, String_1.toS)(ref).trim().toUpperCase().slice(0, 1);
|
||||
if (!(0, String_1.blank)(config.ref) &&
|
||||
ref !== expectedRefPositive &&
|
||||
ref !== expectedRefNegative) {
|
||||
warnings.push(`Invalid GPS${coordinateType}Ref: ${JSON.stringify(config.ref)}.`);
|
||||
ref = value < 0 ? expectedRefNegative : expectedRefPositive;
|
||||
}
|
||||
// Check range
|
||||
if (Math.abs(value) > max) {
|
||||
isInvalid = true;
|
||||
warnings.push(`Invalid GPS${coordinateType}: ${value} is out of range`);
|
||||
return { value, ref, isInvalid };
|
||||
}
|
||||
// Apply hemisphere reference
|
||||
if (ref === expectedRefNegative) {
|
||||
value = -Math.abs(value);
|
||||
}
|
||||
// Check for mismatched signs with GeolocationPosition
|
||||
if (geoValue != null &&
|
||||
Math.abs(Math.abs(geoValue) - Math.abs(value)) < MAX_LAT_LON_DIFF) {
|
||||
if (Math.sign(geoValue) !== Math.sign(value)) {
|
||||
value = -value;
|
||||
warnings.push(`Corrected GPS${coordinateType} sign based on GeolocationPosition`);
|
||||
}
|
||||
// Force ref to correct value
|
||||
const expectedRef = geoValue < 0 ? expectedRefNegative : expectedRefPositive;
|
||||
if (ref !== expectedRef) {
|
||||
ref = expectedRef;
|
||||
if (!(0, String_1.blank)(config.ref)) {
|
||||
warnings.push(`Corrected GPS${coordinateType}Ref to ${expectedRef} based on GeolocationPosition`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure ref matches coordinate sign
|
||||
const expectedRef = value < 0 ? expectedRefNegative : expectedRefPositive;
|
||||
if (ref != null && ref !== expectedRef && !(0, String_1.blank)(config.ref)) {
|
||||
warnings.push(`Corrected GPS${coordinateType}Ref to ${ref} to match coordinate sign`);
|
||||
}
|
||||
ref = expectedRef;
|
||||
return { value: roundGpsDecimal(value), ref, isInvalid };
|
||||
}
|
||||
//# sourceMappingURL=CoordinateParser.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"CoordinateParser.js","sourceRoot":"","sources":["../src/CoordinateParser.ts"],"names":[],"mappings":"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 truncated
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { DateTime } from "luxon";
|
||||
import { ExifDate } from "./ExifDate";
|
||||
import { ExifDateTime } from "./ExifDateTime";
|
||||
import { ExifTime } from "./ExifTime";
|
||||
import { Maybe } from "./Maybe";
|
||||
export declare function validDateTime(dt: Maybe<DateTime>): dt is DateTime;
|
||||
export declare const SecondMs = 1000;
|
||||
export declare const MinuteMs: number;
|
||||
export declare const HourMs: number;
|
||||
export declare const DayMs: number;
|
||||
export type DateOrTime = ExifDateTime | ExifDate | ExifTime | DateTime;
|
||||
export declare function isDateOrTime(o: unknown): o is DateOrTime;
|
||||
export declare function dateTimeToExif(d: DateTime, opts?: {
|
||||
includeOffset?: boolean;
|
||||
includeMilliseconds?: boolean;
|
||||
}): string;
|
||||
export declare function toExifString(d: DateOrTime): Maybe<string>;
|
||||
export declare function hms(d: DateTime, opts?: {
|
||||
includeMilliseconds?: boolean;
|
||||
}): string;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DayMs = exports.HourMs = exports.MinuteMs = exports.SecondMs = void 0;
|
||||
exports.validDateTime = validDateTime;
|
||||
exports.isDateOrTime = isDateOrTime;
|
||||
exports.dateTimeToExif = dateTimeToExif;
|
||||
exports.toExifString = toExifString;
|
||||
exports.hms = hms;
|
||||
const luxon_1 = require("luxon");
|
||||
const ExifDate_1 = require("./ExifDate");
|
||||
const ExifDateTime_1 = require("./ExifDateTime");
|
||||
const ExifTime_1 = require("./ExifTime");
|
||||
function validDateTime(dt) {
|
||||
return dt != null && dt.isValid;
|
||||
}
|
||||
exports.SecondMs = 1000;
|
||||
exports.MinuteMs = 60 * exports.SecondMs;
|
||||
exports.HourMs = 60 * exports.MinuteMs;
|
||||
exports.DayMs = 24 * exports.HourMs;
|
||||
function isDateOrTime(o) {
|
||||
return (o instanceof ExifDateTime_1.ExifDateTime ||
|
||||
o instanceof ExifDate_1.ExifDate ||
|
||||
o instanceof ExifTime_1.ExifTime ||
|
||||
luxon_1.DateTime.isDateTime(o));
|
||||
}
|
||||
function dateTimeToExif(d, opts) {
|
||||
return d.toFormat("y:MM:dd HH:mm:ss" +
|
||||
(opts?.includeMilliseconds === true ? ".u" : "") +
|
||||
(opts?.includeOffset === false ? "" : "ZZ"));
|
||||
}
|
||||
function toExifString(d) {
|
||||
if (luxon_1.DateTime.isDateTime(d)) {
|
||||
return dateTimeToExif(d);
|
||||
}
|
||||
else {
|
||||
return d?.toExifString?.();
|
||||
}
|
||||
}
|
||||
function hms(d, opts) {
|
||||
return d.toFormat("HH:mm:ss" + (opts?.includeMilliseconds === true ? ".SSS" : ""));
|
||||
}
|
||||
//# sourceMappingURL=DateTime.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DateTime.js","sourceRoot":"","sources":["../src/DateTime.ts"],"names":[],"mappings":";;;AAMA,sCAEC;AASD,oCAOC;AAED,wCASC;AAED,oCAMC;AAED,kBAOC;AApDD,iCAAiC;AACjC,yCAAsC;AACtC,iDAA8C;AAC9C,yCAAsC;AAGtC,SAAgB,aAAa,CAAC,EAAmB;IAC/C,OAAO,EAAE,IAAI,IAAI,IAAI,EAAE,CAAC,OAAO,CAAC;AAClC,CAAC;AAEY,QAAA,QAAQ,GAAG,IAAI,CAAC;AAChB,QAAA,QAAQ,GAAG,EAAE,GAAG,gBAAQ,CAAC;AACzB,QAAA,MAAM,GAAG,EAAE,GAAG,gBAAQ,CAAC;AACvB,QAAA,KAAK,GAAG,EAAE,GAAG,cAAM,CAAC;AAIjC,SAAgB,YAAY,CAAC,CAAU;IACrC,OAAO,CACL,CAAC,YAAY,2BAAY;QACzB,CAAC,YAAY,mBAAQ;QACrB,CAAC,YAAY,mBAAQ;QACrB,gBAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CACvB,CAAC;AACJ,CAAC;AAED,SAAgB,cAAc,CAC5B,CAAW,EACX,IAAiE;IAEjE,OAAO,CAAC,CAAC,QAAQ,CACf,kBAAkB;QAChB,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,CAAC,IAAI,EAAE,aAAa,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAC9C,CAAC;AACJ,CAAC;AAED,SAAgB,YAAY,CAAC,CAAa;IACxC,IAAI,gBAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3B,OAAO,cAAc,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,EAAE,YAAY,EAAE,EAAE,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,SAAgB,GAAG,CACjB,CAAW,EACX,IAAwC;IAExC,OAAO,CAAC,CAAC,QAAQ,CACf,UAAU,GAAG,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAChE,CAAC;AACJ,CAAC"}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* @deprecated use {@link CapturedAtTagNames} instead.
|
||||
*/
|
||||
export declare const DefaultCreateDateTagNames: readonly ["SubSecDateTimeOriginal", "SubSecCreateDate", "SubSecMediaCreateDate", "DateTimeOriginal", "CreateDate", "MediaCreateDate", "CreationDate", "DateTimeCreated", "TimeCreated"];
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DefaultCreateDateTagNames = void 0;
|
||||
const CapturedAtTagNames_1 = require("./CapturedAtTagNames");
|
||||
/**
|
||||
* @deprecated use {@link CapturedAtTagNames} instead.
|
||||
*/
|
||||
exports.DefaultCreateDateTagNames = CapturedAtTagNames_1.CapturedAtTagNames;
|
||||
//# sourceMappingURL=DefaultCreateDateTagNames.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DefaultCreateDateTagNames.js","sourceRoot":"","sources":["../src/DefaultCreateDateTagNames.ts"],"names":[],"mappings":";;;AAAA,6DAA0D;AAE1D;;GAEG;AACU,QAAA,yBAAyB,GAAG,uCAAkB,CAAC"}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import * as bc from "batch-cluster";
|
||||
import { ExifToolOptions } from "./ExifToolOptions";
|
||||
import { Omit } from "./Omit";
|
||||
import { Tags } from "./Tags";
|
||||
export declare const ConsoleLogger: bc.Logger;
|
||||
/**
|
||||
* Default values for `ExifToolOptions`, except for `processFactory` (which is
|
||||
* created by the ExifTool constructor)
|
||||
*/
|
||||
export declare const DefaultExifToolOptions: Omit<ExifToolOptions, "processFactory" | "ignoreShebang">;
|
||||
/**
|
||||
* @see https://github.com/photostructure/exiftool-vendored.js/issues/215
|
||||
*/
|
||||
export declare function defaultAdjustTimeZoneIfDaylightSavings(t: Tags): number | undefined;
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
"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.DefaultExifToolOptions = exports.ConsoleLogger = void 0;
|
||||
exports.defaultAdjustTimeZoneIfDaylightSavings = defaultAdjustTimeZoneIfDaylightSavings;
|
||||
const bc = __importStar(require("batch-cluster"));
|
||||
const node_util_1 = require("node:util");
|
||||
const Boolean_1 = require("./Boolean");
|
||||
const CapturedAtTagNames_1 = require("./CapturedAtTagNames");
|
||||
const DefaultExiftoolArgs_1 = require("./DefaultExiftoolArgs");
|
||||
const DefaultMaxProcs_1 = require("./DefaultMaxProcs");
|
||||
const ExiftoolPath_1 = require("./ExiftoolPath");
|
||||
const GeoTz_1 = require("./GeoTz");
|
||||
const IsWin32_1 = require("./IsWin32");
|
||||
const VersionTask_1 = require("./VersionTask");
|
||||
const _debuglog = (0, node_util_1.debuglog)("exiftool-vendored");
|
||||
function noop() { }
|
||||
exports.ConsoleLogger = {
|
||||
trace: noop,
|
||||
debug: _debuglog,
|
||||
info: _debuglog,
|
||||
warn: console.warn,
|
||||
error: console.error,
|
||||
};
|
||||
function logger() {
|
||||
return (0, node_util_1.debuglog)("exiftool-vendored").enabled ? exports.ConsoleLogger : bc.NoLogger;
|
||||
}
|
||||
/**
|
||||
* Default values for `ExifToolOptions`, except for `processFactory` (which is
|
||||
* created by the ExifTool constructor)
|
||||
*/
|
||||
exports.DefaultExifToolOptions = Object.freeze({
|
||||
...new bc.BatchClusterOptions(),
|
||||
maxProcs: DefaultMaxProcs_1.DefaultMaxProcs,
|
||||
maxTasksPerProcess: 500,
|
||||
spawnTimeoutMillis: 30000,
|
||||
streamFlushMillis: 10,
|
||||
// see https://github.com/photostructure/exiftool-vendored.js/issues/34 :
|
||||
taskTimeoutMillis: 20000,
|
||||
onIdleIntervalMillis: 2000,
|
||||
taskRetries: 1,
|
||||
exiftoolPath: ExiftoolPath_1.exiftoolPath,
|
||||
exiftoolArgs: DefaultExiftoolArgs_1.DefaultExiftoolArgs,
|
||||
exiftoolEnv: {},
|
||||
checkPerl: !(0, IsWin32_1.isWin32)(),
|
||||
pass: "{ready}",
|
||||
fail: "{ready}",
|
||||
exitCommand: "-stay_open\nFalse\n",
|
||||
versionCommand: new VersionTask_1.VersionTask().command,
|
||||
healthCheckIntervalMillis: 30000,
|
||||
healthCheckCommand: "-ver\n-execute\n",
|
||||
backfillTimezones: true,
|
||||
defaultVideosToUTC: true,
|
||||
geoTz: GeoTz_1.geoTz,
|
||||
geolocation: false,
|
||||
ignoreZeroZeroLatLon: true,
|
||||
ignoreMinorErrors: true,
|
||||
forceWrite: false,
|
||||
imageHashType: false,
|
||||
includeImageDataMD5: undefined,
|
||||
inferTimezoneFromDatestamps: false, // to retain prior behavior
|
||||
inferTimezoneFromDatestampTags: [...CapturedAtTagNames_1.CapturedAtTagNames],
|
||||
inferTimezoneFromTimeStamp: false, // to retain prior behavior
|
||||
logger,
|
||||
numericTags: [
|
||||
"*Duration*",
|
||||
"GPSAltitude",
|
||||
"GPSLatitude",
|
||||
"GPSLongitude",
|
||||
"GPSPosition",
|
||||
"GeolocationPosition",
|
||||
"Orientation",
|
||||
// NOT Rotation! Rotation can be encoded as degrees rotated clockwise, or a
|
||||
// EXIF-Orientation string (!!). If we ask ExifTool for numeric rotations of HEICs,
|
||||
// we get "3" (when it means "Rotate 90 CW"):
|
||||
// $ exiftool -j -Rotation -Orientation IMG_6947.HEIC
|
||||
// [{
|
||||
// "Rotation": "Rotate 90 CW",
|
||||
// "Orientation": "Rotate 90 CW"
|
||||
// }]
|
||||
// $ exiftool -j -Rotation# -Orientation# IMG_6947.HEIC
|
||||
// [{
|
||||
// "Rotation": 3, // < WTH is this? 3 means 180º (!?)
|
||||
// "Orientation": 6 // < expected
|
||||
// }]
|
||||
],
|
||||
useMWG: false,
|
||||
struct: 1,
|
||||
readArgs: ["-fast"],
|
||||
writeArgs: [],
|
||||
adjustTimeZoneIfDaylightSavings: defaultAdjustTimeZoneIfDaylightSavings,
|
||||
preferTimezoneInferenceFromGps: false, // to retain prior behavior
|
||||
keepUTCTime: true,
|
||||
});
|
||||
/**
|
||||
* @see https://github.com/photostructure/exiftool-vendored.js/issues/215
|
||||
*/
|
||||
function defaultAdjustTimeZoneIfDaylightSavings(t) {
|
||||
// `DaylightSavings` may be "Yes" or `true`:
|
||||
return true === (0, Boolean_1.toBoolean)(t.DaylightSavings) &&
|
||||
// Daggum Nikon likes "FS-Nikon", "Nikon", "NIKON", and "NIKON CORPORATION"
|
||||
/\bnikon\b/i.test(String(t.Make))
|
||||
? 60
|
||||
: undefined;
|
||||
}
|
||||
//# sourceMappingURL=DefaultExifToolOptions.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DefaultExifToolOptions.js","sourceRoot":"","sources":["../src/DefaultExifToolOptions.ts"],"names":[],"mappings":"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"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare const DefaultExiftoolArgs: string[];
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DefaultExiftoolArgs = void 0;
|
||||
// -stay_open True -@ -
|
||||
exports.DefaultExiftoolArgs = ["-stay_open", "True", "-@", "-"];
|
||||
//# sourceMappingURL=DefaultExiftoolArgs.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DefaultExiftoolArgs.js","sourceRoot":"","sources":["../src/DefaultExiftoolArgs.ts"],"names":[],"mappings":";;;AAAA,uBAAuB;AACV,QAAA,mBAAmB,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare const DefaultMaxProcs: number;
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
"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.DefaultMaxProcs = void 0;
|
||||
const _os = __importStar(require("node:os"));
|
||||
const Object_1 = require("./Object");
|
||||
function maxCpus() {
|
||||
// https://nodejs.org/api/os.html#osavailableparallelism was added in node
|
||||
// 18.14, but people may still be running archaic versions of node.
|
||||
return Math.max(1, (0, Object_1.isFunction)(_os.availableParallelism)
|
||||
? _os.availableParallelism()
|
||||
: _os.cpus().length);
|
||||
}
|
||||
exports.DefaultMaxProcs = Math.ceil(maxCpus() / 4);
|
||||
//# sourceMappingURL=DefaultMaxProcs.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DefaultMaxProcs.js","sourceRoot":"","sources":["../src/DefaultMaxProcs.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAA+B;AAC/B,qCAAsC;AAEtC,SAAS,OAAO;IACd,0EAA0E;IAC1E,mEAAmE;IAEnE,OAAO,IAAI,CAAC,GAAG,CACb,CAAC,EACD,IAAA,mBAAU,EAAC,GAAG,CAAC,oBAAoB,CAAC;QAClC,CAAC,CAAC,GAAG,CAAC,oBAAoB,EAAE;QAC5B,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,CACtB,CAAC;AACJ,CAAC;AAEY,QAAA,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC"}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type Defined<T> = T extends undefined ? never : T;
|
||||
export type DefinedOrNullValued<T> = {
|
||||
[P in keyof T]: Defined<T[P]> | null;
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//# sourceMappingURL=Defined.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Defined.js","sourceRoot":"","sources":["../src/Defined.ts"],"names":[],"mappings":""}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export declare const DeleteAllTagsArgs: string[];
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.DeleteAllTagsArgs = void 0;
|
||||
exports.DeleteAllTagsArgs = ["-all="];
|
||||
//# sourceMappingURL=DeleteAllTagsArgs.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"DeleteAllTagsArgs.js","sourceRoot":"","sources":["../src/DeleteAllTagsArgs.ts"],"names":[],"mappings":";;;AAAa,QAAA,iBAAiB,GAAG,CAAC,OAAO,CAAC,CAAC"}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ExifToolTask } from "./ExifToolTask";
|
||||
export interface ErrorsAndWarnings {
|
||||
/**
|
||||
* This is a list of all critical errors raised by ExifTool during the read
|
||||
* process.
|
||||
*/
|
||||
errors?: string[];
|
||||
/**
|
||||
* This is a list of all non-critical errors raised by ExifTool during the
|
||||
* read process.
|
||||
*
|
||||
* Invalid field values are considered warnings and not errors, for example.
|
||||
*/
|
||||
warnings?: string[];
|
||||
}
|
||||
export declare function errorsAndWarnings(task: ExifToolTask<unknown>, t?: {
|
||||
Error?: string;
|
||||
Warning?: string;
|
||||
}): Required<ErrorsAndWarnings>;
|
||||
/**
|
||||
* Convert an unknown value to an Error.
|
||||
*/
|
||||
export declare function toError(e: unknown, messageIfBlank?: string): Error;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.errorsAndWarnings = errorsAndWarnings;
|
||||
exports.toError = toError;
|
||||
const Array_1 = require("./Array");
|
||||
const String_1 = require("./String");
|
||||
function errorsAndWarnings(task, t) {
|
||||
return {
|
||||
errors: (0, Array_1.uniq)((0, String_1.compactBlanks)([t?.Error, ...task.errors])),
|
||||
warnings: (0, Array_1.uniq)((0, String_1.compactBlanks)([t?.Warning, ...task.warnings])),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Convert an unknown value to an Error.
|
||||
*/
|
||||
function toError(e, messageIfBlank = "Unknown error") {
|
||||
return e instanceof Error
|
||||
? e
|
||||
: new Error((0, String_1.toNotBlank)((0, String_1.toS)(e)) ?? messageIfBlank);
|
||||
}
|
||||
//# sourceMappingURL=ErrorsAndWarnings.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ErrorsAndWarnings.js","sourceRoot":"","sources":["../src/ErrorsAndWarnings.ts"],"names":[],"mappings":";;AAoBA,8CAQC;AAKD,0BAIC;AArCD,mCAA+B;AAE/B,qCAA0D;AAkB1D,SAAgB,iBAAiB,CAC/B,IAA2B,EAC3B,CAAwC;IAExC,OAAO;QACL,MAAM,EAAE,IAAA,YAAI,EAAC,IAAA,sBAAa,EAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACvD,QAAQ,EAAE,IAAA,YAAI,EAAC,IAAA,sBAAa,EAAC,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;KAC9D,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAgB,OAAO,CAAC,CAAU,EAAE,cAAc,GAAG,eAAe;IAClE,OAAO,CAAC,YAAY,KAAK;QACvB,CAAC,CAAC,CAAC;QACH,CAAC,CAAC,IAAI,KAAK,CAAC,IAAA,mBAAU,EAAC,IAAA,YAAG,EAAC,CAAC,CAAC,CAAC,IAAI,cAAc,CAAC,CAAC;AACtD,CAAC"}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { DateTime } from "luxon";
|
||||
import { Maybe } from "./Maybe";
|
||||
/**
|
||||
* Encodes an ExifDate
|
||||
*/
|
||||
export declare class ExifDate {
|
||||
readonly year: number;
|
||||
readonly month: number;
|
||||
readonly day: number;
|
||||
readonly rawValue?: string | undefined;
|
||||
static from(exifOrIso: string): Maybe<ExifDate>;
|
||||
static fromISO(text: string): Maybe<ExifDate>;
|
||||
private static fromPatterns;
|
||||
static fromExifStrict(text: string): Maybe<ExifDate>;
|
||||
static fromExifLoose(text: string): Maybe<ExifDate>;
|
||||
static fromEXIF(text: string): Maybe<ExifDate>;
|
||||
static fromDateTime(dt: DateTime, rawValue?: string): Maybe<ExifDate>;
|
||||
constructor(year: number, // full year (probably 2019-ish, but maybe Japanese 30-ish). See https://ericasadun.com/2018/12/25/iso-8601-yyyy-yyyy-and-why-your-year-may-be-wrong/
|
||||
month: number, // 1-12, (no crazy 0-11 nonsense from Date!)
|
||||
day: number, // 1-31
|
||||
rawValue?: string | undefined);
|
||||
toDate(): Date;
|
||||
/**
|
||||
* @param deltaMs defaults to 12 hours, so toMillis() is in the middle of the day.
|
||||
*
|
||||
* @return the epoch milliseconds for this day in UTC, plus `deltaMs` milliseconds.
|
||||
*/
|
||||
toMillis(deltaMs?: number): number;
|
||||
toISOString(): string;
|
||||
toExifString(): string;
|
||||
toString(sep?: string): string;
|
||||
toJSON(): {
|
||||
_ctor: string;
|
||||
year: number;
|
||||
month: number;
|
||||
day: number;
|
||||
rawValue: string | undefined;
|
||||
};
|
||||
static fromJSON(json: ReturnType<ExifDate["toJSON"]>): ExifDate;
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ExifDate = void 0;
|
||||
const luxon_1 = require("luxon");
|
||||
const DateTime_1 = require("./DateTime");
|
||||
const Maybe_1 = require("./Maybe");
|
||||
const String_1 = require("./String");
|
||||
const StrictExifRE = /^\d{1,4}:\d{1,2}:\d{1,2}|\d{1,4}-\d{1,2}-\d{1,2}$/;
|
||||
const LooseExifRE = /^\S+\s+\S+\s+\S+$/;
|
||||
/**
|
||||
* Encodes an ExifDate
|
||||
*/
|
||||
class ExifDate {
|
||||
year;
|
||||
month;
|
||||
day;
|
||||
rawValue;
|
||||
static from(exifOrIso) {
|
||||
return (
|
||||
// in order of strictness:
|
||||
this.fromExifStrict(exifOrIso) ??
|
||||
this.fromISO(exifOrIso) ??
|
||||
this.fromExifLoose(exifOrIso));
|
||||
}
|
||||
static fromISO(text) {
|
||||
return StrictExifRE.test((0, String_1.toS)(text).trim())
|
||||
? this.fromDateTime(luxon_1.DateTime.fromISO(text), text)
|
||||
: undefined;
|
||||
}
|
||||
static fromPatterns(text, fmts) {
|
||||
if ((0, String_1.blank)(text))
|
||||
return;
|
||||
text = (0, String_1.toS)(text).trim();
|
||||
for (const fmt of fmts) {
|
||||
const dt = luxon_1.DateTime.fromFormat(text, fmt);
|
||||
if ((0, DateTime_1.validDateTime)(dt)) {
|
||||
return this.fromDateTime(dt, text);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// These are all formats I've seen in the wild from exiftool's output.
|
||||
// More iterations might make sense, like "d MMM, y" or "MMM d, y", but I
|
||||
// want to be constrained in what I consider a valid date to lessen the
|
||||
// chance of misinterpreting a given value.
|
||||
static fromExifStrict(text) {
|
||||
return StrictExifRE.test((0, String_1.toS)(text).trim())
|
||||
? this.fromPatterns(text, ["y:MM:dd", "y-MM-dd", "y:M:d"])
|
||||
: undefined;
|
||||
}
|
||||
static fromExifLoose(text) {
|
||||
// Unfortunately, Luxon parses "00" and "01" as _today_. So if we don't
|
||||
// three non-blank strings parts, reject.
|
||||
return LooseExifRE.test((0, String_1.toS)(text).trim())
|
||||
? this.fromPatterns(text, ["MMM d y", "MMMM d y"])
|
||||
: undefined;
|
||||
}
|
||||
static fromEXIF(text) {
|
||||
return (0, Maybe_1.firstDefinedThunk)([
|
||||
() => this.fromExifStrict(text),
|
||||
() => this.fromExifLoose(text),
|
||||
]);
|
||||
}
|
||||
static fromDateTime(dt, rawValue) {
|
||||
return (0, DateTime_1.validDateTime)(dt)
|
||||
? new ExifDate(dt.year, dt.month, dt.day, rawValue)
|
||||
: undefined;
|
||||
}
|
||||
constructor(year, // full year (probably 2019-ish, but maybe Japanese 30-ish). See https://ericasadun.com/2018/12/25/iso-8601-yyyy-yyyy-and-why-your-year-may-be-wrong/
|
||||
month, // 1-12, (no crazy 0-11 nonsense from Date!)
|
||||
day, // 1-31
|
||||
rawValue) {
|
||||
this.year = year;
|
||||
this.month = month;
|
||||
this.day = day;
|
||||
this.rawValue = rawValue;
|
||||
}
|
||||
toDate() {
|
||||
return new Date(this.year, this.month - 1, this.day);
|
||||
}
|
||||
/**
|
||||
* @param deltaMs defaults to 12 hours, so toMillis() is in the middle of the day.
|
||||
*
|
||||
* @return the epoch milliseconds for this day in UTC, plus `deltaMs` milliseconds.
|
||||
*/
|
||||
toMillis(deltaMs = 12 * DateTime_1.HourMs) {
|
||||
return this.toDate().getTime() + deltaMs;
|
||||
}
|
||||
toISOString() {
|
||||
return this.toString("-");
|
||||
}
|
||||
toExifString() {
|
||||
return this.toString(":");
|
||||
}
|
||||
toString(sep = "-") {
|
||||
return `${this.year}${sep}${(0, String_1.pad2)(this.month, this.day).join(sep)}`;
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
_ctor: "ExifDate",
|
||||
year: this.year,
|
||||
month: this.month,
|
||||
day: this.day,
|
||||
rawValue: this.rawValue,
|
||||
};
|
||||
}
|
||||
static fromJSON(json) {
|
||||
return new ExifDate(json.year, json.month, json.day, json.rawValue);
|
||||
}
|
||||
}
|
||||
exports.ExifDate = ExifDate;
|
||||
//# sourceMappingURL=ExifDate.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExifDate.js","sourceRoot":"","sources":["../src/ExifDate.ts"],"names":[],"mappings":"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"}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import { DateTime, DateTimeJSOptions, DurationLike, ToISOTimeOptions, Zone, ZoneOptions } from "luxon";
|
||||
import { Maybe } from "./Maybe";
|
||||
/**
|
||||
* Encapsulates encoding and decoding EXIF date and time strings,
|
||||
* along with timezone handling functionality.
|
||||
*
|
||||
* Key features:
|
||||
* - Parses datetime strings in various formats (EXIF strict/loose, ISO)
|
||||
* - Supports timezone inference, conversion, and matching between dates
|
||||
* - Preserves original string representations when available
|
||||
* - Provides conversion to/from multiple datetime formats (Luxon DateTime, JS
|
||||
* Date, ISO strings)
|
||||
* - Supports serialization/deserialization via JSON (see
|
||||
* {@link ExifDateTime.fromJSON})
|
||||
*
|
||||
* EXIF datetime strings don't typically include timezone information. This
|
||||
* class provides mechanisms to associate timezone data from other EXIF tags
|
||||
* (like GPS position or timezone offset), and distinguishes between explicitly
|
||||
* set and inferred timezone information.
|
||||
*/
|
||||
export declare class ExifDateTime {
|
||||
#private;
|
||||
readonly year: number;
|
||||
readonly month: number;
|
||||
readonly day: number;
|
||||
readonly hour: number;
|
||||
readonly minute: number;
|
||||
readonly second: number;
|
||||
readonly millisecond?: number | undefined;
|
||||
readonly tzoffsetMinutes?: number | undefined;
|
||||
readonly rawValue?: string | undefined;
|
||||
readonly zoneName?: string | undefined;
|
||||
readonly inferredZone?: boolean | undefined;
|
||||
static from(exifOrIso: Maybe<string | ExifDateTime>, defaultZone?: Maybe<string>): Maybe<ExifDateTime>;
|
||||
static fromISO(iso: string, defaultZone?: Maybe<string>): Maybe<ExifDateTime>;
|
||||
/**
|
||||
* Try to parse a date-time string from EXIF. If there is not both a date
|
||||
* and a time component, returns `undefined`.
|
||||
*
|
||||
* @param text from EXIF metadata
|
||||
* @param defaultZone a "zone name" to use as a backstop, or default, if
|
||||
* `text` doesn't specify a zone. This may be IANA-formatted, like
|
||||
* "America/Los_Angeles", or an offset, like "UTC-3". See
|
||||
* `offsetMinutesToZoneName`.
|
||||
*/
|
||||
static fromEXIF(text: string, defaultZone?: Maybe<string>): Maybe<ExifDateTime>;
|
||||
/**
|
||||
* Parse the given date-time string, EXIF-formatted.
|
||||
*
|
||||
* @param text from EXIF metadata, in `y:M:d H:m:s` format (with optional
|
||||
* sub-seconds and/or timezone)
|
||||
|
||||
* @param defaultZone a "zone name" to use as a backstop, or default, if
|
||||
* `text` doesn't specify a zone. This may be IANA-formatted, like
|
||||
* "America/Los_Angeles", or an offset, like "UTC-3". See
|
||||
* `offsetMinutesToZoneName`.
|
||||
*/
|
||||
static fromExifStrict(text: unknown, defaultZone?: Maybe<string>): Maybe<ExifDateTime>;
|
||||
static fromExifLoose(text: unknown, defaultZone?: Maybe<string>): Maybe<ExifDateTime>;
|
||||
static fromDateTime(dt: Maybe<DateTime>, opts?: {
|
||||
rawValue?: Maybe<string>;
|
||||
unsetMilliseconds?: boolean;
|
||||
inferredZone?: Maybe<boolean>;
|
||||
}): Maybe<ExifDateTime>;
|
||||
/**
|
||||
* Create an ExifDateTime from a number of milliseconds since the epoch
|
||||
* (meaning since 1 January 1970 00:00:00 UTC). Uses the default zone.
|
||||
*
|
||||
* @param millis - a number of milliseconds since 1970 UTC
|
||||
*
|
||||
* @param options.rawValue - the original parsed string input
|
||||
* @param options.zone - the zone to place the DateTime into. Defaults to 'local'.
|
||||
* @param options.locale - a locale to set on the resulting DateTime instance
|
||||
* @param options.outputCalendar - the output calendar to set on the resulting DateTime instance
|
||||
* @param options.numberingSystem - the numbering system to set on the resulting DateTime instance
|
||||
*/
|
||||
static fromMillis(millis: number, options?: DateTimeJSOptions & {
|
||||
rawValue?: string;
|
||||
}): ExifDateTime;
|
||||
static now(opts?: DateTimeJSOptions & {
|
||||
rawValue?: string;
|
||||
}): ExifDateTime;
|
||||
readonly zone: Maybe<string>;
|
||||
constructor(year: number, month: number, day: number, hour: number, minute: number, second: number, millisecond?: number | undefined, tzoffsetMinutes?: number | undefined, rawValue?: string | undefined, zoneName?: string | undefined, inferredZone?: boolean | undefined);
|
||||
get millis(): number | undefined;
|
||||
get hasZone(): boolean;
|
||||
get unsetMilliseconds(): boolean;
|
||||
setZone(zone: string | Zone, opts?: ZoneOptions & {
|
||||
inferredZone?: boolean;
|
||||
}): Maybe<ExifDateTime>;
|
||||
/**
|
||||
* CAUTION: This instance will inherit the system timezone if this instance
|
||||
* has an unset zone (as Luxon doesn't support "unset" timezones)
|
||||
*/
|
||||
toDateTime(overrideZone?: Maybe<string>): DateTime;
|
||||
toEpochSeconds(overrideZone?: Maybe<string>): number;
|
||||
toDate(): Date;
|
||||
toISOString(options?: ToISOTimeOptions): Maybe<string>;
|
||||
toExifString(): string;
|
||||
toString(): Maybe<string>;
|
||||
/**
|
||||
* @return the epoch milliseconds of this
|
||||
*/
|
||||
toMillis(): number;
|
||||
get isValid(): boolean;
|
||||
toJSON(): {
|
||||
_ctor: string;
|
||||
year: number;
|
||||
month: number;
|
||||
day: number;
|
||||
hour: number;
|
||||
minute: number;
|
||||
second: number;
|
||||
millisecond: number | undefined;
|
||||
tzoffsetMinutes: number | undefined;
|
||||
rawValue: string | undefined;
|
||||
zoneName: string | undefined;
|
||||
inferredZone: boolean | undefined;
|
||||
};
|
||||
/**
|
||||
* @return a new ExifDateTime from the given JSON. Note that this instance **may not be valid**.
|
||||
*/
|
||||
static fromJSON(json: Omit<ReturnType<ExifDateTime["toJSON"]>, "_ctor">): ExifDateTime;
|
||||
maybeMatchZone(target: ExifDateTime, maxDeltaMs?: number): Maybe<ExifDateTime>;
|
||||
private ifClose;
|
||||
plus(duration: DurationLike): Maybe<ExifDateTime>;
|
||||
}
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ExifDateTime = void 0;
|
||||
const luxon_1 = require("luxon");
|
||||
const DateTime_1 = require("./DateTime");
|
||||
const Maybe_1 = require("./Maybe");
|
||||
const Object_1 = require("./Object");
|
||||
const String_1 = require("./String");
|
||||
const TimeParsing_1 = require("./TimeParsing");
|
||||
const Timezones_1 = require("./Timezones");
|
||||
/**
|
||||
* Encapsulates encoding and decoding EXIF date and time strings,
|
||||
* along with timezone handling functionality.
|
||||
*
|
||||
* Key features:
|
||||
* - Parses datetime strings in various formats (EXIF strict/loose, ISO)
|
||||
* - Supports timezone inference, conversion, and matching between dates
|
||||
* - Preserves original string representations when available
|
||||
* - Provides conversion to/from multiple datetime formats (Luxon DateTime, JS
|
||||
* Date, ISO strings)
|
||||
* - Supports serialization/deserialization via JSON (see
|
||||
* {@link ExifDateTime.fromJSON})
|
||||
*
|
||||
* EXIF datetime strings don't typically include timezone information. This
|
||||
* class provides mechanisms to associate timezone data from other EXIF tags
|
||||
* (like GPS position or timezone offset), and distinguishes between explicitly
|
||||
* set and inferred timezone information.
|
||||
*/
|
||||
class ExifDateTime {
|
||||
year;
|
||||
month;
|
||||
day;
|
||||
hour;
|
||||
minute;
|
||||
second;
|
||||
millisecond;
|
||||
tzoffsetMinutes;
|
||||
rawValue;
|
||||
zoneName;
|
||||
inferredZone;
|
||||
static from(exifOrIso, defaultZone) {
|
||||
return exifOrIso instanceof ExifDateTime
|
||||
? exifOrIso // already an ExifDateTime
|
||||
: (0, String_1.blank)(exifOrIso)
|
||||
? undefined // in order of strictness:
|
||||
: (this.fromExifStrict(exifOrIso, defaultZone) ??
|
||||
this.fromISO(exifOrIso, defaultZone) ??
|
||||
this.fromExifLoose(exifOrIso, defaultZone));
|
||||
}
|
||||
static fromISO(iso, defaultZone) {
|
||||
if ((0, String_1.blank)(iso) || null != iso.match(/^\d+$/))
|
||||
return undefined;
|
||||
// Unfortunately, DateTime.fromISO() is happy to parse a date with no time,
|
||||
// so we have to do this ourselves:
|
||||
return this.#fromPatterns(iso, (0, TimeParsing_1.timeFormats)({
|
||||
formatPrefixes: ["y-MM-dd'T'", "y-MM-dd ", "y-M-d "],
|
||||
defaultZone,
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Try to parse a date-time string from EXIF. If there is not both a date
|
||||
* and a time component, returns `undefined`.
|
||||
*
|
||||
* @param text from EXIF metadata
|
||||
* @param defaultZone a "zone name" to use as a backstop, or default, if
|
||||
* `text` doesn't specify a zone. This may be IANA-formatted, like
|
||||
* "America/Los_Angeles", or an offset, like "UTC-3". See
|
||||
* `offsetMinutesToZoneName`.
|
||||
*/
|
||||
static fromEXIF(text, defaultZone) {
|
||||
if ((0, String_1.blank)(text))
|
||||
return undefined;
|
||||
return (
|
||||
// .fromExifStrict() uses .fromISO() as a backstop
|
||||
this.fromExifStrict(text, defaultZone) ??
|
||||
this.fromExifLoose(text, defaultZone));
|
||||
}
|
||||
static #fromPatterns(text, fmts) {
|
||||
const result = (0, TimeParsing_1.parseDateTime)(text, fmts);
|
||||
return result == null
|
||||
? undefined
|
||||
: ExifDateTime.fromDateTime(result.dt, {
|
||||
rawValue: text,
|
||||
unsetMilliseconds: result.unsetMilliseconds,
|
||||
inferredZone: result.inferredZone,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Parse the given date-time string, EXIF-formatted.
|
||||
*
|
||||
* @param text from EXIF metadata, in `y:M:d H:m:s` format (with optional
|
||||
* sub-seconds and/or timezone)
|
||||
|
||||
* @param defaultZone a "zone name" to use as a backstop, or default, if
|
||||
* `text` doesn't specify a zone. This may be IANA-formatted, like
|
||||
* "America/Los_Angeles", or an offset, like "UTC-3". See
|
||||
* `offsetMinutesToZoneName`.
|
||||
*/
|
||||
static fromExifStrict(text, defaultZone) {
|
||||
if ((0, String_1.blank)(text) || !(0, String_1.isString)(text))
|
||||
return undefined;
|
||||
return (this.#fromPatterns(text, (0, TimeParsing_1.timeFormats)({ formatPrefixes: ["y:MM:dd ", "y:M:d "], defaultZone })) ??
|
||||
// Not found yet? Maybe it's in ISO format? See
|
||||
// https://github.com/photostructure/exiftool-vendored.js/issues/71
|
||||
this.fromISO(text, defaultZone));
|
||||
}
|
||||
static *#looseExifFormats(defaultZone) {
|
||||
// The following are from actual datestamps seen in the wild (!!)
|
||||
const formats = [
|
||||
"MMM d y HH:mm:ss",
|
||||
"MMM d y, HH:mm:ss",
|
||||
// Thu Oct 13 00:12:27 2016:
|
||||
"ccc MMM d HH:mm:ss y",
|
||||
];
|
||||
const zone = (0, String_1.notBlank)(defaultZone) ? defaultZone : Timezones_1.UnsetZone;
|
||||
for (const fmt of formats) {
|
||||
yield { fmt: fmt, zone, inferredZone: true };
|
||||
}
|
||||
}
|
||||
static fromExifLoose(text, defaultZone) {
|
||||
return (0, String_1.blank)(text) || !(0, String_1.isString)(text)
|
||||
? undefined
|
||||
: this.#fromPatterns(text, this.#looseExifFormats(defaultZone));
|
||||
}
|
||||
static fromDateTime(dt, opts) {
|
||||
if (dt == null || !dt.isValid || dt.year === 0 || dt.year === 1) {
|
||||
return undefined;
|
||||
}
|
||||
return new ExifDateTime(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, dt.millisecond === 0 && true === opts?.unsetMilliseconds
|
||||
? undefined
|
||||
: dt.millisecond, dt.offset === Timezones_1.UnsetZoneOffsetMinutes ? undefined : dt.offset, opts?.rawValue, dt.zoneName == null || dt.zone?.name === Timezones_1.UnsetZone.name
|
||||
? undefined
|
||||
: dt.zoneName, opts?.inferredZone);
|
||||
}
|
||||
/**
|
||||
* Create an ExifDateTime from a number of milliseconds since the epoch
|
||||
* (meaning since 1 January 1970 00:00:00 UTC). Uses the default zone.
|
||||
*
|
||||
* @param millis - a number of milliseconds since 1970 UTC
|
||||
*
|
||||
* @param options.rawValue - the original parsed string input
|
||||
* @param options.zone - the zone to place the DateTime into. Defaults to 'local'.
|
||||
* @param options.locale - a locale to set on the resulting DateTime instance
|
||||
* @param options.outputCalendar - the output calendar to set on the resulting DateTime instance
|
||||
* @param options.numberingSystem - the numbering system to set on the resulting DateTime instance
|
||||
*/
|
||||
static fromMillis(millis, options = {}) {
|
||||
if (options.zone == null ||
|
||||
[Timezones_1.UnsetZoneName, Timezones_1.UnsetZone].includes(options.zone)) {
|
||||
delete options.zone;
|
||||
}
|
||||
let dt = luxon_1.DateTime.fromMillis(millis, {
|
||||
...(0, Object_1.omit)(options, "rawValue"),
|
||||
});
|
||||
if (options.zone == null) {
|
||||
dt = dt.setZone(Timezones_1.UnsetZone, { keepLocalTime: true });
|
||||
}
|
||||
// TODO: is there a way to provide an invalid millisecond value?
|
||||
return this.fromDateTime(dt, { rawValue: options.rawValue });
|
||||
}
|
||||
static now(opts = {}) {
|
||||
return this.fromMillis(Date.now(), opts);
|
||||
}
|
||||
#dt;
|
||||
zone;
|
||||
constructor(year, month, day, hour, minute, second, millisecond, tzoffsetMinutes, rawValue, zoneName, inferredZone) {
|
||||
this.year = year;
|
||||
this.month = month;
|
||||
this.day = day;
|
||||
this.hour = hour;
|
||||
this.minute = minute;
|
||||
this.second = second;
|
||||
this.millisecond = millisecond;
|
||||
this.tzoffsetMinutes = tzoffsetMinutes;
|
||||
this.rawValue = rawValue;
|
||||
this.zoneName = zoneName;
|
||||
this.inferredZone = inferredZone;
|
||||
this.zone = (0, Timezones_1.getZoneName)({ zoneName, tzoffsetMinutes });
|
||||
}
|
||||
get millis() {
|
||||
return this.millisecond;
|
||||
}
|
||||
get hasZone() {
|
||||
return this.zone != null;
|
||||
}
|
||||
get unsetMilliseconds() {
|
||||
return this.millisecond == null;
|
||||
}
|
||||
setZone(zone, opts) {
|
||||
const dt = (0, TimeParsing_1.setZone)({
|
||||
zone,
|
||||
src: this.toDateTime(),
|
||||
srcHasZone: this.hasZone,
|
||||
opts,
|
||||
});
|
||||
return ExifDateTime.fromDateTime(dt, {
|
||||
rawValue: this.rawValue,
|
||||
unsetMilliseconds: this.millisecond == null,
|
||||
inferredZone: opts?.inferredZone ?? true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* CAUTION: This instance will inherit the system timezone if this instance
|
||||
* has an unset zone (as Luxon doesn't support "unset" timezones)
|
||||
*/
|
||||
toDateTime(overrideZone) {
|
||||
return (this.#dt ??= luxon_1.DateTime.fromObject({
|
||||
year: this.year,
|
||||
month: this.month,
|
||||
day: this.day,
|
||||
hour: this.hour,
|
||||
minute: this.minute,
|
||||
second: this.second,
|
||||
millisecond: this.millisecond,
|
||||
}, {
|
||||
zone: overrideZone ?? this.zone,
|
||||
}));
|
||||
}
|
||||
toEpochSeconds(overrideZone) {
|
||||
return this.toDateTime(overrideZone).toUnixInteger();
|
||||
}
|
||||
toDate() {
|
||||
return this.toDateTime().toJSDate();
|
||||
}
|
||||
toISOString(options = {}) {
|
||||
return (0, Maybe_1.denull)(this.toDateTime().toISO({
|
||||
suppressMilliseconds: options.suppressMilliseconds ?? this.millisecond == null,
|
||||
includeOffset: this.hasZone && options.includeOffset !== false,
|
||||
}));
|
||||
}
|
||||
toExifString() {
|
||||
return (0, DateTime_1.dateTimeToExif)(this.toDateTime(), {
|
||||
includeOffset: this.hasZone,
|
||||
includeMilliseconds: this.millisecond != null,
|
||||
});
|
||||
}
|
||||
toString() {
|
||||
return this.toISOString();
|
||||
}
|
||||
/**
|
||||
* @return the epoch milliseconds of this
|
||||
*/
|
||||
toMillis() {
|
||||
return this.toDateTime().toMillis();
|
||||
}
|
||||
get isValid() {
|
||||
return this.toDateTime().isValid;
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
_ctor: "ExifDateTime", // < magick field used by the JSON parser
|
||||
year: this.year,
|
||||
month: this.month,
|
||||
day: this.day,
|
||||
hour: this.hour,
|
||||
minute: this.minute,
|
||||
second: this.second,
|
||||
millisecond: this.millisecond,
|
||||
tzoffsetMinutes: this.tzoffsetMinutes,
|
||||
rawValue: this.rawValue,
|
||||
zoneName: this.zoneName,
|
||||
inferredZone: this.inferredZone,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @return a new ExifDateTime from the given JSON. Note that this instance **may not be valid**.
|
||||
*/
|
||||
static fromJSON(json) {
|
||||
return new ExifDateTime(json.year, json.month, json.day, json.hour, json.minute, json.second, json.millisecond, json.tzoffsetMinutes, json.rawValue, json.zoneName, json.inferredZone);
|
||||
}
|
||||
maybeMatchZone(target, maxDeltaMs = 14 * DateTime_1.MinuteMs) {
|
||||
const targetZone = target.zone;
|
||||
if (targetZone == null || !target.hasZone)
|
||||
return;
|
||||
return (this.setZone(targetZone, { keepLocalTime: false })?.ifClose(target, maxDeltaMs) ??
|
||||
this.setZone(targetZone, { keepLocalTime: true })?.ifClose(target, maxDeltaMs));
|
||||
}
|
||||
ifClose(target, maxDeltaMs = 14 * DateTime_1.MinuteMs) {
|
||||
const ts = this.toMillis();
|
||||
const targetTs = target.toMillis();
|
||||
return Math.abs(ts - targetTs) <= maxDeltaMs ? this : undefined;
|
||||
}
|
||||
plus(duration) {
|
||||
let dt = this.toDateTime().plus(duration);
|
||||
if (!this.hasZone) {
|
||||
dt = dt.setZone(Timezones_1.UnsetZone, { keepLocalTime: true });
|
||||
}
|
||||
return ExifDateTime.fromDateTime(dt, this);
|
||||
}
|
||||
}
|
||||
exports.ExifDateTime = ExifDateTime;
|
||||
//# sourceMappingURL=ExifDateTime.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExifDateTime.js","sourceRoot":"","sources":["../src/ExifDateTime.ts"],"names":[],"mappings":"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 truncated
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { DateTime, Zone, ZoneOptions } from "luxon";
|
||||
import { Maybe } from "./Maybe";
|
||||
/**
|
||||
* Encodes an ExifTime (which may not have a timezone offset)
|
||||
*/
|
||||
export declare class ExifTime {
|
||||
#private;
|
||||
readonly hour: number;
|
||||
readonly minute: number;
|
||||
readonly second: number;
|
||||
readonly millisecond?: number | undefined;
|
||||
readonly rawValue?: string | undefined;
|
||||
readonly inferredZone?: boolean | undefined;
|
||||
static fromEXIF(text: string, defaultZone?: Maybe<string>): Maybe<ExifTime>;
|
||||
static fromDateTime(dt: Maybe<DateTime>, rawValue?: string, zone?: string, inferredZone?: boolean, unsetMilliseconds?: boolean): Maybe<ExifTime>;
|
||||
readonly zone: Maybe<string>;
|
||||
constructor(hour: number, minute: number, second: number, millisecond?: number | undefined, rawValue?: string | undefined, zoneName?: Maybe<string>, inferredZone?: boolean | undefined);
|
||||
toDateTime(): DateTime;
|
||||
/**
|
||||
* Alias for `.millisecond`
|
||||
*/
|
||||
get millis(): number | undefined;
|
||||
get hasZone(): boolean;
|
||||
toString(): string;
|
||||
toISOString(): string;
|
||||
toExifString(): string;
|
||||
setZone(zone: string | Zone, opts?: ZoneOptions): Maybe<ExifTime>;
|
||||
toJSON(): {
|
||||
_ctor: string;
|
||||
hour: number;
|
||||
minute: number;
|
||||
second: number;
|
||||
millisecond: number | undefined;
|
||||
rawValue: string | undefined;
|
||||
zone: Maybe<string>;
|
||||
inferredZone: boolean | undefined;
|
||||
};
|
||||
static fromJSON(json: ReturnType<ExifTime["toJSON"]>): ExifTime;
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ExifTime = void 0;
|
||||
const luxon_1 = require("luxon");
|
||||
const DateTime_1 = require("./DateTime");
|
||||
const String_1 = require("./String");
|
||||
const TimeParsing_1 = require("./TimeParsing");
|
||||
const Timezones_1 = require("./Timezones");
|
||||
/**
|
||||
* Encodes an ExifTime (which may not have a timezone offset)
|
||||
*/
|
||||
class ExifTime {
|
||||
hour;
|
||||
minute;
|
||||
second;
|
||||
millisecond;
|
||||
rawValue;
|
||||
inferredZone;
|
||||
static fromEXIF(text, defaultZone) {
|
||||
const s = (0, String_1.toS)(text).trim();
|
||||
if (s.length === 0)
|
||||
return;
|
||||
const result = (0, TimeParsing_1.parseDateTime)(text, (0, TimeParsing_1.timeFormats)({ defaultZone }));
|
||||
if (result != null) {
|
||||
return this.fromDateTime(result.dt, text, result.unsetZone ? undefined : (0, Timezones_1.getZoneName)({ zone: result.dt.zone }), result.inferredZone, result.unsetMilliseconds);
|
||||
}
|
||||
return;
|
||||
}
|
||||
static fromDateTime(dt, rawValue, zone, inferredZone, unsetMilliseconds) {
|
||||
return !(0, DateTime_1.validDateTime)(dt)
|
||||
? undefined
|
||||
: new ExifTime(dt.hour, dt.minute, dt.second, unsetMilliseconds ? undefined : dt.millisecond, rawValue, zone, inferredZone);
|
||||
}
|
||||
#dt;
|
||||
#z;
|
||||
zone;
|
||||
constructor(hour, minute, second, millisecond, rawValue, zoneName, inferredZone) {
|
||||
this.hour = hour;
|
||||
this.minute = minute;
|
||||
this.second = second;
|
||||
this.millisecond = millisecond;
|
||||
this.rawValue = rawValue;
|
||||
this.inferredZone = inferredZone;
|
||||
this.zone = (0, Timezones_1.getZoneName)({ zoneName });
|
||||
}
|
||||
toDateTime() {
|
||||
return (this.#dt ??= luxon_1.DateTime.fromObject({
|
||||
hour: this.hour,
|
||||
minute: this.minute,
|
||||
second: this.second,
|
||||
millisecond: this.millisecond,
|
||||
}, {
|
||||
zone: this.zone,
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* Alias for `.millisecond`
|
||||
*/
|
||||
get millis() {
|
||||
return this.millisecond;
|
||||
}
|
||||
get hasZone() {
|
||||
return this.zone != null;
|
||||
}
|
||||
#subsec() {
|
||||
return this.millisecond == null ? "" : "." + (0, String_1.pad3)(this.millisecond);
|
||||
}
|
||||
#shortZone() {
|
||||
return (this.#z ??= (0, Timezones_1.zoneToShortOffset)(this.zone));
|
||||
}
|
||||
toString() {
|
||||
return ((0, String_1.pad2)(this.hour, this.minute, this.second).join(":") +
|
||||
this.#subsec() +
|
||||
this.#shortZone());
|
||||
}
|
||||
toISOString() {
|
||||
return this.toString();
|
||||
}
|
||||
toExifString() {
|
||||
return this.toString();
|
||||
}
|
||||
setZone(zone, opts) {
|
||||
const dt = (0, TimeParsing_1.setZone)({
|
||||
zone,
|
||||
src: this.toDateTime(),
|
||||
srcHasZone: this.hasZone,
|
||||
opts,
|
||||
});
|
||||
return ExifTime.fromDateTime(dt, this.rawValue, this.zone, this.inferredZone, this.millisecond == null);
|
||||
}
|
||||
toJSON() {
|
||||
return {
|
||||
_ctor: "ExifTime",
|
||||
hour: this.hour,
|
||||
minute: this.minute,
|
||||
second: this.second,
|
||||
millisecond: this.millisecond,
|
||||
rawValue: this.rawValue,
|
||||
zone: this.zone,
|
||||
inferredZone: this.inferredZone,
|
||||
};
|
||||
}
|
||||
static fromJSON(json) {
|
||||
return new ExifTime(json.hour, json.minute, json.second, json.millisecond, json.rawValue, json.zone, json.inferredZone);
|
||||
}
|
||||
}
|
||||
exports.ExifTime = ExifTime;
|
||||
//# sourceMappingURL=ExifTime.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExifTime.js","sourceRoot":"","sources":["../src/ExifTime.ts"],"names":[],"mappings":"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"}
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
import * as bc from "batch-cluster";
|
||||
import { BinaryExtractionTaskOptions } from "./BinaryExtractionTask";
|
||||
import { ContainerDirectoryItem } from "./ContainerDirectoryItem";
|
||||
import { Defined, DefinedOrNullValued } from "./Defined";
|
||||
import { ErrorsAndWarnings } from "./ErrorsAndWarnings";
|
||||
import { ExifToolOptions } from "./ExifToolOptions";
|
||||
import { ExifToolTask, ExifToolTaskOptions } from "./ExifToolTask";
|
||||
import { ExifToolVendoredTags } from "./ExifToolVendoredTags";
|
||||
import { GeolocationTags } from "./GeolocationTags";
|
||||
import { ICCProfileTags } from "./ICCProfileTags";
|
||||
import { IPTCApplicationRecordTags } from "./IPTCApplicationRecordTags";
|
||||
import { ImageDataHashTag } from "./ImageDataHashTag";
|
||||
import { CollectionInfo, KeywordInfoStruct, KeywordStruct, MWGCollectionsTags, MWGKeywordTags } from "./MWGTags";
|
||||
import { Maybe } from "./Maybe";
|
||||
import { Omit } from "./Omit";
|
||||
import { PreviewTag } from "./PreviewTag";
|
||||
import { Json, Literal, RawTags } from "./RawTags";
|
||||
import { ReadTaskOptions } from "./ReadTask";
|
||||
import { ResourceEvent } from "./ResourceEvent";
|
||||
import { ShortcutTags } from "./ShortcutTags";
|
||||
import { Struct } from "./Struct";
|
||||
import { APPTags, CompositeTags, EXIFTags, ExifToolTags, FileTags, FlashPixTags, IPTCTags, JFIFTags, MPFTags, MakerNotesTags, MetaTags, PanasonicRawTags, PhotoshopTags, PrintIMTags, QuickTimeTags, RAFTags, RIFFTags, Tags, XMPTags } from "./Tags";
|
||||
import { Version } from "./Version";
|
||||
import { AdditionalWriteTags, ExpandedDateTags, MutableTags, StructAppendTags, WritableGPSRefs, WriteTags } from "./WriteTags";
|
||||
import { WriteTaskOptions, WriteTaskResult } from "./WriteTask";
|
||||
export { BinaryField } from "./BinaryField";
|
||||
export { CapturedAtTagNames } from "./CapturedAtTagNames";
|
||||
export { DefaultExifToolOptions } from "./DefaultExifToolOptions";
|
||||
export { DefaultExiftoolArgs } from "./DefaultExiftoolArgs";
|
||||
export { DefaultMaxProcs } from "./DefaultMaxProcs";
|
||||
export { ExifDate } from "./ExifDate";
|
||||
export { ExifDateTime } from "./ExifDateTime";
|
||||
export { ExifTime } from "./ExifTime";
|
||||
export { ExifToolTask } from "./ExifToolTask";
|
||||
export { exiftoolPath } from "./ExiftoolPath";
|
||||
export { GeolocationTagNames, isGeolocationTag } from "./GeolocationTags";
|
||||
export { parseJSON } from "./JSON";
|
||||
export { DefaultReadTaskOptions } from "./ReadTask";
|
||||
export { TimezoneOffsetTagnames, UnsetZone, UnsetZoneName, UnsetZoneOffsetMinutes, defaultVideosToUTC, offsetMinutesToZoneName, } from "./Timezones";
|
||||
export { DefaultWriteTaskOptions, WriteTaskOptionFields } from "./WriteTask";
|
||||
export type { APPTags, AdditionalWriteTags, IPTCApplicationRecordTags as ApplicationRecordTags, BinaryExtractionTaskOptions, CollectionInfo, CompositeTags, ContainerDirectoryItem, Defined, DefinedOrNullValued, EXIFTags, ErrorsAndWarnings, ExifToolOptions, ExifToolTags, ExifToolTaskOptions, ExifToolVendoredTags, ExpandedDateTags, FileTags, FlashPixTags, GeolocationTags, ICCProfileTags, IPTCApplicationRecordTags, IPTCTags, ImageDataHashTag, JFIFTags, Json, KeywordInfoStruct, KeywordStruct, Literal, MPFTags, MWGCollectionsTags, MWGKeywordTags, MakerNotesTags, Maybe, MetaTags, MutableTags, Omit, PanasonicRawTags, PhotoshopTags, PrintIMTags, QuickTimeTags, RAFTags, RIFFTags, RawTags, ReadTaskOptions, ResourceEvent, ShortcutTags, Struct, StructAppendTags, Tags, Version, WritableGPSRefs, WriteTags, WriteTaskOptions, WriteTaskResult, XMPTags, };
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export declare class ExifTool {
|
||||
#private;
|
||||
readonly options: ExifToolOptions;
|
||||
readonly batchCluster: bc.BatchCluster;
|
||||
constructor(options?: Partial<ExifToolOptions>);
|
||||
readonly exiftoolPath: () => Promise<string>;
|
||||
/**
|
||||
* Register life cycle event listeners. Delegates to BatchProcess.
|
||||
*/
|
||||
readonly on: bc.BatchCluster["on"];
|
||||
/**
|
||||
* Unregister life cycle event listeners. Delegates to BatchProcess.
|
||||
*/
|
||||
readonly off: bc.BatchCluster["off"];
|
||||
/**
|
||||
* @return a promise holding the version number of the vendored ExifTool
|
||||
*/
|
||||
version(): Promise<string>;
|
||||
/**
|
||||
* Read the tags in `file`.
|
||||
*
|
||||
* @param file the file to extract metadata tags from
|
||||
*
|
||||
* @param options overrides to the default ExifTool options provided to the
|
||||
* ExifTool constructor.
|
||||
*
|
||||
* @returns A resolved Tags promise. If there are errors during reading, the
|
||||
* `.errors` field will be present.
|
||||
*/
|
||||
read<T extends Tags = Tags>(file: string, options?: ReadTaskOptions): Promise<T>;
|
||||
/**
|
||||
* Read the tags in `file`.
|
||||
*
|
||||
* @param file the file to extract metadata tags from
|
||||
*
|
||||
* @param readArgs any additional ExifTool arguments, like `["-fast"]`,
|
||||
* `["-fast2"]`, `["-g"]`, or `["-api", "largefilesupport=1"]`. Note that
|
||||
* providing a value here will override the `readArgs` array provided to the
|
||||
* ExifTool constructor. **Note that most other arguments will require you to
|
||||
* use `readRaw`.** Note that the default is `["-fast"]`, so if you want
|
||||
* ExifTool to read the entire file for metadata, you should pass an empty
|
||||
* array as the second parameter. See https://exiftool.org/#performance for
|
||||
* more information about `-fast` and `-fast2`.
|
||||
*
|
||||
* @param options overrides to the default ExifTool options provided to the
|
||||
* ExifTool constructor.
|
||||
*
|
||||
* @returns A resolved Tags promise. If there are errors during reading, the
|
||||
* `.errors` field will be present.
|
||||
*
|
||||
* @deprecated use
|
||||
* {@link ExifTool.read(file: string, options?: ReadTaskOptions)} instead
|
||||
* (move `readArgs` into your `options` hash)
|
||||
*/
|
||||
read<T extends Tags = Tags>(file: string, readArgs?: string[], options?: ReadTaskOptions): Promise<T>;
|
||||
/**
|
||||
* Read the tags from `file`, without any post-processing of ExifTool values.
|
||||
*
|
||||
* **You probably want `read`, not this method. READ THE REST OF THIS COMMENT
|
||||
* CAREFULLY.**
|
||||
*
|
||||
* If you want to extract specific tag values from a file, you may want to use
|
||||
* this, but all data validation and inference heuristics provided by `read`
|
||||
* will be skipped.
|
||||
*
|
||||
* Note that performance will be very similar to `read`, and will actually be
|
||||
* worse if you don't include `-fast` or `-fast2` (as the most expensive bit
|
||||
* is the perl interpreter and scanning the file on disk).
|
||||
*
|
||||
* @param args any additional arguments other than the file path. Note that
|
||||
* "-json", and the Windows unicode filename handler flags, "-charset
|
||||
* filename=utf8", will be added automatically.
|
||||
*
|
||||
* @return Note that the return value will be similar to `Tags`, but with no
|
||||
* date, time, or other rich type parsing that you get from `.read()`. The
|
||||
* field values will be `string | number | string[]`.
|
||||
*
|
||||
* @see https://github.com/photostructure/exiftool-vendored.js/issues/44 for
|
||||
* typing details.
|
||||
*/
|
||||
readRaw(file: string, args?: string[]): Promise<RawTags>;
|
||||
/**
|
||||
* Write the given `tags` to `file`.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return 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: string, tags: WriteTags, options?: WriteTaskOptions): Promise<WriteTaskResult>;
|
||||
/**
|
||||
* @param file an existing file to write `tags` to
|
||||
*
|
||||
* @param tags the tags to write to `file`.
|
||||
*
|
||||
* @param writeArgs any additional ExifTool arguments, like `-n`, or
|
||||
* `-overwrite_original`.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @deprecated use
|
||||
* {@link ExifTool.write(file: string, tags: WriteTags, options?: WriteTaskOptions)}
|
||||
* instead: move `writeArgs` into your `options` hash.
|
||||
*/
|
||||
write(file: string, tags: WriteTags, writeArgs?: string[], options?: WriteTaskOptions): Promise<WriteTaskResult>;
|
||||
/**
|
||||
* 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: string, opts?: {
|
||||
retain?: (keyof Tags | string)[];
|
||||
} & Partial<ExifToolTaskOptions>): Promise<WriteTaskResult>;
|
||||
/**
|
||||
* 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: string, thumbnailFile: string, opts?: BinaryExtractionTaskOptions): Promise<void>;
|
||||
/**
|
||||
* 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: string, previewFile: string, opts?: BinaryExtractionTaskOptions): Promise<void>;
|
||||
/**
|
||||
* 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: string, outputFile: string, opts?: BinaryExtractionTaskOptions): Promise<void>;
|
||||
/**
|
||||
* 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 not be written to `dest`.
|
||||
*/
|
||||
extractBinaryTag(tagname: string, src: string, dest: string, opts?: BinaryExtractionTaskOptions): Promise<void>;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
extractBinaryTagToBuffer(tagname: PreviewTag, imageFile: string, opts?: ExifToolTaskOptions): Promise<Buffer>;
|
||||
/**
|
||||
* 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: string, outputFile: string, opts?: {
|
||||
allowMakerNoteRepair?: boolean;
|
||||
} & ExifToolTaskOptions): Promise<void>;
|
||||
/**
|
||||
* 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?: boolean): Promise<void>;
|
||||
/**
|
||||
* @return true if `.end()` has been invoked
|
||||
*/
|
||||
get ended(): boolean;
|
||||
/**
|
||||
* Most users will not need to use `enqueueTask` directly. This method
|
||||
* supports submitting custom `BatchCluster` tasks.
|
||||
*
|
||||
* @param task is a thunk to support retries by providing new instances on retries.
|
||||
*
|
||||
* @see BinaryExtractionTask for an example task implementation
|
||||
*/
|
||||
enqueueTask<T>(task: () => ExifToolTask<T>, retriable?: boolean): Promise<T>;
|
||||
/**
|
||||
* @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(): number[];
|
||||
/**
|
||||
* @return the number of pending (not currently worked on) tasks
|
||||
*/
|
||||
get pendingTasks(): number;
|
||||
/**
|
||||
* @return the total number of child processes created by this instance
|
||||
*/
|
||||
get spawnedProcs(): number;
|
||||
/**
|
||||
* @return the current number of child processes currently servicing tasks
|
||||
*/
|
||||
get busyProcs(): number;
|
||||
/**
|
||||
* @return report why child processes were recycled
|
||||
*/
|
||||
childEndCounts(): {
|
||||
startError: number;
|
||||
broken: number;
|
||||
closed: number;
|
||||
ending: number;
|
||||
ended: number;
|
||||
idle: number;
|
||||
old: number;
|
||||
"proc.close": number;
|
||||
"proc.disconnect": number;
|
||||
"proc.error": number;
|
||||
"proc.exit": number;
|
||||
"stderr.error": number;
|
||||
stderr: number;
|
||||
"stdin.error": number;
|
||||
"stdout.error": number;
|
||||
timeout: number;
|
||||
tooMany: number;
|
||||
unhealthy: number;
|
||||
worn: number;
|
||||
};
|
||||
/**
|
||||
* Shut down any currently-running child processes. New child processes will
|
||||
* be started automatically to handle new tasks.
|
||||
*/
|
||||
closeChildProcesses(gracefully?: boolean): Promise<void>;
|
||||
}
|
||||
/**
|
||||
* Use this singleton rather than instantiating new {@link ExifTool} instances
|
||||
* in order to leverage a single running ExifTool process.
|
||||
*
|
||||
* As of v3.0, its {@link ExifToolOptions.maxProcs} is set to 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.
|
||||
*/
|
||||
export declare const exiftool: ExifTool;
|
||||
+485
@@ -0,0 +1,485 @@
|
||||
"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.offsetMinutesToZoneName = exports.defaultVideosToUTC = exports.UnsetZoneOffsetMinutes = exports.UnsetZoneName = exports.UnsetZone = exports.TimezoneOffsetTagnames = exports.DefaultReadTaskOptions = 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 = 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 String_1 = require("./String");
|
||||
const VersionTask_1 = require("./VersionTask");
|
||||
const Which_1 = require("./Which");
|
||||
const WriteTask_1 = require("./WriteTask");
|
||||
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 ReadTask_2 = require("./ReadTask");
|
||||
Object.defineProperty(exports, "DefaultReadTaskOptions", { enumerable: true, get: function () { return ReadTask_2.DefaultReadTaskOptions; } });
|
||||
var Timezones_1 = require("./Timezones");
|
||||
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, "defaultVideosToUTC", { enumerable: true, get: function () { return Timezones_1.defaultVideosToUTC; } });
|
||||
Object.defineProperty(exports, "offsetMinutesToZoneName", { enumerable: true, get: function () { return Timezones_1.offsetMinutesToZoneName; } });
|
||||
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
|
||||
*/
|
||||
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,
|
||||
});
|
||||
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,
|
||||
};
|
||||
const 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 BatchProcess.
|
||||
*/
|
||||
on = (event, listener) => this.batchCluster.on(event, listener);
|
||||
/**
|
||||
* Unregister life cycle event listeners. Delegates to BatchProcess.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* **You probably want `read`, not this method. READ THE REST OF THIS COMMENT
|
||||
* CAREFULLY.**
|
||||
*
|
||||
* If you want to extract specific tag values from a file, you may want to use
|
||||
* this, but all data validation and inference heuristics provided by `read`
|
||||
* will be skipped.
|
||||
*
|
||||
* Note that performance will be very similar to `read`, and will actually be
|
||||
* worse if you don't include `-fast` or `-fast2` (as the most expensive bit
|
||||
* is the perl interpreter and scanning the file on disk).
|
||||
*
|
||||
* @param args any additional arguments other than the file path. Note that
|
||||
* "-json", and the Windows unicode filename handler flags, "-charset
|
||||
* filename=utf8", will be added automatically.
|
||||
*
|
||||
* @return Note that the return value will be similar to `Tags`, but with no
|
||||
* date, time, or other rich type parsing that you get from `.read()`. The
|
||||
* field values will be `string | number | string[]`.
|
||||
*
|
||||
* @see https://github.com/photostructure/exiftool-vendored.js/issues/44 for
|
||||
* typing details.
|
||||
*/
|
||||
readRaw(file, args = []) {
|
||||
return this.enqueueTask(() => ReadRawTask_1.ReadRawTask.for(file, args, this.#taskOptions()));
|
||||
}
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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 not 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 is a thunk to support retries by providing new instances on retries.
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
exports.ExifTool = ExifTool;
|
||||
/**
|
||||
* Use this singleton rather than instantiating new {@link ExifTool} instances
|
||||
* in order to leverage a single running ExifTool process.
|
||||
*
|
||||
* As of v3.0, its {@link ExifToolOptions.maxProcs} is set to 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
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExifTool.js","sourceRoot":"","sources":["../src/ExifTool.ts"],"names":[],"mappings":"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 truncated
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
import * as bc from "batch-cluster";
|
||||
import { geoTz } from "./GeoTz";
|
||||
import { Maybe } from "./Maybe";
|
||||
import { Tags } from "./Tags";
|
||||
/**
|
||||
* Options for the {@link ExifTool} constructor.
|
||||
*
|
||||
* Defaults are defined in {@link DefaultExifToolOptions}.
|
||||
*/
|
||||
export interface ExifToolOptions extends bc.BatchClusterOptions, bc.BatchProcessOptions, bc.ChildProcessFactory {
|
||||
/**
|
||||
* The maximum number of ExifTool child processes to spawn when load merits.
|
||||
*
|
||||
* Defaults to 1/4 the number of CPUs, minimally 1.
|
||||
*/
|
||||
maxProcs: number;
|
||||
/**
|
||||
* The maximum number of requests a given ExifTool process will service
|
||||
* before being retired.
|
||||
*
|
||||
* Defaults to 500, to balance performance with memory usage.
|
||||
*/
|
||||
maxTasksPerProcess: number;
|
||||
/**
|
||||
* Spawning new ExifTool processes must not take longer than
|
||||
* {@link spawnTimeoutMillis} milliseconds before the child process is timed
|
||||
* out and a new attempt is made. Be pessimistic here--windows can regularly
|
||||
* take several seconds to spin up a process, thanks to antivirus
|
||||
* shenanigans. This can't be set to a value less than 100ms.
|
||||
*
|
||||
* Defaults to 30 seconds, to accommodate slow Windows machines.
|
||||
*/
|
||||
spawnTimeoutMillis: number;
|
||||
/**
|
||||
* If requests to ExifTool take longer than this, presume the underlying
|
||||
* process is dead and we should restart the task. This can't be set to a
|
||||
* value less than 10ms, and really should be set to at more than a second
|
||||
* unless `taskRetries` is sufficiently large or all writes will be to a
|
||||
* fast local disk. Defaults to 10 seconds.
|
||||
*/
|
||||
taskTimeoutMillis: number;
|
||||
/**
|
||||
* An interval timer is scheduled to do periodic maintenance of underlying
|
||||
* child processes with this periodicity.
|
||||
*
|
||||
* Defaults to 2 seconds.
|
||||
*/
|
||||
onIdleIntervalMillis: number;
|
||||
/**
|
||||
* The number of times a task can error or timeout and be retried.
|
||||
*
|
||||
* Defaults to 1 (every task gets 2 chances).
|
||||
*/
|
||||
taskRetries: number;
|
||||
/**
|
||||
* Allows for non-standard paths to ExifTool. Defaults to the perl or
|
||||
* windows binaries provided by `exiftool-vendored.pl` or
|
||||
* `exiftool-vendored.exe`.
|
||||
*
|
||||
* This must be the full path to `exiftool`, not just the directory.
|
||||
*/
|
||||
exiftoolPath: string | Promise<string> | ((logger?: bc.Logger) => string | Promise<string>);
|
||||
/**
|
||||
* Args only passed to exiftool on launch. You probably don't need to change
|
||||
* this from the default.
|
||||
*/
|
||||
exiftoolArgs: string[];
|
||||
/**
|
||||
* Environment variables passed to ExifTool (besides `EXIFTOOL_HOME`)
|
||||
*/
|
||||
exiftoolEnv: NodeJS.ProcessEnv;
|
||||
/**
|
||||
* Should ExifTool use MWG (Metadata Working Group) composite tags for
|
||||
* reading and writing tags?
|
||||
*
|
||||
* ExifTool recommends this to be set to true. This defaults to `false` to
|
||||
* maintain consistency with prior versions.
|
||||
*
|
||||
* Note that this can result in many tag value differences from
|
||||
* `ExifTool.read`, and makes `ExifTool.write` write to "synonymous" MWG
|
||||
* tags automatically.
|
||||
*
|
||||
* @see https://exiftool.org/TagNames/MWG.html for details
|
||||
*/
|
||||
useMWG: boolean;
|
||||
/**
|
||||
* Tag names (which can have '*' glob matchers) which you want numeric
|
||||
* values, rather than ExifTool's "Print Conversion."
|
||||
*
|
||||
* If you're using tag values only for human consumption, you may want to
|
||||
* leave this blank.
|
||||
*
|
||||
* The default includes "*Duration*", {@link Tags.GPSAltitude},
|
||||
* {@link Tags.GPSLatitude}, {@link Tags.GPSLongitude},
|
||||
* {@link Tags.GPSPosition}, and {@link Tags.Orientation}.
|
||||
*/
|
||||
numericTags: string[];
|
||||
/**
|
||||
* If defined, ExifTool will attempt to calculate an "ImageDataHash" tag
|
||||
* value with a checksum of image data.
|
||||
*
|
||||
* Note that as of 2022-04-12, ExifTool supports JPEG, TIFF, PNG, CRW, CR3,
|
||||
* MRW, RAF, X3F, IIQ, JP2, JXL, HEIC and AVIF images, MOV/MP4 videos, and
|
||||
* some RIFF-based files such as AVI, WAV and WEBP.
|
||||
*
|
||||
* This defaults to undefined, as it adds ~20ms of overhead to every read
|
||||
*/
|
||||
imageHashType: false | "MD5" | "SHA256" | "SHA512";
|
||||
/**
|
||||
* @deprecated Use `imageHashType` instead.
|
||||
*/
|
||||
includeImageDataMD5: boolean | undefined;
|
||||
/**
|
||||
* Video file dates are assumed to be in UTC, rather than using timezone
|
||||
* inference used in images. To disable this default, set this to false.
|
||||
*
|
||||
* @see https://github.com/photostructure/exiftool-vendored.js/issues/113
|
||||
*/
|
||||
defaultVideosToUTC: boolean;
|
||||
/**
|
||||
* Should we try to backfill timezones for date-times that don't have them?
|
||||
* If set to `true`, and {@link defaultVideosToUTC} is also `true`, we'll
|
||||
* try backfilling timezones for date-times that are UTC, as well.
|
||||
*
|
||||
* Setting this to `false` removes **all** timezone inference--only those
|
||||
* date-times with an explicit offset will have a defined timezone.
|
||||
*
|
||||
* Prior versions of exiftool-vendored would use the file's `.tz` as a
|
||||
* backstop even if this was set to `false`.
|
||||
*
|
||||
* As of version 23, this now defaults to `true`, as it's more likely to be
|
||||
* what people expect.
|
||||
*/
|
||||
backfillTimezones: boolean;
|
||||
/**
|
||||
* We always look at {@link Tags.TimeZone}, {@link Tags.OffsetTime},
|
||||
* {@link Tags.TimeZoneOffset}, {@link Tags.OffsetTimeOriginal},
|
||||
* {@link Tags.OffsetTimeDigitized}, and GPS metadata to infer the timezone.
|
||||
*
|
||||
* If these strategies fail, and this is enabled, we'll try to infer the
|
||||
* timezone from non-UTC datestamps included in the
|
||||
* {@link inferTimezoneFromDatestampTags} value.
|
||||
*
|
||||
* This defaults to false as it both retains prior behavior and means fewer
|
||||
* "fuzzy" heuristics are enabled by default.
|
||||
*/
|
||||
inferTimezoneFromDatestamps: boolean;
|
||||
/**
|
||||
* This is the list of tag names that will be used to infer the timezone as
|
||||
* a backstop, if no explicit timezone is found in metadata. Note that
|
||||
* datestamps with UTC offsets are ignored, as they are frequently
|
||||
* incorrectly set.
|
||||
*
|
||||
* This setting is only in play if {@link inferTimezoneFromDatestamps} has
|
||||
* been overridden to be `true`.
|
||||
*
|
||||
* This defaults to {@link CapturedAtTagNames}
|
||||
*/
|
||||
inferTimezoneFromDatestampTags: (keyof Tags)[];
|
||||
/**
|
||||
* Some cameras (Samsung Galaxy S7, for example) may not always include GPS
|
||||
* metadata in photos if a fix can't be obtained. If this option is true, and
|
||||
* GPS metadata is missing, we'll try to infer the timezone from the
|
||||
* difference of the TimeStamp tag and the first defined tag value from
|
||||
* {@link inferTimezoneFromDatestampTags}.
|
||||
*
|
||||
* This heuristic is pretty sketchy, and used as a last resort. You shouldn't
|
||||
* enable it unless you have to.
|
||||
*
|
||||
* @see https://github.com/photostructure/exiftool-vendored.js/issues/209
|
||||
*/
|
||||
inferTimezoneFromTimeStamp: boolean;
|
||||
/**
|
||||
* Some software uses a GPS position of (0,0) as a synonym for "unset". If
|
||||
* this option is true, and GPSLatitude and GPSLongitude are both 0, then
|
||||
* those values will be returned, but the TZ will not be inferred from that
|
||||
* location.
|
||||
*
|
||||
* If both this and {@link geolocation} are `true`, we will _delete_ the
|
||||
* Geolocation tags from the returned metadata object.
|
||||
*
|
||||
* @see https://en.wikipedia.org/wiki/Null_Island
|
||||
*/
|
||||
ignoreZeroZeroLatLon: boolean;
|
||||
/**
|
||||
* Override the default geo-to-timezone lookup service. Note that if
|
||||
{@link geolocation} is enabled, we'll use
|
||||
{@link Tags.GeolocationTimeZone} if it's not blank.
|
||||
*
|
||||
* This defaults to `@photostructure/tz-lookup`, but if you have the
|
||||
* resources, consider using `geo-tz` for more accurate results.
|
||||
*
|
||||
* If your implementation throws an error, `ExifTool` will consider that given
|
||||
* latitude/longitude as invalid.
|
||||
*
|
||||
* Here's a snippet of how to use `geo-tz` instead of `tz-lookup`:
|
||||
*
|
||||
```js
|
||||
const geotz = require("geo-tz")
|
||||
const { ExifTool } = require("exiftool-vendored")
|
||||
const exiftool = new ExifTool({ geoTz: (lat, lon) => geotz.find(lat, lon)[0] })
|
||||
```
|
||||
*
|
||||
* @see https://github.com/photostructure/tz-lookup
|
||||
* @see https://github.com/evansiroky/node-geo-tz/
|
||||
*/
|
||||
geoTz: typeof geoTz;
|
||||
/**
|
||||
* When reading metadata, should we enable ExifTool's geolocation features?
|
||||
* Note that this requires ExifTool version 12.78 or later.
|
||||
*
|
||||
* @see https://exiftool.org/geolocation.html
|
||||
* @see {@link GeolocationTags}
|
||||
*/
|
||||
geolocation: boolean;
|
||||
/**
|
||||
* Should we ignore minor errors when reading metadata?
|
||||
*
|
||||
* This defaults to `true`, as ExifTool can be quite chatty.
|
||||
*/
|
||||
ignoreMinorErrors: boolean;
|
||||
/**
|
||||
* When writing an extracted tag to a file,
|
||||
* this will overwrite an existing file instead of throwing an error.
|
||||
* Enabling this option is equivalent to `-w!` in ExifTool.
|
||||
*
|
||||
* This defaults to `false`.
|
||||
*/
|
||||
forceWrite: boolean;
|
||||
/**
|
||||
* `ExifTool` has a shebang line that assumes a valid `perl` is installed at
|
||||
* `/usr/bin/perl`.
|
||||
*
|
||||
* Some environments may not include a valid `/usr/bin/perl` (like AWS
|
||||
* Lambda), but `perl` may be available in your `PATH` some place else (like
|
||||
* `/opt/bin/perl`), if you pull in a perl layer.
|
||||
*
|
||||
* This will default to `true` in those environments as a workaround in these
|
||||
* situations. Note also that `perl` will be spawned in a sub-shell.
|
||||
*/
|
||||
ignoreShebang: boolean;
|
||||
/**
|
||||
* Should we check for a readable and executable `perl` file in $PATH? This
|
||||
* defaults to false on Windows, and true everywhere else. Set this to false
|
||||
* if you know perl is installed.
|
||||
*/
|
||||
checkPerl: boolean;
|
||||
/**
|
||||
* How should ExifTool handle nested structures?
|
||||
*
|
||||
* Defaults to `1`.
|
||||
*
|
||||
* - `0` = Read/copy flattened tags
|
||||
* - `1` = Read/copy structures
|
||||
* - `2` = Read/copy both flattened and structured tags, but flag flattened
|
||||
* tags as "unsafe" for copying
|
||||
* - `"undef"` = Same as `0` for reading and `2` for copying
|
||||
*
|
||||
* @see https://exiftool.org/struct.html
|
||||
*/
|
||||
struct: "undef" | 0 | 1 | 2;
|
||||
/**
|
||||
* Any additional arguments that should be added by default to all read tasks,
|
||||
* like `["-fast", "-api", "largefilesupport=1"]`. The value provided to the
|
||||
* ExifTool constructor can be overridden in the call to {@link ExifTool.read}
|
||||
*/
|
||||
readArgs: string[];
|
||||
/**
|
||||
* Any additional arguments that should be added by default to all write
|
||||
* tasks, like `["-overwrite_original"]`. The value provided to the ExifTool
|
||||
* constructor can be overridden in the call to {@link ExifTool.write}.
|
||||
*/
|
||||
writeArgs: string[];
|
||||
/**
|
||||
* The TimeZone tag normally represents the offset from UTC.
|
||||
*
|
||||
* Unfortunately, at least for some Nikon cameras, the TimeZone tag **and the
|
||||
* DaylightSavings tag** must be taken into account to find the UTC offset.
|
||||
*
|
||||
* By default, this is a predicate that returns `true` if the `Make` tag is
|
||||
* `Nikon`. If you find other makes and models that need this treatment,
|
||||
* please open a ticket on GitHub with example images or videos and we can
|
||||
* update the default predicate.
|
||||
*
|
||||
* The return value is the number of minutes to adjust the timezone by.
|
||||
*
|
||||
* @see https://github.com/photostructure/exiftool-vendored.js/issues/215
|
||||
*/
|
||||
adjustTimeZoneIfDaylightSavings: (tags: Tags, tz: string) => Maybe<number>;
|
||||
/**
|
||||
* Timezone parsing requires a bunch of heuristics due to hardware and
|
||||
* software companies not following metadata specifications similarly.
|
||||
*
|
||||
* If GPS metadata is trustworthy, set this to `true` to override explicit
|
||||
* values assigned to {@link TimezoneOffsetTagnames}.
|
||||
*
|
||||
* Note that there **are** regions that have had their IANA timezone change
|
||||
* over time--this will result incorrect timezones.
|
||||
*/
|
||||
preferTimezoneInferenceFromGps: boolean;
|
||||
/**
|
||||
* Should ExifTool keep times that are stored as seconds since UTC epoch as
|
||||
* UTC times? If false, ExifTool will use local time instead of UTC/Zulu.
|
||||
*
|
||||
* We default to `true` to ensure we don't unintentionally adopt local
|
||||
* timezones.
|
||||
*
|
||||
* Please note: when trying to validate this option, I **could not find a
|
||||
* single example** that had a unixtime-encoded datetime, so I suspect this is
|
||||
* irrelevant for most use cases and files.
|
||||
*
|
||||
* @see https://exiftool.org/ExifTool.html#KeepUTCTime
|
||||
*/
|
||||
keepUTCTime: boolean;
|
||||
}
|
||||
export declare function handleDeprecatedOptions<T extends Partial<Pick<ExifToolOptions, "includeImageDataMD5" | "imageHashType">>>(options: T): T;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.handleDeprecatedOptions = handleDeprecatedOptions;
|
||||
function handleDeprecatedOptions(options) {
|
||||
if (options.imageHashType == null && options.includeImageDataMD5 != null) {
|
||||
options.imageHashType = options.includeImageDataMD5 ? "MD5" : false;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
//# sourceMappingURL=ExifToolOptions.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExifToolOptions.js","sourceRoot":"","sources":["../src/ExifToolOptions.ts"],"names":[],"mappings":";;AAiWA,0DASC;AATD,SAAgB,uBAAuB,CAIrC,OAAU;IACV,IAAI,OAAO,CAAC,aAAa,IAAI,IAAI,IAAI,OAAO,CAAC,mBAAmB,IAAI,IAAI,EAAE,CAAC;QACzE,OAAO,CAAC,aAAa,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IACtE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { ExifToolTags } from "./Tags";
|
||||
export declare const ExifToolTagNames: (keyof ExifToolTags)[];
|
||||
/**
|
||||
* Is the given tag name intrinsic to the content of a given file? In other
|
||||
* words, is it not an artifact of a metadata field?
|
||||
*/
|
||||
export declare function isExifToolTag(name: string): name is keyof ExifToolTags;
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ExifToolTagNames = void 0;
|
||||
exports.isExifToolTag = isExifToolTag;
|
||||
const Object_1 = require("./Object");
|
||||
exports.ExifToolTagNames = (0, Object_1.keysOf)({
|
||||
ExifToolVersion: true,
|
||||
SourceFile: true,
|
||||
Error: true,
|
||||
Warning: true,
|
||||
});
|
||||
/**
|
||||
* Is the given tag name intrinsic to the content of a given file? In other
|
||||
* words, is it not an artifact of a metadata field?
|
||||
*/
|
||||
function isExifToolTag(name) {
|
||||
return exports.ExifToolTagNames.includes(name);
|
||||
}
|
||||
//# sourceMappingURL=ExifToolTags.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExifToolTags.js","sourceRoot":"","sources":["../src/ExifToolTags.ts"],"names":[],"mappings":";;;AAcA,sCAEC;AAhBD,qCAAkC;AAGrB,QAAA,gBAAgB,GAAG,IAAA,eAAM,EAAe;IACnD,eAAe,EAAE,IAAI;IACrB,UAAU,EAAE,IAAI;IAChB,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,IAAI;CACd,CAAC,CAAC;AAEH;;;GAGG;AACH,SAAgB,aAAa,CAAC,IAAY;IACxC,OAAO,wBAAgB,CAAC,QAAQ,CAAC,IAA0B,CAAC,CAAC;AAC/D,CAAC"}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import * as bc from "batch-cluster";
|
||||
import { ExifToolOptions } from "./ExifToolOptions";
|
||||
export type ExifToolTaskOptions = Pick<ExifToolOptions, "ignoreMinorErrors">;
|
||||
export declare abstract class ExifToolTask<T> extends bc.Task<T> {
|
||||
#private;
|
||||
readonly args: string[];
|
||||
readonly options?: ExifToolTaskOptions | undefined;
|
||||
static renderCommand(args: string[], options?: ExifToolTaskOptions): string;
|
||||
readonly errors: string[];
|
||||
readonly warnings: string[];
|
||||
constructor(args: string[], options?: ExifToolTaskOptions | undefined);
|
||||
onStderr(buf: string | Buffer): void;
|
||||
protected abstract parse(input: string, error?: Error): T;
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
"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.ExifToolTask = void 0;
|
||||
const bc = __importStar(require("batch-cluster"));
|
||||
const IsWarning_1 = require("./IsWarning");
|
||||
const String_1 = require("./String");
|
||||
const BadPerlInstallationRE = /Can't locate \S+ in @INC/i;
|
||||
class ExifToolTask extends bc.Task {
|
||||
args;
|
||||
options;
|
||||
static renderCommand(args, options) {
|
||||
const result = args.filter((ea) => !(0, String_1.blank)(ea));
|
||||
if (options?.ignoreMinorErrors === true) {
|
||||
result.push("-ignoreMinorErrors");
|
||||
}
|
||||
result.push("-execute");
|
||||
return result.join("\n") + "\n";
|
||||
}
|
||||
errors = [];
|
||||
warnings = [];
|
||||
constructor(args, options) {
|
||||
super(ExifToolTask.renderCommand(args, options), (stdout, stderr, passed) => this.#parser(stdout, stderr, passed));
|
||||
this.args = args;
|
||||
this.options = options;
|
||||
}
|
||||
onStderr(buf) {
|
||||
if (BadPerlInstallationRE.test(buf.toString())) {
|
||||
// This isn't an error we can recover from: there's a Perl module that
|
||||
// needs to be installed. See
|
||||
// https://github.com/photostructure/exiftool-vendored.js/issues/168 for
|
||||
// details.
|
||||
throw new Error(buf.toString());
|
||||
}
|
||||
super.onStderr(buf);
|
||||
}
|
||||
#parser(stdout, stderr, passed) {
|
||||
let error;
|
||||
if ((0, String_1.notBlank)(stderr) || !passed) {
|
||||
for (const line of (0, String_1.splitLines)(stderr ?? "")) {
|
||||
if ((0, IsWarning_1.isWarning)(line)) {
|
||||
this.warnings.push(line);
|
||||
}
|
||||
else if (/error|warning/i.test(line)) {
|
||||
this.errors.push(line);
|
||||
// new Error() will add a "Error: " prefix:
|
||||
error ??= new Error(line.replace(/^error: /i, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.parse(stdout, error);
|
||||
}
|
||||
}
|
||||
exports.ExifToolTask = ExifToolTask;
|
||||
//# sourceMappingURL=ExifToolTask.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExifToolTask.js","sourceRoot":"","sources":["../src/ExifToolTask.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,kDAAoC;AAEpC,2CAAwC;AAExC,qCAAuD;AAEvD,MAAM,qBAAqB,GAAG,2BAA2B,CAAC;AAI1D,MAAsB,YAAgB,SAAQ,EAAE,CAAC,IAAO;IAc3C;IACA;IAdX,MAAM,CAAC,aAAa,CAAC,IAAc,EAAE,OAA6B;QAChE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAA,cAAK,EAAC,EAAE,CAAC,CAAC,CAAC;QAC/C,IAAI,OAAO,EAAE,iBAAiB,KAAK,IAAI,EAAE,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACpC,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACxB,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClC,CAAC;IAEQ,MAAM,GAAa,EAAE,CAAC;IACtB,QAAQ,GAAa,EAAE,CAAC;IAEjC,YACW,IAAc,EACd,OAA6B;QAEtC,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,CAC1E,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CACrC,CAAC;QALO,SAAI,GAAJ,IAAI,CAAU;QACd,YAAO,GAAP,OAAO,CAAsB;IAKxC,CAAC;IAEQ,QAAQ,CAAC,GAAoB;QACpC,IAAI,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;YAC/C,sEAAsE;YACtE,6BAA6B;YAC7B,wEAAwE;YACxE,WAAW;YACX,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClC,CAAC;QACD,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,CAAC,MAAc,EAAE,MAA0B,EAAE,MAAe;QACjE,IAAI,KAAmB,CAAC;QACxB,IAAI,IAAA,iBAAQ,EAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,KAAK,MAAM,IAAI,IAAI,IAAA,mBAAU,EAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC5C,IAAI,IAAA,qBAAS,EAAC,IAAI,CAAC,EAAE,CAAC;oBACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC3B,CAAC;qBAAM,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oBACvC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;oBACvB,2CAA2C;oBAC3C,KAAK,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;gBACrD,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACnC,CAAC;CAGF;AAlDD,oCAkDC"}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { ErrorsAndWarnings } from "./ErrorsAndWarnings";
|
||||
import { StrEnum, StrEnumKeys } from "./StrEnum";
|
||||
/**
|
||||
* This tags are added to {@link Tags} from this library.
|
||||
*/
|
||||
export interface ExifToolVendoredTags extends ErrorsAndWarnings {
|
||||
/**
|
||||
* Either an offset, like `UTC-7`, or an actual IANA timezone, like
|
||||
* `America/Los_Angeles`.
|
||||
*
|
||||
* This will be missing if we can't intuit a timezone from the metadata.
|
||||
* @deprecated use `zone` instead
|
||||
*/
|
||||
tz?: string;
|
||||
/**
|
||||
* The IANA timezone, like `America/Los_Angeles`, or a IANA-rendered static offset, like `UTC-7`.
|
||||
*
|
||||
* This will be missing if we can't intuit a timezone from the metadata.
|
||||
*/
|
||||
zone?: string;
|
||||
/**
|
||||
* Description of where and how `tz` was extracted
|
||||
* @deprecated use `zoneSource` instead
|
||||
*/
|
||||
tzSource?: string;
|
||||
/**
|
||||
* Description of where and how `zone` was extracted
|
||||
*/
|
||||
zoneSource?: string;
|
||||
}
|
||||
export declare const ExifToolVendoredTagNames: StrEnum<"zone" | "tz" | "tzSource" | "zoneSource" | "errors" | "warnings">;
|
||||
export type ExifToolVendoredTagName = StrEnumKeys<typeof ExifToolVendoredTagNames>;
|
||||
export declare function isExifToolVendoredTag(name: string): name is keyof ExifToolVendoredTags;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ExifToolVendoredTagNames = void 0;
|
||||
exports.isExifToolVendoredTag = isExifToolVendoredTag;
|
||||
const StrEnum_1 = require("./StrEnum");
|
||||
exports.ExifToolVendoredTagNames = (0, StrEnum_1.strEnum)("tz", "zone", "tzSource", "zoneSource", "errors", "warnings");
|
||||
function isExifToolVendoredTag(name) {
|
||||
return exports.ExifToolVendoredTagNames.includes(name);
|
||||
}
|
||||
//# sourceMappingURL=ExifToolVendoredTags.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExifToolVendoredTags.js","sourceRoot":"","sources":["../src/ExifToolVendoredTags.ts"],"names":[],"mappings":";;;AAgDA,sDAIC;AAnDD,uCAA0D;AAkC7C,QAAA,wBAAwB,GAAG,IAAA,iBAAO,EAC7C,IAAI,EACJ,MAAM,EACN,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,UAAU,CACmC,CAAC;AAMhD,SAAgB,qBAAqB,CACnC,IAAY;IAEZ,OAAO,gCAAwB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACjD,CAAC"}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
import { Logger } from "batch-cluster";
|
||||
/**
|
||||
* This implementation relies on the fact that both `exiftool-vendored.pl` and
|
||||
* `exiftool-vendored.exe` both export the path to their respective exiftool
|
||||
* binary.
|
||||
*
|
||||
* When running in node, this method should suffice.
|
||||
*
|
||||
* When running in Electron, all bets are off, due to ASAR packaging and other
|
||||
* nonsense. As perl can't run from within an ASAR archive, `electron-builder`
|
||||
* must be configured to `asarUnpack`
|
||||
* "**/node_modules/exiftool-vendored.{pl,exe}/". See
|
||||
* <https://www.electron.build/generated/platformspecificbuildoptions#configuration-asarUnpack>
|
||||
* for details.
|
||||
*
|
||||
* If you're using `electron-forge`, add something like the following to your
|
||||
* ForgeConfig.packagerConfig.extraResource array: `fs.join(".", "node_modules",
|
||||
* "exiftool-vendored." + (isWin ? "exe" : "pl"))` but then you must specify a
|
||||
* custom exiftoolPath in your options hash, as subprocesses that use
|
||||
* ELECTRON_RUN_AS_NODE will no longer have process.resourcesPath set.
|
||||
*
|
||||
* If none of the above work for your use case, you can provide your own
|
||||
* `exiftoolPath` implementation to your instance of ExifTool
|
||||
*
|
||||
* @return the path to the exiftool binary, preferring the vendored version in
|
||||
* node_modules.
|
||||
*/
|
||||
export declare function exiftoolPath(logger?: Logger): Promise<string>;
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
"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.exiftoolPath = exiftoolPath;
|
||||
const _fs = __importStar(require("node:fs"));
|
||||
const _path = __importStar(require("node:path"));
|
||||
const IsWin32_1 = require("./IsWin32");
|
||||
const Which_1 = require("./Which");
|
||||
function vendorPackage() {
|
||||
return "exiftool-vendored." + ((0, IsWin32_1.isWin32)() ? "exe" : "pl");
|
||||
}
|
||||
function tryRequire({ prefix = "", logger, } = {}) {
|
||||
const id = prefix + vendorPackage();
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
return require(id);
|
||||
}
|
||||
catch (error) {
|
||||
logger?.warn(id + "not found: ", error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This implementation relies on the fact that both `exiftool-vendored.pl` and
|
||||
* `exiftool-vendored.exe` both export the path to their respective exiftool
|
||||
* binary.
|
||||
*
|
||||
* When running in node, this method should suffice.
|
||||
*
|
||||
* When running in Electron, all bets are off, due to ASAR packaging and other
|
||||
* nonsense. As perl can't run from within an ASAR archive, `electron-builder`
|
||||
* must be configured to `asarUnpack`
|
||||
* "**/node_modules/exiftool-vendored.{pl,exe}/". See
|
||||
* <https://www.electron.build/generated/platformspecificbuildoptions#configuration-asarUnpack>
|
||||
* for details.
|
||||
*
|
||||
* If you're using `electron-forge`, add something like the following to your
|
||||
* ForgeConfig.packagerConfig.extraResource array: `fs.join(".", "node_modules",
|
||||
* "exiftool-vendored." + (isWin ? "exe" : "pl"))` but then you must specify a
|
||||
* custom exiftoolPath in your options hash, as subprocesses that use
|
||||
* ELECTRON_RUN_AS_NODE will no longer have process.resourcesPath set.
|
||||
*
|
||||
* If none of the above work for your use case, you can provide your own
|
||||
* `exiftoolPath` implementation to your instance of ExifTool
|
||||
*
|
||||
* @return the path to the exiftool binary, preferring the vendored version in
|
||||
* node_modules.
|
||||
*/
|
||||
async function exiftoolPath(logger) {
|
||||
const path = tryRequire({ prefix: "", logger });
|
||||
// This s/app.asar/app.asar.unpacked/ path switch adds support for Electron
|
||||
// apps whose modules are ASAR-packed (like by electron-builder).
|
||||
const asarUnpackedPath = path
|
||||
?.split(_path.sep)
|
||||
.map((ea) => (ea === "app.asar" ? "app.asar.unpacked" : ea))
|
||||
.join(_path.sep);
|
||||
// NOTE: we must check for the fixedPath FIRST, because Electron's ASAR
|
||||
// shenanigans will make existsSync return true for asar-packed resources
|
||||
if (asarUnpackedPath != null && _fs.existsSync(asarUnpackedPath)) {
|
||||
return asarUnpackedPath;
|
||||
}
|
||||
if (path != null && _fs.existsSync(path)) {
|
||||
return path;
|
||||
}
|
||||
logger?.warn("Failed to find exiftool via " + vendorPackage());
|
||||
// process.resourcesPath is set by electron-forge:
|
||||
const electronResourcePath = process
|
||||
.resourcesPath;
|
||||
if (electronResourcePath != null) {
|
||||
const forgePath = _path.join(electronResourcePath, vendorPackage(), "bin", "exiftool" + ((0, IsWin32_1.isWin32)() ? ".exe" : ""));
|
||||
if (_fs.existsSync(forgePath)) {
|
||||
return forgePath;
|
||||
}
|
||||
else {
|
||||
logger?.warn("Failed to find exiftool in electron forge resources path: " +
|
||||
forgePath);
|
||||
}
|
||||
}
|
||||
// Last ditch: is there a globally installed exiftool in the PATH?
|
||||
const fromPath = await (0, Which_1.which)("exiftool");
|
||||
if (fromPath != null) {
|
||||
return fromPath;
|
||||
}
|
||||
throw new Error(`Failed to find ExifTool installation: set exiftoolPath explicitly.`);
|
||||
}
|
||||
//# sourceMappingURL=ExiftoolPath.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ExiftoolPath.js","sourceRoot":"","sources":["../src/ExiftoolPath.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,oCAkDC;AApGD,6CAA+B;AAC/B,iDAAmC;AACnC,uCAAoC;AAEpC,mCAAgC;AAEhC,SAAS,aAAa;IACpB,OAAO,oBAAoB,GAAG,CAAC,IAAA,iBAAO,GAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,UAAU,CAAC,EAClB,MAAM,GAAG,EAAE,EACX,MAAM,MACyC,EAAE;IACjD,MAAM,EAAE,GAAG,MAAM,GAAG,aAAa,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,iEAAiE;QACjE,OAAO,OAAO,CAAC,EAAE,CAAC,CAAC;IACrB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,aAAa,EAAE,KAAK,CAAC,CAAC;QACxC,OAAO;IACT,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACI,KAAK,UAAU,YAAY,CAAC,MAAe;IAChD,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAChD,2EAA2E;IAC3E,iEAAiE;IAEjE,MAAM,gBAAgB,GAAG,IAAI;QAC3B,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;SACjB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,UAAU,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;SAC3D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAEnB,uEAAuE;IACvE,yEAAyE;IACzE,IAAI,gBAAgB,IAAI,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACjE,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IACD,IAAI,IAAI,IAAI,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACzC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,EAAE,IAAI,CAAC,8BAA8B,GAAG,aAAa,EAAE,CAAC,CAAC;IAE/D,kDAAkD;IAClD,MAAM,oBAAoB,GAAI,OAAsC;SACjE,aAAa,CAAC;IACjB,IAAI,oBAAoB,IAAI,IAAI,EAAE,CAAC;QACjC,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAC1B,oBAAoB,EACpB,aAAa,EAAE,EACf,KAAK,EACL,UAAU,GAAG,CAAC,IAAA,iBAAO,GAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CACvC,CAAC;QACF,IAAI,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC9B,OAAO,SAAS,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,IAAI,CACV,4DAA4D;gBAC1D,SAAS,CACZ,CAAC;QACJ,CAAC;IACH,CAAC;IAED,kEAAkE;IAClE,MAAM,QAAQ,GAAG,MAAM,IAAA,aAAK,EAAC,UAAU,CAAC,CAAC;IACzC,IAAI,QAAQ,IAAI,IAAI,EAAE,CAAC;QACrB,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,IAAI,KAAK,CACb,oEAAoE,CACrE,CAAC;AACJ,CAAC"}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export declare function isFileEmpty(path: string): Promise<boolean>;
|
||||
export declare const isPlatformCaseSensitive: () => boolean;
|
||||
export declare function compareFilePaths(a: string, b: string): boolean;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isPlatformCaseSensitive = void 0;
|
||||
exports.isFileEmpty = isFileEmpty;
|
||||
exports.compareFilePaths = compareFilePaths;
|
||||
const node_fs_1 = require("node:fs");
|
||||
const node_path_1 = require("node:path");
|
||||
const Lazy_1 = require("./Lazy");
|
||||
const String_1 = require("./String");
|
||||
async function isFileEmpty(path) {
|
||||
if ((0, String_1.blank)(path)) {
|
||||
throw new Error("isFileEmpty(): blank path");
|
||||
}
|
||||
// TODO: convert this to using fs/promises once node 12 is EOL (2022-04-30)
|
||||
try {
|
||||
const s = await new Promise((res, rej) => {
|
||||
try {
|
||||
(0, node_fs_1.stat)(path, (err, val) => (err == null ? res(val) : rej(err)));
|
||||
}
|
||||
catch (err) {
|
||||
rej(err);
|
||||
}
|
||||
});
|
||||
return s == null || s.size === 0;
|
||||
}
|
||||
catch (err) {
|
||||
if (err &&
|
||||
typeof err === "object" &&
|
||||
"code" in err &&
|
||||
err.code === "ENOENT")
|
||||
return true;
|
||||
else
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
exports.isPlatformCaseSensitive = (0, Lazy_1.lazy)(() => process.platform !== "win32" && process.platform !== "darwin");
|
||||
function compareFilePaths(a, b) {
|
||||
const aNorm = (0, node_path_1.normalize)(a);
|
||||
const bNorm = (0, node_path_1.normalize)(b);
|
||||
return (0, exports.isPlatformCaseSensitive)()
|
||||
? aNorm === bNorm
|
||||
: aNorm.localeCompare(bNorm, undefined, { sensitivity: "base" }) === 0;
|
||||
}
|
||||
//# sourceMappingURL=File.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"File.js","sourceRoot":"","sources":["../src/File.ts"],"names":[],"mappings":";;;AAKA,kCAyBC;AAMD,4CAMC;AA1CD,qCAAsC;AACtC,yCAAsC;AACtC,iCAA8B;AAC9B,qCAAiC;AAE1B,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,IAAA,cAAK,EAAC,IAAI,CAAC,EAAE,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC/C,CAAC;IAED,2EAA2E;IAC3E,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,IAAI,OAAO,CAAQ,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC9C,IAAI,CAAC;gBACH,IAAA,cAAI,EAAC,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAChE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,GAAG,CAAC,CAAC;YACX,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IACE,GAAG;YACH,OAAO,GAAG,KAAK,QAAQ;YACvB,MAAM,IAAI,GAAG;YACb,GAAG,CAAC,IAAI,KAAK,QAAQ;YAErB,OAAO,IAAI,CAAC;;YACT,MAAM,GAAG,CAAC;IACjB,CAAC;AACH,CAAC;AAEY,QAAA,uBAAuB,GAAG,IAAA,WAAI,EACzC,GAAG,EAAE,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,CACpE,CAAC;AAEF,SAAgB,gBAAgB,CAAC,CAAS,EAAE,CAAS;IACnD,MAAM,KAAK,GAAG,IAAA,qBAAS,EAAC,CAAC,CAAC,CAAC;IAC3B,MAAM,KAAK,GAAG,IAAA,qBAAS,EAAC,CAAC,CAAC,CAAC;IAC3B,OAAO,IAAA,+BAAuB,GAAE;QAC9B,CAAC,CAAC,KAAK,KAAK,KAAK;QACjB,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { FileTags } from "./Tags";
|
||||
/**
|
||||
* Is the given tag name intrinsic to the content of a given file?
|
||||
*
|
||||
* In other words, is it not an artifact of a metadata field?
|
||||
*/
|
||||
export declare function isFileTag(name: string): name is keyof FileTags;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isFileTag = isFileTag;
|
||||
const Object_1 = require("./Object");
|
||||
const FileTagNames = (0, Object_1.keysOf)({
|
||||
BMPVersion: true,
|
||||
BitsPerSample: true,
|
||||
ColorComponents: true,
|
||||
CurrentIPTCDigest: true,
|
||||
Directory: true,
|
||||
EncodingProcess: true,
|
||||
ExifByteOrder: true,
|
||||
FileAccessDate: true,
|
||||
FileCreateDate: true,
|
||||
FileInodeChangeDate: true,
|
||||
FileModifyDate: true,
|
||||
FileName: true,
|
||||
FilePermissions: true,
|
||||
FileSize: true,
|
||||
FileType: true,
|
||||
FileTypeExtension: true,
|
||||
ImageDataMD5: true,
|
||||
ImageHeight: true,
|
||||
ImageWidth: true,
|
||||
MIMEType: true,
|
||||
NumColors: true,
|
||||
NumImportantColors: true,
|
||||
PixelsPerMeterX: true,
|
||||
PixelsPerMeterY: true,
|
||||
Planes: true,
|
||||
YCbCrSubSampling: true,
|
||||
});
|
||||
/**
|
||||
* Is the given tag name intrinsic to the content of a given file?
|
||||
*
|
||||
* In other words, is it not an artifact of a metadata field?
|
||||
*/
|
||||
function isFileTag(name) {
|
||||
return FileTagNames.includes(name);
|
||||
}
|
||||
//# sourceMappingURL=FileTags.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"FileTags.js","sourceRoot":"","sources":["../src/FileTags.ts"],"names":[],"mappings":";;AAqCA,8BAEC;AAvCD,qCAAkC;AAGlC,MAAM,YAAY,GAAG,IAAA,eAAM,EAAW;IACpC,UAAU,EAAE,IAAI;IAChB,aAAa,EAAE,IAAI;IACnB,eAAe,EAAE,IAAI;IACrB,iBAAiB,EAAE,IAAI;IACvB,SAAS,EAAE,IAAI;IACf,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,IAAI;IACpB,cAAc,EAAE,IAAI;IACpB,mBAAmB,EAAE,IAAI;IACzB,cAAc,EAAE,IAAI;IACpB,QAAQ,EAAE,IAAI;IACd,eAAe,EAAE,IAAI;IACrB,QAAQ,EAAE,IAAI;IACd,QAAQ,EAAE,IAAI;IACd,iBAAiB,EAAE,IAAI;IACvB,YAAY,EAAE,IAAI;IAClB,WAAW,EAAE,IAAI;IACjB,UAAU,EAAE,IAAI;IAChB,QAAQ,EAAE,IAAI;IACd,SAAS,EAAE,IAAI;IACf,kBAAkB,EAAE,IAAI;IACxB,eAAe,EAAE,IAAI;IACrB,eAAe,EAAE,IAAI;IACrB,MAAM,EAAE,IAAI;IACZ,gBAAgB,EAAE,IAAI;CACvB,CAAC,CAAC;AAEH;;;;GAIG;AACH,SAAgB,SAAS,CAAC,IAAY;IACpC,OAAO,YAAY,CAAC,QAAQ,CAAC,IAAsB,CAAC,CAAC;AACvD,CAAC"}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Tells ExifTool to handle UTF-8-encoded filenames on Windows.
|
||||
*
|
||||
* Without this setting, non-latin filename characters on Windows won't be
|
||||
* encoded correctly, and the file will be erroneously reported as not
|
||||
* existing.
|
||||
*
|
||||
* This setting isn't necessary on non-Windows platforms, nor on Windows
|
||||
* machines that have been configured to use UTF-8, but it doesn't hurt
|
||||
* anything to include this option.
|
||||
*
|
||||
* @see https://github.com/photostructure/exiftool-vendored.js/issues/124
|
||||
* @see https://exiftool.org/exiftool_pod.html#WINDOWS-UNICODE-FILE-NAMES
|
||||
* @see https://exiftool.org/faq.html#Q10
|
||||
*/
|
||||
export declare const Utf8FilenameCharsetArgs: string[];
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Utf8FilenameCharsetArgs = void 0;
|
||||
const IsWin32_1 = require("./IsWin32");
|
||||
/**
|
||||
* Tells ExifTool to handle UTF-8-encoded filenames on Windows.
|
||||
*
|
||||
* Without this setting, non-latin filename characters on Windows won't be
|
||||
* encoded correctly, and the file will be erroneously reported as not
|
||||
* existing.
|
||||
*
|
||||
* This setting isn't necessary on non-Windows platforms, nor on Windows
|
||||
* machines that have been configured to use UTF-8, but it doesn't hurt
|
||||
* anything to include this option.
|
||||
*
|
||||
* @see https://github.com/photostructure/exiftool-vendored.js/issues/124
|
||||
* @see https://exiftool.org/exiftool_pod.html#WINDOWS-UNICODE-FILE-NAMES
|
||||
* @see https://exiftool.org/faq.html#Q10
|
||||
*/
|
||||
exports.Utf8FilenameCharsetArgs = (0, IsWin32_1.isWin32)()
|
||||
? ["-charset", "filename=utf8"]
|
||||
: [];
|
||||
//# sourceMappingURL=FilenameCharsetArgs.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"FilenameCharsetArgs.js","sourceRoot":"","sources":["../src/FilenameCharsetArgs.ts"],"names":[],"mappings":";;;AAAA,uCAAoC;AAEpC;;;;;;;;;;;;;;GAcG;AACU,QAAA,uBAAuB,GAAG,IAAA,iBAAO,GAAE;IAC9C,CAAC,CAAC,CAAC,UAAU,EAAE,eAAe,CAAC;IAC/B,CAAC,CAAC,EAAE,CAAC"}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { ExifDateTime } from "./ExifDateTime";
|
||||
import { Maybe } from "./Maybe";
|
||||
import { MaybeReadonly } from "./MaybeReadonly";
|
||||
import { Tags } from "./Tags";
|
||||
/**
|
||||
* Returns the first date/time tag in `dateTimeTags` that strictly parses from
|
||||
* EXIF.
|
||||
*/
|
||||
export declare function firstDateTime(tags: Maybe<Tags>, dateTimeTags?: MaybeReadonly<(keyof Tags)[]>): Maybe<ExifDateTime>;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.firstDateTime = firstDateTime;
|
||||
const CapturedAtTagNames_1 = require("./CapturedAtTagNames");
|
||||
const ExifDateTime_1 = require("./ExifDateTime");
|
||||
const String_1 = require("./String");
|
||||
/**
|
||||
* Returns the first date/time tag in `dateTimeTags` that strictly parses from
|
||||
* EXIF.
|
||||
*/
|
||||
function firstDateTime(tags, dateTimeTags = CapturedAtTagNames_1.CapturedAtTagNames) {
|
||||
for (const tag of dateTimeTags) {
|
||||
const dt = tags?.[tag];
|
||||
if (dt instanceof ExifDateTime_1.ExifDateTime) {
|
||||
return dt;
|
||||
}
|
||||
else if ((0, String_1.isString)(dt)) {
|
||||
const edt = ExifDateTime_1.ExifDateTime.fromEXIF(dt);
|
||||
if (edt != null) {
|
||||
return edt;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
//# sourceMappingURL=FirstDateTime.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"FirstDateTime.js","sourceRoot":"","sources":["../src/FirstDateTime.ts"],"names":[],"mappings":";;AAWA,sCAgBC;AA3BD,6DAA0D;AAC1D,iDAA8C;AAG9C,qCAAoC;AAGpC;;;GAGG;AACH,SAAgB,aAAa,CAC3B,IAAiB,EACjB,eAA8C,uCAAkB;IAEhE,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,EAAE,GAAG,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,EAAE,YAAY,2BAAY,EAAE,CAAC;YAC/B,OAAO,EAAE,CAAC;QACZ,CAAC;aAAM,IAAI,IAAA,iBAAQ,EAAC,EAAE,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,2BAAY,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;YACtC,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;gBAChB,OAAO,GAAG,CAAC;YACb,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO;AACT,CAAC"}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { ExifToolOptions } from "./ExifToolOptions";
|
||||
import { Maybe } from "./Maybe";
|
||||
export type GpsLocationTags = {
|
||||
GPSLatitude?: number;
|
||||
GPSLatitudeRef?: string;
|
||||
GPSLongitude?: number;
|
||||
GPSLongitudeRef?: string;
|
||||
GPSPosition?: string;
|
||||
GeolocationPosition?: string;
|
||||
};
|
||||
export declare const GpsLocationTagNames: (keyof GpsLocationTags)[];
|
||||
export interface GpsParseResult {
|
||||
result: GpsLocationTags;
|
||||
details: string;
|
||||
invalid: boolean;
|
||||
warnings: string[];
|
||||
}
|
||||
export declare function parseGPSLocation(tags: GpsLocationTags, opts: Pick<ExifToolOptions, "ignoreZeroZeroLatLon">): Maybe<Partial<GpsParseResult>>;
|
||||
Loaded 100 of 5445 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user