@eris/exif
Advanced tools
Comparing version 0.4.1-alpha.3 to 0.4.1-alpha.4
@@ -1,3 +0,5 @@ | ||
import { IIFDEntry, IReader, IFDTagName } from '../utils/types'; | ||
/// <reference types="node" /> | ||
import { IIFDEntry, IReader, IFDTagName, Endian } from '../utils/types'; | ||
export declare class IFDEntry implements IIFDEntry { | ||
startOffset: number; | ||
tag: number; | ||
@@ -7,3 +9,3 @@ dataType: number; | ||
private readonly dataReader; | ||
constructor(tag: number, dataType: number, length: number, dataReader: IReader); | ||
constructor(startOffset: number, tag: number, dataType: number, length: number, dataReader: IReader); | ||
readonly lengthInBytes: number; | ||
@@ -14,2 +16,3 @@ readonly friendlyTagName: IFDTagName; | ||
static read(reader: IReader): IFDEntry; | ||
static mutate(buffer: Buffer, simpleEntry: IIFDEntry, data: Buffer, endianness: Endian): Buffer; | ||
} |
@@ -6,5 +6,8 @@ "use strict"; | ||
const log_1 = require("../utils/log"); | ||
const writer_1 = require("../utils/writer"); | ||
const reader_1 = require("../utils/reader"); | ||
const log = log_1.createLogger('ifd-entry'); | ||
class IFDEntry { | ||
constructor(tag, dataType, length, dataReader) { | ||
constructor(startOffset, tag, dataType, length, dataReader) { | ||
this.startOffset = startOffset; | ||
this.tag = tag; | ||
@@ -70,2 +73,3 @@ this.dataType = dataType; | ||
static read(reader) { | ||
const startOffset = reader.getPosition(); | ||
const tag = reader.read(2); | ||
@@ -76,6 +80,29 @@ const dataType = reader.read(2); | ||
log.verbose(`read tag ${tag}`, dataReader); | ||
return new IFDEntry(tag, dataType, length, dataReader); | ||
return new IFDEntry(startOffset, tag, dataType, length, dataReader); | ||
} | ||
static mutate(buffer, simpleEntry, data, endianness) { | ||
if (simpleEntry.dataType !== types_1.IFDDataType.String) | ||
throw new Error('Can only mutate strings'); | ||
// Always ensure the string ends with null terminator | ||
if (data[data.length - 1] !== 0) | ||
data = Buffer.concat([data, Buffer.from([0])]); | ||
// Read the real IFD so we have something better to work with | ||
const reader = new reader_1.Reader(buffer, simpleEntry.startOffset); | ||
reader.setEndianess(endianness); | ||
const entry = IFDEntry.read(reader); | ||
if (entry.lengthInBytes <= 4) | ||
throw new Error('Can only change strings of length >3'); | ||
// Replace the length with the new length | ||
const newLength = data.length; | ||
const writer = new writer_1.Writer(buffer, { dangerouslyAvoidCopy: true }); | ||
writer.setEndianess(endianness); | ||
writer.seek(entry.startOffset + 4); | ||
writer.write(newLength, 4); | ||
// Replace the data itself | ||
entry.dataReader.seek(0); | ||
const dataOffset = entry.dataReader.read(4); | ||
return writer_1.Writer.spliceBufferRange(buffer, data, dataOffset, dataOffset + entry.lengthInBytes); | ||
} | ||
} | ||
exports.IFDEntry = IFDEntry; | ||
//# sourceMappingURL=ifd-entry.js.map |
@@ -1,3 +0,5 @@ | ||
import { IGenericMetadata, IBufferLike, IJPEGOptions } from '../utils/types'; | ||
/// <reference types="node" /> | ||
import { IGenericMetadata, IBufferLike, IJPEGOptions, IIFDEntry, IFDTagName } from '../utils/types'; | ||
export declare class TIFFDecoder { | ||
private readonly _buffer; | ||
private readonly _reader; | ||
@@ -15,2 +17,4 @@ private _ifds; | ||
extractMetadata(): IGenericMetadata; | ||
extractIFDEntries(): IIFDEntry[]; | ||
static replaceIFDEntry(decoder: TIFFDecoder, tag: IFDTagName, data: Buffer): Buffer; | ||
} |
@@ -10,5 +10,7 @@ "use strict"; | ||
const log_1 = require("../utils/log"); | ||
const ifd_entry_1 = require("./ifd-entry"); | ||
const log = log_1.createLogger('decoder'); | ||
class TIFFDecoder { | ||
constructor(buffer) { | ||
this._buffer = buffer; | ||
this._reader = new reader_1.Reader(buffer); | ||
@@ -166,4 +168,15 @@ } | ||
} | ||
extractIFDEntries() { | ||
this._readAndValidateHeader(); | ||
this._readIFDs(); | ||
return this._ifds.map(ifd => ifd.entries).reduce((a, b) => a.concat(b), []); | ||
} | ||
static replaceIFDEntry(decoder, tag, data) { | ||
const ifd = decoder.extractIFDEntries().find(ifd => tags_1.getFriendlyName(ifd.tag) === tag); | ||
if (!ifd) | ||
throw new Error(`Could not find "${tag}" in buffer`); | ||
return ifd_entry_1.IFDEntry.mutate(Buffer.from(decoder._buffer), ifd, data, decoder._reader.getEndianess()); | ||
} | ||
} | ||
exports.TIFFDecoder = TIFFDecoder; | ||
//# sourceMappingURL=tiff-decoder.js.map |
@@ -6,2 +6,3 @@ "use strict"; | ||
const keywords_parser_1 = require("../metadata/keywords-parser"); | ||
const writableTags = Object.assign({}, tags_1.xmpTags, { DateTimeOriginal: true }); | ||
const log = log_1.createLogger('xmp-encoder'); | ||
@@ -55,4 +56,4 @@ const XMP_BASE_FILE = ` | ||
const tagName = key; | ||
if (!(tagName in tags_1.xmpTags)) { | ||
log(`skipping ${tagName} which is not an XMP tag`); | ||
if (!(tagName in writableTags)) { | ||
log(`skipping ${tagName} which is not a writable tag`); | ||
continue; | ||
@@ -140,2 +141,4 @@ } | ||
static _buildReplacement(tagName, value) { | ||
if (tagName === 'DateTimeOriginal') | ||
return `exif:DateTimeOriginal="${value}"`; | ||
if (tagName !== 'DCSubjectBagOfWords') | ||
@@ -142,0 +145,0 @@ return `xmp:${tagName}="${value}"`; |
@@ -9,2 +9,3 @@ import { IBufferLike, IReader, Endian } from '../utils/types'; | ||
getPosition(): number; | ||
getEndianess(): Endian; | ||
setEndianess(endian: Endian): void; | ||
@@ -11,0 +12,0 @@ read(length: number): number; |
@@ -16,2 +16,5 @@ "use strict"; | ||
} | ||
getEndianess() { | ||
return this._endianness; | ||
} | ||
setEndianess(endian) { | ||
@@ -18,0 +21,0 @@ this._endianness = endian; |
@@ -16,2 +16,3 @@ /// <reference types="node" /> | ||
getPosition(): number; | ||
getEndianess(): Endian; | ||
setEndianess(endian: Endian): void; | ||
@@ -46,2 +47,3 @@ read(length: number): number; | ||
export interface IIFDEntry { | ||
startOffset: number; | ||
tag: number; | ||
@@ -95,2 +97,5 @@ dataType: number; | ||
} | ||
export interface IWriterOptions { | ||
dangerouslyAvoidCopy?: boolean; | ||
} | ||
export interface IJPEGOptions { | ||
@@ -97,0 +102,0 @@ skipMetadata?: boolean; |
@@ -1,2 +0,3 @@ | ||
import { IBufferLike, IWriter, Endian } from '../utils/types'; | ||
/// <reference types="node" /> | ||
import { IBufferLike, IWriter, Endian, IWriterOptions } from '../utils/types'; | ||
export declare class Writer implements IWriter { | ||
@@ -6,3 +7,3 @@ private readonly _bytes; | ||
private _endianness; | ||
constructor(buffer?: IBufferLike); | ||
constructor(buffer?: IBufferLike, options?: IWriterOptions); | ||
getPosition(): number; | ||
@@ -17,2 +18,3 @@ setEndianess(endian: Endian): void; | ||
toBuffer(): IBufferLike; | ||
static spliceBufferRange(buffer: Buffer, replacement: Buffer, start: number, end: number): Buffer; | ||
} |
@@ -5,8 +5,12 @@ "use strict"; | ||
class Writer { | ||
constructor(buffer) { | ||
constructor(buffer, options = {}) { | ||
this._bytes = []; | ||
this._position = 0; | ||
this._endianness = types_1.Endian.Big; | ||
if (buffer) | ||
this.writeBuffer(buffer); | ||
if (buffer) { | ||
if (options.dangerouslyAvoidCopy) | ||
this._bytes = buffer; | ||
else | ||
this.writeBuffer(buffer); | ||
} | ||
} | ||
@@ -67,4 +71,9 @@ getPosition() { | ||
} | ||
static spliceBufferRange(buffer, replacement, start, end) { | ||
const preamble = buffer.slice(0, start); | ||
const postamble = buffer.slice(end); | ||
return Buffer.concat([preamble, replacement, postamble]); | ||
} | ||
} | ||
exports.Writer = Writer; | ||
//# sourceMappingURL=writer.js.map |
import {getFriendlyName} from '../utils/tags' | ||
import {IFDDataType, IIFDEntry, IReader, IFDTagName, getDataTypeSize} from '../utils/types' | ||
import {IFDDataType, IIFDEntry, IReader, IFDTagName, getDataTypeSize, Endian} from '../utils/types' | ||
import {createLogger} from '../utils/log' | ||
import {Writer} from '../utils/writer' | ||
import {Reader} from '../utils/reader' | ||
@@ -9,2 +11,3 @@ const log = createLogger('ifd-entry') | ||
public constructor( | ||
public startOffset: number, | ||
public tag: number, | ||
@@ -80,2 +83,3 @@ public dataType: number, | ||
public static read(reader: IReader): IFDEntry { | ||
const startOffset = reader.getPosition() | ||
const tag = reader.read(2) | ||
@@ -87,4 +91,32 @@ const dataType = reader.read(2) | ||
log.verbose(`read tag ${tag}`, dataReader) | ||
return new IFDEntry(tag, dataType, length, dataReader) | ||
return new IFDEntry(startOffset, tag, dataType, length, dataReader) | ||
} | ||
public static mutate( | ||
buffer: Buffer, | ||
simpleEntry: IIFDEntry, | ||
data: Buffer, | ||
endianness: Endian, | ||
): Buffer { | ||
if (simpleEntry.dataType !== IFDDataType.String) throw new Error('Can only mutate strings') | ||
// Always ensure the string ends with null terminator | ||
if (data[data.length - 1] !== 0) data = Buffer.concat([data, Buffer.from([0])]) | ||
// Read the real IFD so we have something better to work with | ||
const reader = new Reader(buffer, simpleEntry.startOffset) | ||
reader.setEndianess(endianness) | ||
const entry = IFDEntry.read(reader) | ||
if (entry.lengthInBytes <= 4) throw new Error('Can only change strings of length >3') | ||
// Replace the length with the new length | ||
const newLength = data.length | ||
const writer = new Writer(buffer, {dangerouslyAvoidCopy: true}) | ||
writer.setEndianess(endianness) | ||
writer.seek(entry.startOffset + 4) | ||
writer.write(newLength, 4) | ||
// Replace the data itself | ||
entry.dataReader.seek(0) | ||
const dataOffset = entry.dataReader.read(4) | ||
return Writer.spliceBufferRange(buffer, data, dataOffset, dataOffset + entry.lengthInBytes) | ||
} | ||
} |
@@ -17,4 +17,7 @@ import {IFD} from '../decoder/ifd' | ||
IJPEGOptions, | ||
IIFDEntry, | ||
IFDTagName, | ||
} from '../utils/types' | ||
import {createLogger} from '../utils/log' | ||
import {IFDEntry} from './ifd-entry' | ||
@@ -30,2 +33,3 @@ const log = createLogger('decoder') | ||
export class TIFFDecoder { | ||
private readonly _buffer: IBufferLike | ||
private readonly _reader: IReader | ||
@@ -37,2 +41,3 @@ private _ifds: IIFD[] | ||
public constructor(buffer: IBufferLike) { | ||
this._buffer = buffer | ||
this._reader = new Reader(buffer) | ||
@@ -209,2 +214,14 @@ } | ||
} | ||
public extractIFDEntries(): IIFDEntry[] { | ||
this._readAndValidateHeader() | ||
this._readIFDs() | ||
return this._ifds.map(ifd => ifd.entries).reduce((a, b) => a.concat(b), []) | ||
} | ||
public static replaceIFDEntry(decoder: TIFFDecoder, tag: IFDTagName, data: Buffer): Buffer { | ||
const ifd = decoder.extractIFDEntries().find(ifd => getFriendlyName(ifd.tag) === tag) | ||
if (!ifd) throw new Error(`Could not find "${tag}" in buffer`) | ||
return IFDEntry.mutate(Buffer.from(decoder._buffer), ifd, data, decoder._reader.getEndianess()) | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import {IGenericMetadata, IBufferLike} from '../utils/types' | ||
import {IGenericMetadata, IBufferLike, XMPTagName} from '../utils/types' | ||
import {xmpTags} from '../utils/tags' | ||
@@ -6,2 +6,7 @@ import {createLogger} from '../utils/log' | ||
const writableTags: Record<XMPTagName | 'DateTimeOriginal', boolean> = { | ||
...xmpTags, | ||
DateTimeOriginal: true, | ||
} | ||
const log = createLogger('xmp-encoder') | ||
@@ -66,4 +71,4 @@ | ||
if (!(tagName in xmpTags)) { | ||
log(`skipping ${tagName} which is not an XMP tag`) | ||
if (!(tagName in writableTags)) { | ||
log(`skipping ${tagName} which is not a writable tag`) | ||
continue | ||
@@ -174,2 +179,3 @@ } | ||
): string { | ||
if (tagName === 'DateTimeOriginal') return `exif:DateTimeOriginal="${value}"` | ||
if (tagName !== 'DCSubjectBagOfWords') return `xmp:${tagName}="${value}"` | ||
@@ -176,0 +182,0 @@ const keywords = parseKeywords({DCSubjectBagOfWords: value}) |
@@ -18,2 +18,6 @@ import {IBufferLike, IReader, Endian} from '../utils/types' | ||
public getEndianess(): Endian { | ||
return this._endianness | ||
} | ||
public setEndianess(endian: Endian): void { | ||
@@ -20,0 +24,0 @@ this._endianness = endian |
@@ -19,2 +19,3 @@ export interface ILogger { | ||
getPosition(): number | ||
getEndianess(): Endian | ||
setEndianess(endian: Endian): void | ||
@@ -54,2 +55,3 @@ read(length: number): number | ||
export interface IIFDEntry { | ||
startOffset: number | ||
tag: number | ||
@@ -110,2 +112,6 @@ dataType: number | ||
export interface IWriterOptions { | ||
dangerouslyAvoidCopy?: boolean | ||
} | ||
export interface IJPEGOptions { | ||
@@ -112,0 +118,0 @@ skipMetadata?: boolean |
@@ -1,9 +0,9 @@ | ||
import {IBufferLike, IWriter, Endian} from '../utils/types' | ||
import {IBufferLike, IWriter, Endian, IWriterOptions} from '../utils/types' | ||
export class Writer implements IWriter { | ||
private readonly _bytes: number[] | ||
private readonly _bytes: number[] | IBufferLike | ||
private _position: number | ||
private _endianness: Endian | ||
public constructor(buffer?: IBufferLike) { | ||
public constructor(buffer?: IBufferLike, options: IWriterOptions = {}) { | ||
this._bytes = [] | ||
@@ -13,3 +13,6 @@ this._position = 0 | ||
if (buffer) this.writeBuffer(buffer) | ||
if (buffer) { | ||
if (options.dangerouslyAvoidCopy) this._bytes = buffer | ||
else this.writeBuffer(buffer) | ||
} | ||
} | ||
@@ -79,2 +82,13 @@ | ||
} | ||
public static spliceBufferRange( | ||
buffer: Buffer, | ||
replacement: Buffer, | ||
start: number, | ||
end: number, | ||
): Buffer { | ||
const preamble = buffer.slice(0, start) | ||
const postamble = buffer.slice(end) | ||
return Buffer.concat([preamble, replacement, postamble]) | ||
} | ||
} |
{ | ||
"name": "@eris/exif", | ||
"version": "0.4.1-alpha.3", | ||
"version": "0.4.1-alpha.4", | ||
"description": "Parses EXIF data.", | ||
@@ -40,3 +40,3 @@ "main": "./dist/index.js", | ||
}, | ||
"gitHead": "b8b08c09d1f2d26d4a5a9363866e43e340bf6c0f" | ||
"gitHead": "411a3177ed2583d528bbf2ecc0b769f9253d34c1" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
231278
4257