Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove DOMParser(), decode compatible with Web Workers #59

Merged
merged 1 commit into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
}