tethr
Version:
Controlls USB-connected cameras, webcam, and smartphone camera from browser
153 lines • 6.21 kB
JavaScript
import { times } from 'lodash';
import { PTPDataView } from './PTPDataView';
export var IFDType;
(function (IFDType) {
IFDType[IFDType["Byte"] = 1] = "Byte";
IFDType[IFDType["Ascii"] = 2] = "Ascii";
IFDType[IFDType["Short"] = 3] = "Short";
IFDType[IFDType["Long"] = 4] = "Long";
IFDType[IFDType["Rational"] = 5] = "Rational";
IFDType[IFDType["Undefined"] = 7] = "Undefined";
IFDType[IFDType["SignedShort"] = 8] = "SignedShort";
IFDType[IFDType["Slong"] = 9] = "Slong";
IFDType[IFDType["Srational"] = 10] = "Srational";
IFDType[IFDType["Float"] = 11] = "Float";
IFDType[IFDType["Double"] = 12] = "Double";
})(IFDType || (IFDType = {}));
export function decodeIFD(data, scheme) {
const dataView = new DataView(data);
const asciiDecoder = new TextDecoder('ascii');
const tagToEntry = new Map(Object.entries(scheme).map(([name, { tag, type }]) => {
return [tag, { name, type }];
}));
const result = {};
// The first 4 byte represents the size of packet.
const entryCount = dataView.getUint32(4, true);
for (let i = 0; i < entryCount; i++) {
// (packet size::Uint32) + (entry count::Uint32) = 8
const entryOffset = 8 + 12 * i;
// Read a directory entry
const tag = dataView.getUint16(entryOffset, true);
const type = dataView.getUint16(entryOffset + 2, true);
const count = dataView.getUint32(entryOffset + 4, true);
// If the data size exceeds 4 bytes, this represents a offset
// to the data. Oterwise, it's data itself.
const valueOffset = dataView.getUint32(entryOffset + 8, true);
const entryScheme = tagToEntry.get(tag);
if (!entryScheme)
continue;
if (type !== entryScheme.type)
throw new Error(`Invalid IFD: Tag type for entry ${entryScheme.name} does not match. Expected ${IFDType[entryScheme.type]}, got ${IFDType[type]}`);
let value = null;
switch (type) {
case IFDType.Byte: {
const offset = count > 4 ? valueOffset : entryOffset + 8;
const buf = data.slice(offset, offset + count);
value = [...new Uint8Array(buf)];
break;
}
case IFDType.Ascii: {
const buf = data.slice(valueOffset, valueOffset + count - 1);
value = asciiDecoder.decode(buf);
break;
}
case IFDType.Short: {
const offset = count > 2 ? valueOffset : entryOffset + 8;
const buf = data.slice(offset, offset + count * 2);
value = [...new Uint16Array(buf)];
break;
}
case IFDType.Long: {
const offset = count > 1 ? valueOffset : entryOffset + 8;
const buf = data.slice(offset, offset + count * 4);
value = [...new Uint32Array(buf)];
break;
}
case IFDType.Undefined: {
const offset = count > 4 ? valueOffset : entryOffset + 8;
value = data.slice(offset, offset + count);
break;
}
case IFDType.SignedShort: {
// Signed SHORT
const offset = count > 2 ? valueOffset : entryOffset + 8;
value = times(count, i => {
const f = dataView.getUint8(offset + i * 2);
const d = dataView.getInt8(offset + i * 2 + 1);
return d + f / 0x100;
});
break;
}
case IFDType.Float: {
const offset = count > 1 ? valueOffset : entryOffset + 8;
const buf = data.slice(offset, offset + count * 4);
value = [...new Float32Array(buf)];
break;
}
default:
throw new Error(`Type ${IFDType[type]} is not yet supported`);
}
result[entryScheme.name] = value;
}
return result;
}
export function encodeIFD(data) {
const dataView = new PTPDataView();
const entries = Object.values(data).sort((a, b) => {
if (a.tag === b.tag)
throw new Error('Same tag has found');
return a.tag < b.tag ? 1 : -1;
});
const entryCount = entries.length;
// the first 4 bytes represents the packet size,
// but it somehow works just to fill 'em with zeros, at least in Sigma fp
dataView.goto(4);
dataView.writeUint32(entryCount);
let dataOffset = 8 + entries.length * 12;
for (const [index, entry] of entries.entries()) {
const entryOffset = 8 + index * 12;
dataView.goto(entryOffset);
dataView.writeUint16(entry.tag);
dataView.writeUint16(entry.type);
const count = entry.value instanceof ArrayBuffer
? entry.value.byteLength
: entry.value.length;
dataView.writeUint32(count);
switch (entry.type) {
case IFDType.Byte: {
if (count > 4) {
throw new Error('Not yet supported');
}
else {
for (let i = 0; i < 4; i++) {
dataView.writeUint8(entry.value[i]);
}
}
break;
}
case IFDType.Short: {
if (entry.value.length > 2) {
dataView.writeUint32(dataOffset);
dataView.goto(dataOffset);
for (let i = 0; i < entry.value.length; i++) {
dataView.writeUint16(entry.value[i]);
}
dataOffset += entry.value.length * 2;
}
else {
for (let i = 0; i < 2; i++) {
dataView.writeUint16(entry.value[i]);
}
}
break;
}
default:
throw new Error(`Type ${IFDType[entry.type]} is not yet supported`);
}
}
dataView.writeCheckSum();
return dataView.toBuffer();
}
;
window.encodeIFD = encodeIFD;
//# sourceMappingURL=IFD.js.map