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 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 offsetToBytes(int readOffset, int length) { final List 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 listIfd() { int i = _firstIfd(); final List ifds = []; while (i > 0) { ifds.add(i); i = _nextIfd(i); } return ifds; } List readIfdEntries(int ifd, {required bool relative}) { final numEntries = file.readInt(ifd, 2); return List.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 readSlice(int relativePos, int length) { return file.readSlice(relativePos, length); } IfdRatios _readIfdRatios(IfdEntry entry) { final List 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 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 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, }); }