From a26b6a9ebaf3a9b0444546ce9f6f0063d3a2099a Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 21 Mar 2025 01:39:34 +0100 Subject: [PATCH] PngExifReader.php: add png exif reader with parser!!!! --- inc/Data/Driver/Metadata/PngExifReader.php | 148 +++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 inc/Data/Driver/Metadata/PngExifReader.php diff --git a/inc/Data/Driver/Metadata/PngExifReader.php b/inc/Data/Driver/Metadata/PngExifReader.php new file mode 100644 index 00000000..9791dfb9 --- /dev/null +++ b/inc/Data/Driver/Metadata/PngExifReader.php @@ -0,0 +1,148 @@ + \strlen($chunk_blob)) { + return null; + } + + while ($current_offset > 0) { + // Number of directory entries. + $num_entries = \unpack($unpack_fmt, \substr($tiff_data, $current_offset, 2))[1]; + $current_offset += 2; + + if ($current_offset + $num_entries * 12 > \strlen($chunk_blob)) { + return null; + } + + for ($i = 0; $i < $num_entries; $i++) { + $entry_offset = $current_offset + ($i * 12); + + // Read tag ID. + $tag_id = \unpack($unpack_fmt, \substr($tiff_data, $entry_offset, 2))[1]; + + if ($tag_id === self::ORIENTATION_TAG_ID) { + $field_type = \unpack($unpack_fmt, \substr($tiff_data, $entry_offset + 2, 2))[1]; + $value_count = \unpack($unpack_fmt, \substr($tiff_data, $entry_offset + 4, 4))[1]; + + if ($field_type === self::TYPE_SHORT && $value_count !== 1) { + // Read value. It is stored directly if it's <4 bytes. + $value_offset = $entry_offset + 8; + $orientation = \unpack($unpack_fmt, \substr($tiff_data, $value_offset, 2))[1]; + + return $orientation; // Return the Orientation value + } + } + } + + // Offset to the next IFD (last 4 bytes of the directory) + $current_offset = \unpack($unpack_fmt, \substr($tiff_data, $current_offset + ($num_entries * 12), 4))[1]; + } + + return null; + } + + // Mostly adapted from https://stackoverflow.com/a/2190438 + private static function tryReadPngChunks(mixed $fd): ?int { + $text_chunks = []; + + // Read the magic bytes and verify. + $header = \fread($fd, 8); + + if ($header != "\x89PNG\x0d\x0a\x1a\x0a") { + return null; + } + + // Loop through the PNG's chunks. Byte 0-3 is length, Byte 4-7 is type. + $chunkHeader = \fread($fd, 8); + + while ($chunkHeader) { + // Extract length and type from binary data. + $chunk = @\unpack('Nsize/a4type', $chunkHeader); + + // Store position into internal array. + if (!isset($text_chunks[$chunk['type']])) { + $text_chunks[$chunk['type']] = []; + } + + if ($chunk['type'] === 'tEXt') { + if ($chunk['size'] > 0 && $chunk['size'] < self::MAX_CHUNK_SIZE) { + $size = $chunk['size']; + $chunk_blob = \fread($fd, $size); + $ret = self::tryReadExifChunk($chunk_blob); + if ($ret !== null) { + return $ret; + } + } + } + + // Skip to next chunk (over body and CRC) + \fseek($fd, $chunk['size'] + 4, SEEK_CUR); + + // Read next chunk header + $chunkHeader = \fread($fd, 8); + } + + return null; + } + + public function getOrientation(string $file): ?int { + // Open the file. + $fd = \fopen($file, 'r'); + if ($fd === false) { + return null; + } + $ret = self::tryReadPngChunks($fd); + \fclose($fd); + if ($ret === null || $ret > 9) { + return null; + } else { + return $ret; + } + } +}