An advanced Node.js interface to the exiftool. π
βAn amazing level of senseless perfectionism, which is simply impossible not to respect.β
Exiftool is an amazing tool written by Phil Harvey in Perl which can read and write metadata to a number of file formats. This library supports streams. So, you don't have to handle anything from your end. It also cache network files to save your bandwidth.
npm install @enviro/metadata
- Exiftool need perl to be installed on your system, Download Perl.
- Node.js version >= 16.x
- ExifTool is no longer included with this package, Download ExifTool.
Read more about seamless ExifTool integration usingpath
option in documentation.
import Metadata from "@enviro/metadata";
// OPTIONAL
// only required if you are using user-defined tags
async function config() {
await Metadata.configurator({
default: true,
no_cache_cleanup: true,
tags: [
{
name: "CUSTOM_TAG",
type: "string",
exifPropGroup: "Exif",
exifPropSubGroup: "Main",
},
{
name: "CUSTOM_TAG",
type: "string",
exifPropGroup: "PDF",
exifPropSubGroup: "Info",
},
],
});
}
-
Name Type Description default
boolean
[Advanced]
Defaultundefined
The default configuration use hex keys in file. If you want to use key name in file, set this option explicitlyfalse
.keyCodeEvaluator
number
[Advanced]
The value must be greater than 53248. This is the default key for the first user-defined tag.no_cache_cleanup
boolean
This will stop auto cache cleaner on node server startup. tags
[ExifCustomTag]
Configure Exiftool to add or remove new metadata tags to the file. -
Name Type Description name
string
The name of the custom tag. type
"string"
Type of the custom tag's value. You can pass custom valid type in here which is supported by exif tag group. exifPropGroup
"Exif"
|"PDF"
|string
The custom tag's belongs to this group. Custom group can be passed as an argument. Read more at Family 0 (Information Type) exifPropSubGroup
"Main"
|"Info"
|string
The custom tag's belongs to this specific sub group. Custom sub-group can be passed as an argument. Read more at Family 1 (Specific Location)
-
-
async function read() { try { const metadata = await Metadata.get("test.pdf", { // path not required, since ExifTool is accessible from terminal using exiftool. // path: "exiftool", tags: [ { name: "FileName", exclude: true, }, ], }); console.log(metadata); } catch (e) { console.error(e); } }
[ { FileSize: "89 kB", FileModifyDate: "2022:11:11 17:08:58+05:00", FileAccessDate: "2022:11:11 17:08:58+05:00", FileInodeChangeDate: "2022:11:11 17:08:58+05:00", FilePermissions: "-rwxrwxrwx", FileType: "PDF", FileTypeExtension: "pdf", MIMEType: "application/pdf", PDFVersion: 1.3, Linearized: "No", Encryption: "Standard V1.2 (40-bit)", UserAccess: "Print, Copy, Annotate, Fill forms, Extract, Assemble, Print high-res", CreateDate: "2001:10:26 13:39:34", Producer: "Acrobat Distiller 4.05 for Windows", ModifyDate: "2001:10:26 13:40:41-04:00", Title: "PDF Bookmark Sample", Author: "Accelio Corporation", PageCount: 4, }, ];
FileName excluded from the result.
import { createReadStream } from "node:fs"; async function streamFile() { try { const rs = createReadStream("sample.pdf"); const metadata = await Metadata.get(rs, { path: "path/to/ExifTool", }); console.log(metadata); } catch (e) { console.error(e); } }
async function streamURL() { try { const metadata = await Metadata.get( "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf", { no_cache: true, } ); console.log(metadata); } catch (e) { console.error(e); } }
Name Type Description fileName
string
Read: Cache file name. no_cache
boolean
This option doesn't work on local file path passed as a parameter. no_cache: true
default for stream, read more about Stream caching.no_cache: false
default for network file, read more Network stream.tags
[ExifReadOPTag]
Filter the output metadata tags either excluded them or only include them. all
boolean
Default false
Iftrue
, all the metadata tags will be returned. This will override thetags
option.
NOTE: This option can cause significant performance issues. Use it only if you need all the metadata tags.batch
BatchReadOptions
Options for batch processing. -
Name Type Description name
string
Name of the metadata tag. exclude
boolean
exclude: true
exclude this tag from the result.exclude: false
make this tag exclusive.custom
string
[Advanced]
Custom Exiftool reading command can be directly passed tocustom
.
Read more about exiftool commands. -
Name Type Description no_network_cache
boolean
|[string]
Default false
.
Iftrue
, the network files will not be cached. If specified list of URLs provided, than that URLs will not be cached.no_stream_cache
[string]
Default null
.
If file name is not valid for stream perspective, then that specific stream will not be cached.[]
ornull
means all streams will be discarded after reading metadata.
-
async function write() { try { const metadata = await Metadata.set("sample.pdf", { tags: [ { name: "CUSTOM_TAG", value: "custom value", }, ], }); console.log(metadata); } catch (e) { console.error(e); } }
async function streamFile() { try { const rs = createReadStream("sample.pdf"); const metadata = await Metadata.set(rs, { path: "path/to/ExifTool", tags: [ { name: "CUSTOM_TAG", value: null, }, ], }); console.log(metadata); } catch (e) { console.error(e); } }
An empty tag will delete the tag from the file.
async function streamURL() { try { // NOTE: This pdf has a size of 26 MB const metadata = await Metadata.set( "https://research.nhm.org/pdfs/10840/10840.pdf", { path: "path/to/ExifTool", new: true, prev: true, tags: [ { name: "CUSTOM_TAG", value: "NEW VALUE", }, ], } ); console.log(metadata); } catch (e) { console.error(e); } }
{ message: 'File updated successfully!', // Path to the file which metadata was edited file: '../../dir/to/the/sample.pdf', // OR // This type of path will be generated for streams file: '../../path/to/dir/.tmp/0b00f9b2-cc7d-48b6-8fad-d7e2992de663' }
Name Type Description fileName
string
Custom cache file name. metadata
boolean
Default false
.
Returns the metadata of the file before modifying metadata will be returned.new
boolean
[!] Priority new
>metadata
.
Defaultfalse
.
Returns the metadata of the file after modifying metadata will be returned.all
boolean
Default false
.
Iftrue
, all the metadata tags will be returned. This will override thetags
option. NOTE: This option can cause significant performance issues. Use it only if you need all the metadata tags.delete_all
boolean
π΄ [UNSAFE]
Defaultfalse
.
Iftrue
all the metadata tags will be deleted from the file. Newtags
will remain unaffected.tags
[ExifWriteOPTag]
Add new or edit existing metadata tags to the file. -
Name Type Description name
string
The name of the metadata tag. If it's a custom tag, make sure to initialize the Metadata.configurator()
value
any
|null
The value of the metadata tag. If the tag has no value then it will be removed from the file. custom
string
[Advanced]
Custom Exiftool writing command can be directly passed tocustom
.
Read more about ExifTool commands.empty_tag
boolean
β [DEPRECATED]
Delete the current tag's value without deleting the whole tag from the file.
-
This function allows to copy the metadata from one file or directory to another file or directory.
async function copyFromFileToStream() { try { const stream = createReadStream("/path/to/dir/img_2.jpg"); const result = await Metadata.copy( "/path/to/dir/img_1.jpg", stream, { path: "path/to/ExifTool", src: { tags: [ { name: "Author", }, ], }, dst: { tags: [ { name: "Author", }, ], }, } ); console.log(JSON.stringify(result, null, 2)); } catch (e) { console.error(e); } }
The above code will copy the
Author
tag from image img_1.jpg and set it to img_2.jpg, but dst overrides theAuthor
tag to be deleted. Hence, the tag will be removed from the image img_2.jpg. -
These options can be passed to read, write, and copy methods.
-
Name Type Description path
string
[Required]
Default'exiftool'
.
The path of the ExifTool binaries.
NOTE: If exiftool is callable from CLI asperl exiftool
, thenpath
is optional.config
string
[Advanced]
The path to the config file to use with exiftool.del_cache_on_error
boolean
Default false
Iftrue
, the temporary file will be deleted if an error occurs.
Recommended to use with stream cache.stream
boolean
[Advanced]
Defaultfalse
.
Iftrue
, instead of returning metadata in a JSON compatible object, an instance of readable stream containing metadata string will be returned.
NOTE: This option will return raw metadata instead of JSON, custom parser will be required to parse JSON. JSON.parse() will always throw an error withfast: true
.
-
Metadata.clear();
async function streamURL() {
const metadata = await Metadata.get("https://example.com/imagine.pdf", {
no_cache: true,
});
console.log(metadata);
}
Explicitly set no_cache: true
to disable caching for network file.
async function streamFile() {
const rs = createReadStream("sample.pdf");
const metadata = await Metadata.get(rs, {
no_cache: false,
});
}
Explicitly set 'no_cache' to false to enable caching for stream.
-
If Metadata.configurator does not meet the requirements. You can provide a custom configuration file with Metadata.get() or Metadata.set() function options.
# Example config file %Image::ExifTool::UserDefined = ( 'Image::ExifTool::Exif::Main' => { d000 => { Name => 'CUSTOM_TAG', Writable => 'string', }, }, 'Image::ExifTool::PDF::Info' => { CUSTOM_TAG => { Name => 'CUSTOM_TAG', Writable => 'string', }, }, ); 1; #end
Read more: https://exiftool.org/config.html
async function read() { try { const metadata = await Metadata.get("image.jpg", { config: "../path/to/sample.cfg", }); console.log(metadata); } catch (e) { console.error(e); } }
-
This library does not support caching for stream passed as a parameter in such case you should provide something that could identify the cache file later. If you do not provide a cache file name a random name will be generated.
no_cache: false
will be ignored for stream passed as a parameter, iffileName
is not provided.import { createReadStream } from "node:fs"; const rs = createReadStream("sample.pdf"); const metadata = await Metadata.get(rs, { fileName: "sample.pdf", no_cache: false, }); console.log(metadata);
-
There is a significant overhead in loading ExifTool, so performance may be greatly improved by taking advantage of ExifTool's batch processing capabilities.
-
This allows to process multiple files in one function call, and helps to reduce the startup overhead. Sample batch processing code can be found in the examples/batch directory.
-
async function readMultipleFromDisk() { try { const metadata = await Metadata.get([ "/path/to/dir/samples/img_1.jpg", "/path/to/dir/samples/img_2.jpg", ... ]); console.log(metadata); } catch (e) { console.error(e); } }
-
async function writeMultipleFromDirectory() { try { const metadata = await Metadata.set("../../path/to/dir", { new: true, metadata: true, tags: [ { name: "Author", value: "John Doe", }, ], }); console.log(JSON.stringify(metadata, null, 4)); } catch (e) { console.error(e); } }
-
-
To execute complex or direct access to ExifTool use CLI method.
const command = `-j ~/path/to/file`; const subscriberId = Metadata.CLI.run(command || commands, (err, data) => { console.log(err || data); Metadata.CLI.stop(subscriberId); }, { path: exifToolPath });
β οΈ NOTE: If thecommand
contains arbitrary spaces, use array syntax to avoid confusion.
Example:
const command = [ '-j', '-Author="Foo Bar"', '"~path/to/file/containing spaces/file name.pdf"' ]
Remove the current CLI events listener once the task is finished to avoid receiving metadata for additional CLI tasks. Use
"queue"
option, which will handle everything for you.Metadata.CLI.stop(subscriberId);
This will automatically terminate the CLI process and all events listeners.
Metadata.CLI.kill();
More examples: examples/raw.js
Name Type Description path
string
[Required]
The path to the ExifTool binary.
NOTE:path
is optional, if ExifTool is callable from the CLI asperl exiftool
.config
string
The configuration file path. raw
boolean
[Advanced]
Defaultfalse
Iftrue
, the child process will be returned.streams
boolean
[Advanced]
Defaultfalse
Iftrue
, raw streams will be returned.queue
boolean
[Advanced]
Defaultfalse
Iftrue
, the stream observer will serve streams in queued order and will remove the callback from the stream observer once the command is fulfilled successfully or not. -
Experimental features can be unstable or maybe subject to change, using them in a production environment is not recommended.
Name Type Description fast
boolean
[Advanced]
Defaultfalse
Iftrue
, a dedicated process will be started for the binaries. This can significantly increase performance.
To exitfast
mode, useMetadata.stop();
- The package is pure ESM. It cannot be require() from CommonJS.
- Future version might break backward compatibility.
- Check release notes for breaking changes.
- This library is currently rewritten in TypeScript.
fast
mode will be enabled by default.
Examples can be found inside example directory.
- Comming Soon
If you discover any bug, create an issue.