@eris/exif
Advanced tools
Comparing version 0.3.1-alpha.0 to 0.3.1-alpha.1
@@ -6,4 +6,5 @@ import { IBufferLike, IGenericMetadata } from '../utils/types'; | ||
private _handleMatch; | ||
private _decodeKeywords; | ||
extractMetadata(): IGenericMetadata; | ||
static isXMP(buffer: IBufferLike): boolean; | ||
} |
@@ -12,2 +12,18 @@ "use strict"; | ||
} | ||
function getXMLTagRegExp(tag, flags) { | ||
return new RegExp(`<${tag}>((.|\\s)*?)</${tag}>`, flags); | ||
} | ||
function findXMLTag(text, tag) { | ||
const regex = getXMLTagRegExp(tag, 'i'); | ||
const match = text.match(regex); | ||
if (!match) | ||
return null; | ||
return { innerXML: match[1] }; | ||
} | ||
function findXMLTags(text, tag) { | ||
const matches = text.match(getXMLTagRegExp(tag, 'ig')); | ||
if (!matches) | ||
return []; | ||
return matches.map(item => findXMLTag(item, tag)); | ||
} | ||
class XMPDecoder { | ||
@@ -35,2 +51,14 @@ constructor(buffer) { | ||
} | ||
_decodeKeywords(genericMetadata) { | ||
const subjectEl = findXMLTag(this._text, 'dc:subject'); | ||
if (!subjectEl) | ||
return; | ||
const bagEl = findXMLTag(subjectEl.innerXML, 'rdf:Bag'); | ||
if (!bagEl) | ||
return; | ||
const keywords = findXMLTags(bagEl.innerXML, 'rdf:li'); | ||
if (!keywords.length) | ||
return; | ||
genericMetadata.DCSubjectBagOfWords = JSON.stringify(keywords.map(item => item.innerXML)); | ||
} | ||
extractMetadata() { | ||
@@ -44,2 +72,3 @@ const metadata = {}; | ||
} | ||
this._decodeKeywords(metadata); | ||
return metadata; | ||
@@ -46,0 +75,0 @@ } |
@@ -8,3 +8,5 @@ import { IGenericMetadata, IBufferLike } from '../utils/types'; | ||
private static _findExistingTag; | ||
private static _handleKeywords; | ||
private static _generateKeywordsPayload; | ||
private static _findIndexOfRdfDescriptionEnd; | ||
} |
@@ -5,2 +5,3 @@ "use strict"; | ||
const log_1 = require("../utils/log"); | ||
const keywords_parser_1 = require("../metadata/keywords-parser"); | ||
const log = log_1.createLogger('xmp-encoder'); | ||
@@ -26,2 +27,3 @@ const XMP_BASE_FILE = ` | ||
const XMP_PACKET_END = '<?xpacket end="w"?>'; | ||
const BASE_NEWLINE = '\n '; | ||
class XMPEncoder { | ||
@@ -55,7 +57,26 @@ static isWrappedInPacket(xmpData) { | ||
log(`examining ${tagName}`); | ||
if (typeof metadata[tagName] === 'undefined') | ||
continue; | ||
if (!(tagName in tags_1.xmpTags)) | ||
continue; | ||
const value = metadata[tagName]; | ||
if (tagName === 'DCSubjectBagOfWords') { | ||
const result = XMPEncoder._handleKeywords(xmpData, metadata); | ||
xmpData = result.xmpData; | ||
extraLength += result.extraLength; | ||
continue; | ||
} | ||
if (typeof value === 'undefined') { | ||
log(`unsetting ${tagName}`); | ||
const existing = XMPEncoder._findExistingTag(xmpData, tagName); | ||
if (!existing) | ||
continue; | ||
let preamble = xmpData.slice(0, existing.start); | ||
const postamble = xmpData.slice(existing.start + existing.length); | ||
if (postamble.startsWith('\n')) | ||
preamble = preamble.replace(/\n +$/, ''); | ||
const beforeLength = xmpData.length; | ||
xmpData = `${preamble}${postamble}`; | ||
const afterLength = xmpData.length; | ||
extraLength += afterLength - beforeLength; | ||
continue; | ||
} | ||
log(`writing ${tagName} as "${value}"`); | ||
@@ -65,2 +86,3 @@ const existing = XMPEncoder._findExistingTag(xmpData, tagName); | ||
if (existing) { | ||
log(`found existing ${tagName} - ${existing.value}`); | ||
const preamble = xmpData.slice(0, existing.start); | ||
@@ -73,6 +95,7 @@ const postamble = xmpData.slice(existing.start + existing.length); | ||
else { | ||
log(`did not find existing ${tagName}`); | ||
const rdfEnd = XMPEncoder._findIndexOfRdfDescriptionEnd(xmpData); | ||
const preamble = xmpData.slice(0, rdfEnd); | ||
const postamble = xmpData.slice(rdfEnd); | ||
const replacementWithNewline = `\n ${replacement}`; | ||
const replacementWithNewline = `${BASE_NEWLINE}${replacement}`; | ||
xmpData = `${preamble}${replacementWithNewline}${postamble}`; | ||
@@ -89,2 +112,4 @@ extraLength += replacementWithNewline.length; | ||
if (existingWhitespaceLength > extraLength) { | ||
// We only need to adjust the whitespace if we had enough room to fit our data into it | ||
log(`adjusting whitespace, our ${extraLength} will fit into ${existingWhitespaceLength}`); | ||
const indexOfMatch = xmpData.indexOf(existingWhitespaceMatch[0]); | ||
@@ -109,2 +134,50 @@ const preamble = xmpData.slice(0, indexOfMatch); | ||
} | ||
static _handleKeywords(xmpData, metadata) { | ||
const newKeywords = keywords_parser_1.parseKeywords(metadata); | ||
const existingKeywordsMatch = xmpData.match(/<dc:subject>(.|\s)*?<\/dc:subject>/); | ||
if (!newKeywords || !newKeywords.length) { | ||
// Nothing to remove, move on | ||
if (!existingKeywordsMatch) | ||
return { xmpData, extraLength: 0 }; | ||
// Remove the payload | ||
const indexOfMatch = existingKeywordsMatch.index; | ||
const original = existingKeywordsMatch[0]; | ||
const preamble = xmpData.slice(0, indexOfMatch); | ||
const postamble = xmpData.slice(indexOfMatch + original.length); | ||
return { | ||
xmpData: `${preamble}${postamble}`, | ||
extraLength: -original.length, | ||
}; | ||
} | ||
const replacement = XMPEncoder._generateKeywordsPayload(newKeywords); | ||
if (existingKeywordsMatch) { | ||
const indexOfMatch = existingKeywordsMatch.index; | ||
const original = existingKeywordsMatch[0]; | ||
const preamble = xmpData.slice(0, indexOfMatch); | ||
const postamble = xmpData.slice(indexOfMatch + original.length); | ||
return { | ||
xmpData: `${preamble}${replacement}${postamble}`, | ||
extraLength: replacement.length - original.length, | ||
}; | ||
} | ||
else { | ||
const rdfEnd = XMPEncoder._findIndexOfRdfDescriptionEnd(xmpData) + 1; | ||
const preamble = xmpData.slice(0, rdfEnd); | ||
const postamble = xmpData.slice(rdfEnd); | ||
const replacementWithNewline = `${BASE_NEWLINE}${replacement}`; | ||
return { | ||
xmpData: `${preamble}${replacementWithNewline}${postamble}`, | ||
extraLength: replacementWithNewline.length, | ||
}; | ||
} | ||
} | ||
static _generateKeywordsPayload(keywords) { | ||
return [ | ||
`<dc:subject>`, | ||
` <rdf:Bag>`, | ||
...keywords.map(word => ` <rdf:li>${word.replace(/</g, '')}</rdf:li>`), | ||
` </rdf:Bag>`, | ||
`</dc:subject>`, | ||
].join(BASE_NEWLINE); | ||
} | ||
static _findIndexOfRdfDescriptionEnd(xmp) { | ||
@@ -111,0 +184,0 @@ const regex = /<rdf:Description(\s*(\w+:\w+=".*?")\s*)*?>/im; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const date_parser_1 = require("../metadata/date-parser"); | ||
const lens_parser_1 = require("../metadata/lens-parser"); | ||
const date_parser_1 = require("./date-parser"); | ||
const keywords_parser_1 = require("./keywords-parser"); | ||
const lens_parser_1 = require("./lens-parser"); | ||
const properties = { | ||
@@ -24,2 +25,3 @@ // TODO: look into how to normalize GPS coordinates | ||
colorLabel: ['Label'], | ||
keywords: [keywords_parser_1.parseKeywords], | ||
}; | ||
@@ -26,0 +28,0 @@ function getResultValue(item, results) { |
@@ -12,2 +12,3 @@ "use strict"; | ||
MetadataDate: true, | ||
DCSubjectBagOfWords: true, | ||
}; | ||
@@ -14,0 +15,0 @@ // TODO: fill in all IFDDataTypes with -1 |
@@ -120,5 +120,6 @@ /// <reference types="node" /> | ||
colorLabel?: 'Blue' | 'Red' | 'Purple' | 'Yellow' | 'Green'; | ||
keywords?: string[]; | ||
} | ||
export declare function getDataTypeSize(dataType: number, name?: string | number): number; | ||
export declare type XMPTagName = 'Rating' | 'Label' | 'MetadataDate'; | ||
export declare type XMPTagName = 'Rating' | 'Label' | 'MetadataDate' | 'DCSubjectBagOfWords'; | ||
export declare type IFDTagName = 'Unknown' | 'ImageWidth' | 'NewSubfileType' | 'SubfileType' | 'ImageWidth' | 'ImageLength' | 'BitsPerSample' | 'Compression' | 'PhotometricInterpretation' | 'Thresholding' | 'CellWidth' | 'CellLength' | 'FillOrder' | 'DocumentName' | 'ImageDescription' | 'Make' | 'Model' | 'StripOffsets' | 'Orientation' | 'SamplesPerPixel' | 'RowsPerStrip' | 'StripByteCounts' | 'MinSampleValue' | 'MaxSampleValue' | 'XResolution' | 'YResolution' | 'PlanarConfiguration' | 'PageName' | 'XPosition' | 'YPosition' | 'FreeOffsets' | 'FreeByteCounts' | 'GrayResponseUnit' | 'GrayResponseCurve' | 'T4Options' | 'T6Options' | 'ResolutionUnit' | 'PageNumber' | 'ColorResponseUnit' | 'TransferFunction' | 'Software' | 'ModifyDate' | 'Artist' | 'HostComputer' | 'Predictor' | 'WhitePoint' | 'PrimaryChromaticities' | 'ColorMap' | 'HalftoneHints' | 'TileWidth' | 'TileLength' | 'TileOffsets' | 'TileByteCounts' | 'BadFaxLines' | 'CleanFaxData' | 'ConsecutiveBadFaxLines' | 'SubIFD' | 'InkSet' | 'InkNames' | 'NumberOfInks' | 'DotRange' | 'TargetPrinter' | 'ExtraSamples' | 'SampleFormat' | 'SMinSampleValue' | 'SMaxSampleValue' | 'TransferRange' | 'ClipPath' | 'XClipPathUnits' | 'YClipPathUnits' | 'Indexed' | 'JPEGTables' | 'OPIProxy' | 'GlobalParametersIFD' | 'ProfileType' | 'FaxProfile' | 'CodingMethods' | 'VersionYear' | 'ModeNumber' | 'Decode' | 'DefaultImageColor' | 'T82Options' | 'JPEGProc' | 'JPEGInterchangeFormat' | 'JPEGInterchangeFormatLength' | 'JPEGRestartInterval' | 'JPEGLosslessPredictors' | 'JPEGPointTransforms' | 'JPEGQTables' | 'JPEGDCTables' | 'JPEGACTables' | 'YCbCrCoefficients' | 'YCbCrSubSampling' | 'YCbCrPositioning' | 'ReferenceBlackWhite' | 'StripRowCounts' | 'XMLPacket' | 'USPTOMiscellaneous' | 'RelatedImageFileFormat' | 'RelatedImageWidth' | 'RelatedImageHeight' | 'Rating' | 'XP_DIP_XML' | 'StitchInfo' | 'RatingPercent' | 'ImageID' | 'WangTag1' | 'WangAnnotation' | 'WangTag3' | 'WangTag4' | 'Matteing' | 'DataType' | 'ImageDepth' | 'TileDepth' | 'Model2' | 'CFARepeatPatternDim' | 'CFAPattern' | 'BatteryLevel' | 'KodakIFD' | 'Copyright' | 'ExposureTime' | 'FNumber' | 'MDFileTag' | 'MDScalePixel' | 'MDColorTable' | 'MDLabName' | 'MDSampleInfo' | 'MDPrepDate' | 'MDPrepTime' | 'MDFileUnits' | 'PixelScale' | 'AdventScale' | 'AdventRevision' | 'UIC1Tag' | 'UIC2Tag' | 'UIC3Tag' | 'UIC4Tag' | 'IPTC-NAA' | 'IntergraphPacketData' | 'IntergraphFlagRegisters' | 'IntergraphMatrix' | 'INGRReserved' | 'ModelTiePoint' | 'Site' | 'ColorSequence' | 'IT8Header' | 'RasterPadding' | 'BitsPerRunLength' | 'BitsPerExtendedRunLength' | 'ColorTable' | 'ImageColorIndicator' | 'BackgroundColorIndicator' | 'ImageColorValue' | 'BackgroundColorValue' | 'PixelIntensityRange' | 'TransparencyIndicator' | 'ColorCharacterization' | 'HCUsage' | 'TrapIndicator' | 'CMYKEquivalent' | 'SEMInfo' | 'AFCP_IPTC' | 'PixelMagicJBIGOptions' | 'ModelTransform' | 'WB_GRGBLevels' | 'LeafData' | 'PhotoshopSettings' | 'EXIFTag' | 'InterColorProfile' | 'TIFF_FXExtensions' | 'MultiProfiles' | 'SharedData' | 'T88Options' | 'ImageLayer' | 'GeoTiffDirectory' | 'GeoTiffDoubleParams' | 'GeoTiffAsciiParams' | 'ExposureProgram' | 'SpectralSensitivity' | 'GPSTag' | 'ISO' | 'Opto-ElectricConvFactor' | 'Interlace' | 'TimeZoneOffset' | 'SelfTimerMode' | 'SensitivityType' | 'StandardOutputSensitivity' | 'RecommendedExposureIndex' | 'ISOSpeed' | 'ISOSpeedLatitudeyyy' | 'ISOSpeedLatitudezzz' | 'FaxRecvParams' | 'FaxSubAddress' | 'FaxRecvTime' | 'LeafSubIFD' | 'EXIFVersion' | 'DateTimeOriginal' | 'CreateDate' | 'ComponentsConfiguration' | 'CompressedBitsPerPixel' | 'ShutterSpeedValue' | 'ApertureValue' | 'BrightnessValue' | 'ExposureCompensation' | 'MaxApertureValue' | 'SubjectDistance' | 'MeteringMode' | 'LightSource' | 'Flash' | 'FocalLength' | 'FlashEnergy' | 'SpatialFrequencyResponse' | 'Noise' | 'FocalPlaneXResolution' | 'FocalPlaneYResolution' | 'FocalPlaneResolutionUnit' | 'ImageNumber' | 'SecurityClassification' | 'ImageHistory' | 'SubjectArea' | 'ExposureIndex' | 'TIFFEPStandardID' | 'SensingMethod' | 'CIP3DataFile' | 'CIP3Sheet' | 'CIP3Side' | 'StoNits' | 'MakerNote' | 'UserComment' | 'SubSecTime' | 'SubSecTimeOriginal' | 'SubSecTimeDigitized' | 'MSDocumentText' | 'MSPropertySetStorage' | 'MSDocumentTextPosition' | 'ImageSourceData' | 'XPTitle' | 'XPComment' | 'XPAuthor' | 'XPKeywords' | 'XPSubject' | 'FlashpixVersion' | 'ColorSpace' | 'EXIFImageWidth' | 'EXIFImageHeight' | 'RelatedSoundFile' | 'InteropOffset' | 'SubjectLocation' | 'TIFF-EPStandardID' | 'FileSource' | 'SceneType' | 'CustomRendered' | 'ExposureMode' | 'WhiteBalance' | 'DigitalZoomRatio' | 'FocalLengthIn35mmFormat' | 'SceneCaptureType' | 'GainControl' | 'Contrast' | 'Saturation' | 'Sharpness' | 'DeviceSettingDescription' | 'SubjectDistanceRange' | 'ImageUniqueID' | 'OwnerName' | 'SerialNumber' | 'LensMake' | 'LensModel' | 'LensSerialNumber' | 'GDALMetadata' | 'GDALNoData' | 'Gamma' | 'ExpandSoftware' | 'ExpandLens' | 'ExpandFilm' | 'ExpandFilterLens' | 'ExpandScanner' | 'ExpandFlashLamp' | 'PixelFormat' | 'Transformation' | 'Uncompressed' | 'ImageType' | 'WidthResolution' | 'HeightResolution' | 'ImageOffset' | 'ImageByteCount' | 'AlphaOffset' | 'AlphaByteCount' | 'ImageDataDiscard' | 'AlphaDataDiscard' | 'OceScanjobDesc' | 'OceApplicationSelector' | 'OceIDNumber' | 'OceImageLogic' | 'Annotations' | 'PrintImageMatching' | 'USPTOOriginalContentType' | 'DNGVersion' | 'DNGBackwardVersion' | 'UniqueCameraModel' | 'LocalizedCameraModel' | 'CFAPlaneColor' | 'CFALayout' | 'LinearizationTable' | 'BlackLevelRepeatDim' | 'BlackLevel' | 'BlackLevelDeltaH' | 'BlackLevelDeltaV' | 'WhiteLevel' | 'DefaultScale' | 'DefaultCropOrigin' | 'DefaultCropSize' | 'ColorMatrix1' | 'ColorMatrix2' | 'CameraCalibration1' | 'CameraCalibration2' | 'ReductionMatrix1' | 'ReductionMatrix2' | 'AnalogBalance' | 'AsShotNeutral' | 'AsShotWhiteXY' | 'BaselineExposure' | 'BaselineNoise' | 'BaselineSharpness' | 'BayerGreenSplit' | 'LinearResponseLimit' | 'CameraSerialNumber' | 'LensInfo' | 'ChromaBlurRadius' | 'AntiAliasStrength' | 'ShadowScale' | 'DNGPrivateData' | 'MakerNoteSafety' | 'RawImageSegmentation' | 'CalibrationIlluminant1' | 'CalibrationIlluminant2' | 'BestQualityScale' | 'RawDataUniqueID' | 'AliasLayerMetadata' | 'OriginalRawFileName' | 'OriginalRawFileData' | 'ActiveArea' | 'MaskedAreas' | 'AsShotICCProfile' | 'AsShotPreProfileMatrix' | 'CurrentICCProfile' | 'CurrentPreProfileMatrix' | 'ColorimetricReference' | 'PanasonicTitle' | 'PanasonicTitle2' | 'CameraCalibrationSignature' | 'ProfileCalibrationSignature' | 'ProfileIFD' | 'AsShotProfileName' | 'NoiseReductionApplied' | 'ProfileName' | 'ProfileHueSatMapDims' | 'ProfileHueSatMapData1' | 'ProfileHueSatMapData2' | 'ProfileToneCurve' | 'ProfileEmbedPolicy' | 'ProfileCopyright' | 'ForwardMatrix1' | 'ForwardMatrix2' | 'PreviewApplicationName' | 'PreviewApplicationVersion' | 'PreviewSettingsName' | 'PreviewSettingsDigest' | 'PreviewColorSpace' | 'PreviewDateTime' | 'RawImageDigest' | 'OriginalRawFileDigest' | 'SubTileBlockSize' | 'RowInterleaveFactor' | 'ProfileLookTableDims' | 'ProfileLookTableData' | 'OpcodeList1' | 'OpcodeList2' | 'OpcodeList3' | 'NoiseProfile' | 'TimeCodes' | 'FrameRate' | 'TStop' | 'ReelName' | 'OriginalDefaultFinalSize' | 'OriginalBestQualitySize' | 'OriginalDefaultCropSize' | 'CameraLabel' | 'ProfileHueSatMapEncoding' | 'ProfileLookTableEncoding' | 'BaselineExposureOffset' | 'DefaultBlackRender' | 'NewRawImageDigest' | 'RawToPreviewGain' | 'DefaultUserCrop' | 'Padding' | 'OffsetSchema' | 'OwnerName' | 'SerialNumber' | 'Lens' | 'KDC_IFD' | 'RawFile' | 'Converter' | 'WhiteBalance' | 'Exposure' | 'Shadows' | 'Brightness' | 'Contrast' | 'Saturation' | 'Sharpness' | 'Smoothness' | 'MoireFilter' | 'GPSVersionID' | 'GPSLatitudeRef' | 'GPSLatitude' | 'GPSLongitudeRef' | 'GPSLongitude' | 'GPSAltitudeRef' | 'GPSAltitude' | 'GPSTimeStamp' | 'GPSSatellites' | 'GPSStatus' | 'GPSMeasureMode' | 'GPSDOP' | 'GPSSpeedRef' | 'GPSSpeed' | 'GPSTrackRef' | 'GPSTrack' | 'GPSImgDirectionRef' | 'GPSImgDirection' | 'GPSMapDatum' | 'GPSDestLatitudeRef' | 'GPSDestLatitude' | 'GPSDestLongitudeRef' | 'GPSDestLongitude' | 'GPSDestBearingRef' | 'GPSDestBearing' | 'GPSDestDistanceRef' | 'GPSDestDistance' | 'GPSProcessingMethod' | 'GPSAreaInformation' | 'GPSDateStamp' | 'GPSDifferential' | 'GPSHPositioningError'; |
@@ -15,2 +15,19 @@ import {IBufferLike, IGenericMetadata, IFDTagName, XMPTagName} from '../utils/types' | ||
function getXMLTagRegExp(tag: string, flags?: string): RegExp { | ||
return new RegExp(`<${tag}>((.|\\s)*?)</${tag}>`, flags) | ||
} | ||
function findXMLTag(text: string, tag: string): {innerXML: string} | null { | ||
const regex = getXMLTagRegExp(tag, 'i') | ||
const match = text.match(regex) | ||
if (!match) return null | ||
return {innerXML: match[1]} | ||
} | ||
function findXMLTags(text: string, tag: string): Array<{innerXML: string}> { | ||
const matches = text.match(getXMLTagRegExp(tag, 'ig')) | ||
if (!matches) return [] | ||
return matches.map(item => findXMLTag(item, tag)!) | ||
} | ||
export class XMPDecoder { | ||
@@ -41,2 +58,12 @@ private readonly _text: string | ||
private _decodeKeywords(genericMetadata: IGenericMetadata): void { | ||
const subjectEl = findXMLTag(this._text, 'dc:subject') | ||
if (!subjectEl) return | ||
const bagEl = findXMLTag(subjectEl.innerXML, 'rdf:Bag') | ||
if (!bagEl) return | ||
const keywords = findXMLTags(bagEl.innerXML, 'rdf:li') | ||
if (!keywords.length) return | ||
genericMetadata.DCSubjectBagOfWords = JSON.stringify(keywords.map(item => item.innerXML)) | ||
} | ||
public extractMetadata(): IGenericMetadata { | ||
@@ -52,2 +79,3 @@ const metadata: IGenericMetadata = {} | ||
this._decodeKeywords(metadata) | ||
return metadata | ||
@@ -54,0 +82,0 @@ } |
import {IGenericMetadata, IBufferLike} from '../utils/types' | ||
import {xmpTags} from '../utils/tags' | ||
import {createLogger} from '../utils/log' | ||
import {parseKeywords} from '../metadata/keywords-parser' | ||
@@ -27,2 +28,3 @@ const log = createLogger('xmp-encoder') | ||
const XMP_PACKET_END = '<?xpacket end="w"?>' | ||
const BASE_NEWLINE = '\n ' | ||
@@ -72,6 +74,27 @@ interface TagLocation { | ||
log(`examining ${tagName}`) | ||
if (typeof metadata[tagName] === 'undefined') continue | ||
if (!(tagName in xmpTags)) continue | ||
const value = metadata[tagName] | ||
if (tagName === 'DCSubjectBagOfWords') { | ||
const result = XMPEncoder._handleKeywords(xmpData, metadata) | ||
xmpData = result.xmpData | ||
extraLength += result.extraLength | ||
continue | ||
} | ||
if (typeof value === 'undefined') { | ||
log(`unsetting ${tagName}`) | ||
const existing = XMPEncoder._findExistingTag(xmpData, tagName) | ||
if (!existing) continue | ||
let preamble = xmpData.slice(0, existing.start) | ||
const postamble = xmpData.slice(existing.start + existing.length) | ||
if (postamble.startsWith('\n')) preamble = preamble.replace(/\n +$/, '') | ||
const beforeLength = xmpData.length | ||
xmpData = `${preamble}${postamble}` | ||
const afterLength = xmpData.length | ||
extraLength += afterLength - beforeLength | ||
continue | ||
} | ||
log(`writing ${tagName} as "${value}"`) | ||
@@ -83,2 +106,3 @@ | ||
if (existing) { | ||
log(`found existing ${tagName} - ${existing.value}`) | ||
const preamble = xmpData.slice(0, existing.start) | ||
@@ -90,6 +114,7 @@ const postamble = xmpData.slice(existing.start + existing.length) | ||
} else { | ||
log(`did not find existing ${tagName}`) | ||
const rdfEnd = XMPEncoder._findIndexOfRdfDescriptionEnd(xmpData) | ||
const preamble = xmpData.slice(0, rdfEnd) | ||
const postamble = xmpData.slice(rdfEnd) | ||
const replacementWithNewline = `\n ${replacement}` | ||
const replacementWithNewline = `${BASE_NEWLINE}${replacement}` | ||
xmpData = `${preamble}${replacementWithNewline}${postamble}` | ||
@@ -104,4 +129,7 @@ extraLength += replacementWithNewline.length | ||
if (!existingWhitespaceMatch) throw new Error('Cannot find XMP packet end') | ||
const existingWhitespaceLength = existingWhitespaceMatch[1].length | ||
if (existingWhitespaceLength > extraLength) { | ||
// We only need to adjust the whitespace if we had enough room to fit our data into it | ||
log(`adjusting whitespace, our ${extraLength} will fit into ${existingWhitespaceLength}`) | ||
const indexOfMatch = xmpData.indexOf(existingWhitespaceMatch[0]) | ||
@@ -128,2 +156,55 @@ const preamble = xmpData.slice(0, indexOfMatch) | ||
private static _handleKeywords( | ||
xmpData: string, | ||
metadata: IGenericMetadata, | ||
): {xmpData: string; extraLength: number} { | ||
const newKeywords = parseKeywords(metadata) | ||
const existingKeywordsMatch = xmpData.match(/<dc:subject>(.|\s)*?<\/dc:subject>/) | ||
if (!newKeywords || !newKeywords.length) { | ||
// Nothing to remove, move on | ||
if (!existingKeywordsMatch) return {xmpData, extraLength: 0} | ||
// Remove the payload | ||
const indexOfMatch = existingKeywordsMatch.index! | ||
const original = existingKeywordsMatch[0] | ||
const preamble = xmpData.slice(0, indexOfMatch) | ||
const postamble = xmpData.slice(indexOfMatch + original.length) | ||
return { | ||
xmpData: `${preamble}${postamble}`, | ||
extraLength: -original.length, | ||
} | ||
} | ||
const replacement = XMPEncoder._generateKeywordsPayload(newKeywords) | ||
if (existingKeywordsMatch) { | ||
const indexOfMatch = existingKeywordsMatch.index! | ||
const original = existingKeywordsMatch[0] | ||
const preamble = xmpData.slice(0, indexOfMatch) | ||
const postamble = xmpData.slice(indexOfMatch + original.length) | ||
return { | ||
xmpData: `${preamble}${replacement}${postamble}`, | ||
extraLength: replacement.length - original.length, | ||
} | ||
} else { | ||
const rdfEnd = XMPEncoder._findIndexOfRdfDescriptionEnd(xmpData) + 1 | ||
const preamble = xmpData.slice(0, rdfEnd) | ||
const postamble = xmpData.slice(rdfEnd) | ||
const replacementWithNewline = `${BASE_NEWLINE}${replacement}` | ||
return { | ||
xmpData: `${preamble}${replacementWithNewline}${postamble}`, | ||
extraLength: replacementWithNewline.length, | ||
} | ||
} | ||
} | ||
private static _generateKeywordsPayload(keywords: string[]): string { | ||
return [ | ||
`<dc:subject>`, | ||
` <rdf:Bag>`, | ||
...keywords.map(word => ` <rdf:li>${word.replace(/</g, '')}</rdf:li>`), | ||
` </rdf:Bag>`, | ||
`</dc:subject>`, | ||
].join(BASE_NEWLINE) | ||
} | ||
private static _findIndexOfRdfDescriptionEnd(xmp: string): number { | ||
@@ -130,0 +211,0 @@ const regex = /<rdf:Description(\s*(\w+:\w+=".*?")\s*)*?>/im |
@@ -1,3 +0,4 @@ | ||
import {parseDate} from '../metadata/date-parser' | ||
import {parseLens} from '../metadata/lens-parser' | ||
import {parseDate} from './date-parser' | ||
import {parseKeywords} from './keywords-parser' | ||
import {parseLens} from './lens-parser' | ||
import {INormalizedMetadata, IGenericMetadata, IFDTagName, XMPTagName} from '../utils/types' | ||
@@ -40,2 +41,3 @@ | ||
colorLabel: ['Label'], | ||
keywords: [parseKeywords], | ||
} | ||
@@ -42,0 +44,0 @@ |
@@ -14,2 +14,3 @@ import {IIFDTagDefinition, IFDTagName, IFDGroup, IFDDataType, XMPTagName} from './types' | ||
MetadataDate: true, | ||
DCSubjectBagOfWords: true, | ||
} | ||
@@ -16,0 +17,0 @@ |
@@ -146,2 +146,3 @@ export interface ILogger { | ||
colorLabel?: 'Blue' | 'Red' | 'Purple' | 'Yellow' | 'Green' | ||
keywords?: string[] | ||
} | ||
@@ -172,3 +173,3 @@ | ||
export type XMPTagName = 'Rating' | 'Label' | 'MetadataDate' | ||
export type XMPTagName = 'Rating' | 'Label' | 'MetadataDate' | 'DCSubjectBagOfWords' | ||
@@ -175,0 +176,0 @@ export type IFDTagName = |
{ | ||
"name": "@eris/exif", | ||
"version": "0.3.1-alpha.0", | ||
"version": "0.3.1-alpha.1", | ||
"description": "Parses EXIF data.", | ||
@@ -49,3 +49,3 @@ "main": "./dist/index.js", | ||
}, | ||
"gitHead": "56e73cf75e7c5a72ac2053fb532041a77e3d12bd" | ||
"gitHead": "3ecabcb8242310727ef6c247feae54977438f9d2" | ||
} |
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
222216
72
4093