Skip to content

Commit

Permalink
Merge pull request #59 from Arecsu/main
Browse files Browse the repository at this point in the history
feat: remove usage of `DOMParser`, decode is now compatible with Web Workers
  • Loading branch information
daniele-pelagatti authored Dec 5, 2024
2 parents 7604871 + 1a94472 commit 93bf1c2
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 85 deletions.
68 changes: 28 additions & 40 deletions src/decode/utils/extractXMP.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,51 @@
import { GainMapMetadata } from '../../core/types'

const getAttribute = (description: Element, name: string, defaultValue?: string) => {
let returnValue: string | [string, string, string]
const parsedValue = description.attributes.getNamedItem(name)?.nodeValue
if (!parsedValue) {
const node = description.getElementsByTagName(name)[0]
if (node) {
const values = node.getElementsByTagName('rdf:li')
if (values.length === 3) {
returnValue = Array.from(values).map(v => v.innerHTML) as [string, string, string]
} else {
throw new Error(`Gainmap metadata contains an array of items for ${name} but its length is not 3`)
}
} else {
if (defaultValue) return defaultValue
else throw new Error(`Can't find ${name} in gainmap metadata`)
const getXMLValue = (xml: string, tag: string, defaultValue?: string): string | [string, string, string] => {
// Check for attribute format first: tag="value"
const attributeMatch = new RegExp(`${tag}="([^"]*)"`, 'i').exec(xml)
if (attributeMatch) return attributeMatch[1]

// Check for tag format: <tag>value</tag> or <tag><rdf:li>value</rdf:li>...</tag>
const tagMatch = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, 'i').exec(xml)
if (tagMatch) {
// Check if it contains rdf:li elements
const liValues = tagMatch[1].match(/<rdf:li>([^<]*)<\/rdf:li>/g)
if (liValues && liValues.length === 3) {
return liValues.map(v => v.replace(/<\/?rdf:li>/g, '')) as [string, string, string]
}
} else {
returnValue = parsedValue
return tagMatch[1].trim()
}

return returnValue
if (defaultValue !== undefined) return defaultValue
throw new Error(`Can't find ${tag} in gainmap metadata`)
}

/**
*
* @param input
* @returns
*/
export const extractXMP = (input: Uint8Array): GainMapMetadata | undefined => {
let str: string
// support node test environment
if (typeof TextDecoder !== 'undefined') str = new TextDecoder().decode(input)
else str = input.toString()

let start = str.indexOf('<x:xmpmeta')
const parser = new DOMParser()

while (start !== -1) {
const end = str.indexOf('x:xmpmeta>', start)
str.slice(start, end + 10)
const xmpBlock = str.slice(start, end + 10)
try {
const xmlDocument = parser.parseFromString(xmpBlock, 'text/xml')
const description = xmlDocument.getElementsByTagName('rdf:Description')[0]

const gainMapMin = getAttribute(description, 'hdrgm:GainMapMin', '0')
const gainMapMax = getAttribute(description, 'hdrgm:GainMapMax')

const gamma = getAttribute(description, 'hdrgm:Gamma', '1')

const offsetSDR = getAttribute(description, 'hdrgm:OffsetSDR', '0.015625')
const offsetHDR = getAttribute(description, 'hdrgm:OffsetHDR', '0.015625')
try {
const gainMapMin = getXMLValue(xmpBlock, 'hdrgm:GainMapMin', '0')
const gainMapMax = getXMLValue(xmpBlock, 'hdrgm:GainMapMax')
const gamma = getXMLValue(xmpBlock, 'hdrgm:Gamma', '1')
const offsetSDR = getXMLValue(xmpBlock, 'hdrgm:OffsetSDR', '0.015625')
const offsetHDR = getXMLValue(xmpBlock, 'hdrgm:OffsetHDR', '0.015625')

let hdrCapacityMin = description.attributes.getNamedItem('hdrgm:HDRCapacityMin')?.nodeValue
if (!hdrCapacityMin) hdrCapacityMin = '0'
// These are always attributes, so we can use a simpler regex
const hdrCapacityMinMatch = /hdrgm:HDRCapacityMin="([^"]*)"/.exec(xmpBlock)
const hdrCapacityMin = hdrCapacityMinMatch ? hdrCapacityMinMatch[1] : '0'

const hdrCapacityMax = description.attributes.getNamedItem('hdrgm:HDRCapacityMax')?.nodeValue
if (!hdrCapacityMax) throw new Error('Incomplete gainmap metadata')
const hdrCapacityMaxMatch = /hdrgm:HDRCapacityMax="([^"]*)"/.exec(xmpBlock)
if (!hdrCapacityMaxMatch) throw new Error('Incomplete gainmap metadata')
const hdrCapacityMax = hdrCapacityMaxMatch[1]

return {
gainMapMin: Array.isArray(gainMapMin) ? gainMapMin.map(v => parseFloat(v)) as [number, number, number] : [parseFloat(gainMapMin), parseFloat(gainMapMin), parseFloat(gainMapMin)],
Expand All @@ -69,7 +57,7 @@ export const extractXMP = (input: Uint8Array): GainMapMetadata | undefined => {
hdrCapacityMax: parseFloat(hdrCapacityMax)
}
} catch (e) {

// Continue searching for another xmpmeta block if this one fails
}
start = str.indexOf('<x:xmpmeta', end)
}
Expand Down
75 changes: 30 additions & 45 deletions src/libultrahdr/decode-jpeg-metadata.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,6 @@
import { GainMapMetadata } from '../core/types'
import { getLibrary } from './library'

/**
* @deprecated
* @param description
* @param name
* @param defaultValue
* @returns
*/
/* istanbul ignore next */
const getAttribute = (description: Element, name: string, defaultValue?: string) => {
let returnValue: string | [string, string, string]
const parsedValue = description.attributes.getNamedItem(name)?.nodeValue
if (!parsedValue) {
const node = description.getElementsByTagName(name)[0]
if (node) {
const values = node.getElementsByTagName('rdf:li')
if (values.length === 3) {
returnValue = Array.from(values).map(v => v.innerHTML) as [string, string, string]
} else {
throw new Error(`Gainmap metadata contains an array of items for ${name} but its length is not 3`)
}
} else {
if (defaultValue) return defaultValue
else throw new Error(`Can't find ${name} in gainmap metadata`)
}
} else {
returnValue = parsedValue
}

return returnValue
}
/**
* Decodes a JPEG file with an embedded Gainmap and XMP Metadata (aka JPEG-R)
*
Expand All @@ -56,23 +26,41 @@ export const decodeJPEGMetadata = async (file: Uint8Array) => {
const result = lib.extractJpegR(file, file.length)
if (!result.success) throw new Error(`${result.errorMessage}`)

const parser = new DOMParser()
const xmlDocument = parser.parseFromString(result.metadata as string, 'text/xml')
const description = xmlDocument.getElementsByTagName('rdf:Description')[0]
const getXMLValue = (xml: string, tag: string, defaultValue?: string): string | [string, string, string] => {
// Check for attribute format first: tag="value"
const attributeMatch = new RegExp(`${tag}="([^"]*)"`, 'i').exec(xml)
if (attributeMatch) return attributeMatch[1]

// Check for tag format: <tag>value</tag> or <tag><rdf:li>value</rdf:li>...</tag>
const tagMatch = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)</${tag}>`, 'i').exec(xml)
if (tagMatch) {
// Check if it contains rdf:li elements
const liValues = tagMatch[1].match(/<rdf:li>([^<]*)<\/rdf:li>/g)
if (liValues && liValues.length === 3) {
return liValues.map(v => v.replace(/<\/?rdf:li>/g, '')) as [string, string, string]
}
return tagMatch[1].trim()
}

const gainMapMin = getAttribute(description, 'hdrgm:GainMapMin', '0')
const gainMapMax = getAttribute(description, 'hdrgm:GainMapMax')
if (defaultValue !== undefined) return defaultValue
throw new Error(`Can't find ${tag} in gainmap metadata`)
}

const gamma = getAttribute(description, 'hdrgm:Gamma', '1')
const metadata = result.metadata as string

const offsetSDR = getAttribute(description, 'hdrgm:OffsetSDR', '0.015625')
const offsetHDR = getAttribute(description, 'hdrgm:OffsetHDR', '0.015625')
const gainMapMin = getXMLValue(metadata, 'hdrgm:GainMapMin', '0')
const gainMapMax = getXMLValue(metadata, 'hdrgm:GainMapMax')
const gamma = getXMLValue(metadata, 'hdrgm:Gamma', '1')
const offsetSDR = getXMLValue(metadata, 'hdrgm:OffsetSDR', '0.015625')
const offsetHDR = getXMLValue(metadata, 'hdrgm:OffsetHDR', '0.015625')

let hdrCapacityMin = description.attributes.getNamedItem('hdrgm:HDRCapacityMin')?.nodeValue
if (!hdrCapacityMin) hdrCapacityMin = '0'
// These are always attributes, so we can use a simpler regex
const hdrCapacityMinMatch = /hdrgm:HDRCapacityMin="([^"]*)"/.exec(metadata)
const hdrCapacityMin = hdrCapacityMinMatch ? hdrCapacityMinMatch[1] : '0'

const hdrCapacityMax = description.attributes.getNamedItem('hdrgm:HDRCapacityMax')?.nodeValue
if (!hdrCapacityMax) throw new Error('Incomplete gainmap metadata')
const hdrCapacityMaxMatch = /hdrgm:HDRCapacityMax="([^"]*)"/.exec(metadata)
if (!hdrCapacityMaxMatch) throw new Error('Incomplete gainmap metadata')
const hdrCapacityMax = hdrCapacityMaxMatch[1]

const parsedMetadata: GainMapMetadata = {
gainMapMin: Array.isArray(gainMapMin) ? gainMapMin.map(v => parseFloat(v)) as [number, number, number] : [parseFloat(gainMapMin), parseFloat(gainMapMin), parseFloat(gainMapMin)],
Expand All @@ -86,9 +74,6 @@ export const decodeJPEGMetadata = async (file: Uint8Array) => {

return {
...result,
/**
* Parsed metadata
*/
parsedMetadata
}
}

0 comments on commit 93bf1c2

Please sign in to comment.