twonly-app-dependencies/exif/lib/src/reader.dart
2026-05-31 01:24:27 +02:00

251 lines
6.8 KiB
Dart

import 'dart:typed_data';
import 'package:exif/src/exif_types.dart';
import 'package:exif/src/field_types.dart';
import 'package:exif/src/file_interface.dart';
import 'package:exif/src/makernote_canon.dart';
import 'package:exif/src/util.dart';
class Reader {
FileReader file;
int baseOffset;
Endian endian;
Reader(this.file, this.baseOffset, this.endian);
List<int> readSlice(int relativePos, int length) {
file.setPositionSync(baseOffset + relativePos);
return file.readSync(length);
}
// Convert slice to integer, based on sign and endian flags.
// Usually this offset is assumed to be relative to the beginning of the
// start of the EXIF information.
// For some cameras that use relative tags, this offset may be relative
// to some other starting point.
int readInt(int offset, int length, {bool signed = false}) {
final sliced = readSlice(offset, length);
int val;
if (endian == Endian.little) {
val = s2nLittleEndian(sliced, signed: signed);
} else {
val = s2nBigEndian(sliced, signed: signed);
}
return val;
}
Ratio readRatio(int offset, {required bool signed}) {
final n = readInt(offset, 4, signed: signed);
final d = readInt(offset + 4, 4, signed: signed);
return Ratio(n, d);
}
// Convert offset to string.
List<int> offsetToBytes(int readOffset, int length) {
final List<int> s = [];
for (int dummy = 0; dummy < length; dummy++) {
if (endian == Endian.little) {
s.add(readOffset & 0xFF);
} else {
s.insert(0, readOffset & 0xFF);
}
readOffset = readOffset >> 8;
}
return s;
}
static Endian endianOfByte(int b) {
if (b == 'I'.codeUnitAt(0)) {
return Endian.little;
}
return Endian.big;
}
}
class IfdReader {
Reader file;
final bool fakeExif;
IfdReader(this.file, {required this.fakeExif});
// Return first IFD.
int _firstIfd() => file.readInt(4, 4);
// Return the pointer to next IFD.
int _nextIfd(int ifd) {
final entries = file.readInt(ifd, 2);
final nextIfd = file.readInt(ifd + 2 + 12 * entries, 4);
if (nextIfd == ifd) {
return 0;
} else {
return nextIfd;
}
}
// Return the list of IFDs in the header.
List<int> listIfd() {
int i = _firstIfd();
final List<int> ifds = [];
while (i > 0) {
ifds.add(i);
i = _nextIfd(i);
}
return ifds;
}
List<IfdEntry> readIfdEntries(int ifd, {required bool relative}) {
final numEntries = file.readInt(ifd, 2);
return List<IfdEntry>.generate(numEntries, (i) {
// entry is index of start of this IFD in the file
final offset = ifd + 2 + 12 * i;
final tag = file.readInt(offset, 2);
final fieldType = FieldType.ofValue(file.readInt(offset + 2, 2));
final count = file.readInt(offset + 4, 4);
final typeLength = fieldType.length;
// Adjust for tag id/type/count (2+2+4 bytes)
// Now we point at either the data or the 2nd level offset
int fieldOffset = offset + 8;
// If the value fits in 4 bytes, it is inlined, else we
// need to jump ahead again.
if (count * typeLength > 4) {
// offset is not the value; it's a pointer to the value
// if relative we set things up so s2n will seek to the right
// place when it adds this.offset. Note that this 'relative'
// is for the Nikon type 3 makernote. Other cameras may use
// other relative offsets, which would have to be computed here
// slightly differently.
if (relative) {
fieldOffset = file.readInt(fieldOffset, 4) + ifd - 8;
if (fakeExif) {
fieldOffset += 18;
}
} else {
fieldOffset = file.readInt(fieldOffset, 4);
}
}
return IfdEntry(
fieldOffset: fieldOffset,
tag: tag,
fieldType: fieldType,
count: count);
});
}
Endian get endian => file.endian;
set endian(Endian e) {
file.endian = e;
}
int get baseOffset => file.baseOffset;
set baseOffset(int v) {
file.baseOffset = v;
}
int readInt(int offset, int length, {bool signed = false}) {
return file.readInt(offset, length, signed: signed);
}
List<int> readSlice(int relativePos, int length) {
return file.readSlice(relativePos, length);
}
IfdRatios _readIfdRatios(IfdEntry entry) {
final List<Ratio> values = [];
var pos = entry.fieldOffset;
for (int dummy = 0; dummy < entry.count; dummy++) {
values.add(file.readRatio(pos, signed: entry.fieldType.isSigned));
pos += entry.fieldType.length;
}
return IfdRatios(values);
}
IfdInts _readIfdInts(IfdEntry entry) {
final List<int> values = [];
var pos = entry.fieldOffset;
for (int dummy = 0; dummy < entry.count; dummy++) {
values.add(file.readInt(pos, entry.fieldType.length,
signed: entry.fieldType.isSigned));
pos += entry.fieldType.length;
}
return IfdInts(values);
}
IfdBytes _readAscii(IfdEntry entry) {
var count = entry.count;
// special case: null-terminated ASCII string
// XXX investigate
// sometimes gets too big to fit in int value
if (count <= 0) {
return IfdBytes.empty();
}
if (count > 1024 * 1024) {
count = 1024 * 1024;
}
try {
// and count < (2**31)) // 2E31 is hardware dependant. --gd
var values = file.readSlice(entry.fieldOffset, count);
// Drop any garbage after a null.
final i = values.indexOf(0);
if (i >= 0) {
values = values.sublist(0, i);
}
return IfdBytes(Uint8List.fromList(values));
} catch (e) {
// warnings.add("exception($e) at position: $filePosition, length: $count");
return IfdBytes.empty();
}
}
IfdValues readField(IfdEntry entry, {required String tagName}) {
if (entry.fieldType == FieldType.ascii) {
return _readAscii(entry);
}
// XXX investigate
// some entries get too big to handle could be malformed
// file or problem with this.s2n
if (entry.count < 1000) {
if (entry.fieldType == FieldType.ratio ||
entry.fieldType == FieldType.signedRatio) {
return _readIfdRatios(entry);
} else {
return _readIfdInts(entry);
}
// The test above causes problems with tags that are
// supposed to have long values! Fix up one important case.
} else if (tagName == 'MakerNote' ||
tagName == MakerNoteCanon.cameraInfoTagName) {
return _readIfdInts(entry);
}
return const IfdNone();
}
List<int> offsetToBytes(int readOffset, int length) {
return file.offsetToBytes(readOffset, length);
}
}
class IfdEntry {
final int fieldOffset;
final int tag;
final FieldType fieldType;
final int count;
IfdEntry({
required this.fieldOffset,
required this.tag,
required this.fieldType,
required this.count,
});
}