feat: improve metadata extraction for TIF (Siril FITS header), fix parseFitsHeader for line-separated format, add ISOSPEED/LIVETIME keywords, improve EXIF extraction for Canon CR2 and iPhone JPEGs
This commit is contained in:
1 parent
baf231d45c
commit
5573c124d5
1 file changed
+44
-8
+44
-8
@@ -1588,10 +1588,10 @@ function processStandard(file) {
|
|||||||
|
|
||||||
bufReader.onload = e => {
|
bufReader.onload = e => {
|
||||||
bufReader._bytes = new Uint8Array(e.target.result);
|
bufReader._bytes = new Uint8Array(e.target.result);
|
||||||
dataReader.readAsDataURL(file);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bufReader.readAsArrayBuffer(file);
|
bufReader.readAsArrayBuffer(file);
|
||||||
|
dataReader.readAsDataURL(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractExifFromJpegBuffer(bytes) {
|
function extractExifFromJpegBuffer(bytes) {
|
||||||
@@ -1704,6 +1704,27 @@ function buildMetaFromRaw(exif) {
|
|||||||
return { camera, lensModel, focalLength, aperture, shutter, iso, date };
|
return { camera, lensModel, focalLength, aperture, shutter, iso, date };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractTiffStringTag(bytes, targetTag) {
|
||||||
|
if (bytes.length < 8) return null;
|
||||||
|
const le = bytes[0] === 0x49;
|
||||||
|
const r16 = o => le ? bytes[o]|(bytes[o+1]<<8) : (bytes[o]<<8)|bytes[o+1];
|
||||||
|
const r32 = o => (le ? bytes[o]|(bytes[o+1]<<8)|(bytes[o+2]<<16)|(bytes[o+3]<<24) : (bytes[o]<<24)|(bytes[o+1]<<16)|(bytes[o+2]<<8)|bytes[o+3]) >>> 0;
|
||||||
|
const ifd0 = r32(4);
|
||||||
|
if (ifd0 + 2 > bytes.length) return null;
|
||||||
|
const count = r16(ifd0);
|
||||||
|
for (let i = 0; i < Math.min(count, 64); i++) {
|
||||||
|
const e = ifd0 + 2 + i * 12;
|
||||||
|
if (e + 12 > bytes.length) break;
|
||||||
|
if (r16(e) === targetTag && r16(e+2) === 2) {
|
||||||
|
const cnt = r32(e+4);
|
||||||
|
const doff = cnt > 4 ? r32(e+8) : e+8;
|
||||||
|
if (doff + cnt > bytes.length) return null;
|
||||||
|
return new TextDecoder('latin1').decode(bytes.slice(doff, doff+cnt)).replace(/\0/g,'').trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function processTiff(file) {
|
function processTiff(file) {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = e => {
|
reader.onload = e => {
|
||||||
@@ -1724,12 +1745,22 @@ function processTiff(file) {
|
|||||||
const imgData = tempCtx.createImageData(w, h);
|
const imgData = tempCtx.createImageData(w, h);
|
||||||
imgData.data.set(rgba);
|
imgData.data.set(rgba);
|
||||||
tempCtx.putImageData(imgData, 0, 0);
|
tempCtx.putImageData(imgData, 0, 0);
|
||||||
const meta = extractMetaFromTiffIfd(ifd);
|
const meta = (() => {
|
||||||
|
const tiffMeta = extractMetaFromTiffIfd(ifd);
|
||||||
|
const hasData = !!(tiffMeta.camera || tiffMeta.shutter || tiffMeta.iso || tiffMeta.focalLength);
|
||||||
|
if (hasData) return tiffMeta;
|
||||||
|
const descBytes = new Uint8Array(buffer);
|
||||||
|
const descTag = extractTiffStringTag(descBytes, 0x010E);
|
||||||
|
if (descTag && descTag.includes('SIMPLE')) {
|
||||||
|
try { return parseFitsHeader(descTag); } catch(e) {}
|
||||||
|
}
|
||||||
|
return tiffMeta;
|
||||||
|
})();
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
|
const cardIsShowing = currentImg !== null;
|
||||||
currentImg = img;
|
currentImg = img;
|
||||||
clearStatus();
|
clearStatus();
|
||||||
const cardIsShowing = currentImg !== null;
|
|
||||||
if (cardIsShowing) {
|
if (cardIsShowing) {
|
||||||
renderCard(img, readEditFields());
|
renderCard(img, readEditFields());
|
||||||
} else {
|
} else {
|
||||||
@@ -1814,10 +1845,15 @@ function processFits(file) {
|
|||||||
|
|
||||||
function parseFitsHeader(text) {
|
function parseFitsHeader(text) {
|
||||||
const meta = {};
|
const meta = {};
|
||||||
const cards = [];
|
|
||||||
for (let i = 0; i + 80 <= text.length; i += 80) {
|
const isFixed = text.length >= 80 && !text.slice(0, 80).includes('\n');
|
||||||
cards.push(text.slice(i, i + 80));
|
let cards = [];
|
||||||
|
if (isFixed) {
|
||||||
|
for (let i = 0; i + 80 <= text.length; i += 80) cards.push(text.slice(i, i + 80));
|
||||||
|
} else {
|
||||||
|
cards = text.split('\n').map(l => l.padEnd(80));
|
||||||
}
|
}
|
||||||
|
|
||||||
const get = key => {
|
const get = key => {
|
||||||
const card = cards.find(c => c.slice(0,8).trim() === key);
|
const card = cards.find(c => c.slice(0,8).trim() === key);
|
||||||
if (!card) return null;
|
if (!card) return null;
|
||||||
@@ -1854,7 +1890,7 @@ function parseFitsHeader(text) {
|
|||||||
const stackcnt = getNum('STACKCNT') || getNum('NCOMBINE') || getNum('NFRAMES');
|
const stackcnt = getNum('STACKCNT') || getNum('NCOMBINE') || getNum('NFRAMES');
|
||||||
meta.frames = stackcnt ? stackcnt + ' frames' : null;
|
meta.frames = stackcnt ? stackcnt + ' frames' : null;
|
||||||
|
|
||||||
const totalexp = getNum('TOTALEXP');
|
const totalexp = getNum('TOTALEXP') || getNum('LIVETIME');
|
||||||
if (totalexp !== null) {
|
if (totalexp !== null) {
|
||||||
const h = Math.floor(totalexp / 3600);
|
const h = Math.floor(totalexp / 3600);
|
||||||
const m = Math.floor((totalexp % 3600) / 60);
|
const m = Math.floor((totalexp % 3600) / 60);
|
||||||
@@ -1867,7 +1903,7 @@ function parseFitsHeader(text) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const gain = getNum('GAIN');
|
const gain = getNum('GAIN');
|
||||||
const iso = getNum('ISO');
|
const iso = getNum('ISO') || getNum('ISOSPEED');
|
||||||
if (iso !== null) meta.iso = 'ISO ' + iso;
|
if (iso !== null) meta.iso = 'ISO ' + iso;
|
||||||
else if (gain !== null) meta.iso = 'Gain ' + gain;
|
else if (gain !== null) meta.iso = 'Gain ' + gain;
|
||||||
else meta.iso = null;
|
else meta.iso = null;
|
||||||
|
|||||||
Reference in new issue
Block a user