import streams import strutils import asyncfile import asyncdispatch import nimexif/libexif import nimexif/helpers import tables import parseutils import options import utils var buf {.threadvar.}: pointer # MUST be called once per thread proc init_jpg*() = init_exif() buf = alloc(65536) proc deinit_jpg*() = deinit_exif() dealloc(buf) const MARKER_START = parseHexStr("FF") # 0xCn SOFn SOS = parseHexStr("DA") # Start Of Scan (begins compressed data) SOI = parseHexStr("D8") # Start Of Image (beginning of datastream) EOI = parseHexStr("D9") # End Of Image (end of datastream) EXIF = parseHexStr("E1") JFIF = parseHexStr("E0") type SofData* = object components*: uint8 precision*: uint8 height*: uint16 width*: uint16 type JpgInfo* = object exifData*: Option[Table[string, string]] sofData*: Option[SofData] proc getSectionSize(file: Stream | AsyncFile): Future[uint16] {.multisync.} = # FIXME consider endianness var size: uint16 let val = toHex(file.read(2).await) discard parseHex(val, size) return size - uint16(2) proc skipSection(file: Stream | AsyncFile): Future[int] {.multisync.} = let size = int64(file.getSectionSize().await) debug("skipping ", size) file.setFilePos(file.getFilePos() + size) proc expect(file: Stream | AsyncFile, expected: string): Future[int] {.multisync.} = let byte = file.read(1).await if byte != expected: error("expected ", toHex(expected),", got ", toHex(byte)) proc process_sof*(file: Stream | AsyncFile): Future[SofData] {.multisync.} = discard parseHex(toHex(file.read(1).await), result.precision) discard parseHex(toHex(file.read(2).await), result.height) discard parseHex(toHex(file.read(2).await), result.width) discard parseHex(toHex(file.read(1).await), result.components) proc calcRes(sd: SofData): uint64 = return uint64(sd.height) * uint64(sd.width) proc collect_jpg*(file: Stream | AsyncFile): Future[JpgInfo] {.multisync,gcsafe.} = var done = false var byte: string discard file.expect(MARKER_START).await discard file.expect(SOI).await while not done: discard file.expect(MARKER_START).await let marker = file.read(1).await case marker: of "": error("unexpected end of file") of SOS: # Beginning of compressed data done = true of EOI: # No compressed data? Okay... done = true of EXIF: debug "found EXIF" let size = int(file.getSectionSize().await) discard file.readBuffer(buf, size).await let ed = exif_data_new_from_data(cast[ptr[uint8]](buf), cuint(size)) let ed_table = ed.collect_exif_data() if result.exifData.isNone: result.exifData = some(ed_table) debug ed_table of JFIF: debug "found JFIF" discard file.skipSection().await else: if toHex(marker).startsWith("C"): debug "found SOF" let section_start = file.getFilePos() + 2 let section_end = section_start + int64(file.getSectionSize().await) let sd = file.process_sof().await if result.sofData.isNone or sd.calcRes() > result.sofData.get.calcRes(): result.sofData = some(sd) debug sd file.setFilePos(section_end) continue debug("unknown section: ", toHex(marker)) discard file.skipSection().await proc collect_jpg*(file: File): JpgInfo = let stream = newFileStream(file) result = collect_jpg(stream) stream.close()