1
1
package com.soywiz.korim.format
2
2
3
+ import com.soywiz.kds.*
4
+ import com.soywiz.klogger.*
5
+ import com.soywiz.kmem.*
6
+ import com.soywiz.korio.compression.util.*
7
+ import com.soywiz.korio.dynamic.KDynamic.Companion.toInt
8
+ import com.soywiz.korio.experimental.*
3
9
import com.soywiz.korio.lang.*
4
10
import com.soywiz.korio.stream.*
11
+ import com.soywiz.krypto.encoding.*
5
12
6
13
// AVIF & HEIC metadata extractor
7
- object AVIFInfo : BaseAVIFInfo (" avif" )
8
- object HEICInfo : BaseAVIFInfo (" heic" )
14
+ object AVIFInfo : ISOBMFF (" avif" )
15
+ object HEICInfo : ISOBMFF (" heic" )
9
16
10
- open class BaseAVIFInfo (vararg exts : String ) : ImageFormatSuspend(*exts) {
17
+ // ISOBMFF
18
+ // https://gpac.github.io/mp4box.js/test/filereader.html
19
+ // https://en.wikipedia.org/wiki/ISO/IEC_base_media_file_format
20
+ // https://www.w3.org/TR/mse-byte-stream-format-isobmff/
21
+ open class ISOBMFF (vararg exts : String ) : ImageFormatSuspend(*exts) {
11
22
override suspend fun decodeHeaderSuspend (s : AsyncStream , props : ImageDecodingProps ): ImageInfo ? {
12
23
val ss = s.slice(4 until 8 ).readString(4 , LATIN1 )
13
24
if (ss != " ftyp" ) return null
14
- return StreamParser (props).also { it.decodeLevel(s, 0 ) }.info
25
+ return StreamParser (props).also { it.decode(s ) }.info
15
26
}
16
27
28
+ data class ItemExtent (val offset : Long , val size : Long )
29
+ data class ItemInfo (val id : Int ) {
30
+ var type: String = " "
31
+ var extents: ArrayList <ItemExtent > = arrayListOf ()
32
+ override fun toString (): String = " ItemInfo($id , $type , $extents )"
33
+ }
34
+
35
+ @OptIn(KorioExperimentalApi ::class )
17
36
class StreamParser (val props : ImageDecodingProps ) {
37
+ val debug get() = props.debug
18
38
var info = ImageInfo ()
39
+ val items = IntMap <ItemInfo >()
19
40
20
- suspend fun decodeLevel (s : AsyncStream , level : Int ) {
41
+ suspend fun decode (s : AsyncStream ) {
42
+ decodeLevel(s.sliceHere(), 0 )
43
+ if (debug) Console .error(" ITEMS" )
44
+ items.fastValueForEach {
45
+ if (it.type == " Exif" ) {
46
+ val extent = it.extents.first()
47
+ val range = s.sliceWithSize(extent.offset + 4 , extent.size - 4 ).readAllAsFastStream()
48
+ EXIF .readExif(range.toAsyncStream(), info, debug = debug)
49
+ }
50
+ }
51
+ }
52
+
53
+ private suspend fun decodeLevel (s : AsyncStream , level : Int ) {
21
54
while (! s.eof()) {
22
55
val blockSize = s.readS32BE()
23
56
val blockType = s.readStringz(4 , LATIN1 )
24
57
// val blockSubtype = s.readStringz(4, LATIN1)
25
58
// val blockStream = s.readStream(blockSize - 12)
26
- val blockStream = s.readStream(blockSize - 8 )
59
+ val blockStream = if (blockSize < 8 ) {
60
+ s.readStream(s.getAvailable())
61
+ } else {
62
+ s.readStream(blockSize - 8 )
63
+ }
27
64
// if (blockSize)
28
- // Console.error("${" ".repeat(level)}blockSize=$blockSize, blockType=$blockType")
65
+ if (debug) {
66
+ Console .error(" ${" " .repeat(level)} blockSize=$blockSize , blockType=$blockType " )
67
+ }
29
68
when (blockType) {
30
69
" ftyp" -> Unit
31
70
" meta" -> {
32
71
blockStream.skip(4 )
33
72
decodeLevel(blockStream, level + 1 )
34
73
}
74
+ // / See ISO 14496-12:2015 § 8.11.3
75
+ // https://github.com/kornelski/avif-parse/blob/2174e0e15647918cbbcb965ba142c285a6ef457f/src/lib.rs#L1136
76
+ " iloc" -> {
77
+ val version = blockStream.readU8()
78
+ val flags = blockStream.readU24BE()
79
+ val sizePacked = ByteArrayBitReader (blockStream.readBytesExact(2 ))
80
+ val offsetSize = sizePacked.readIntBits(4 )
81
+ val lengthSize = sizePacked.readIntBits(4 )
82
+ val baseOffsetSize = sizePacked.readIntBits(4 )
83
+ val indexSize = when (version) {
84
+ 1 , 2 -> sizePacked.readIntBits(4 )
85
+ else -> 0
86
+ }
87
+ val count: Int = when (version) {
88
+ 0 , 1 -> blockStream.readU16BE()
89
+ 2 -> blockStream.readS32BE()
90
+ else -> TODO ()
91
+ }
92
+
93
+ suspend fun AsyncStream.readSize (size : Int ): Long {
94
+ return when (size) {
95
+ 0 -> 0L
96
+ 4 -> blockStream.readS32BE().toLong()
97
+ 8 -> blockStream.readS64BE()
98
+ else -> TODO (" size=$size " )
99
+ }
100
+ }
101
+
102
+ if (debug) {
103
+ Console .error(" iloc version=$version , flags=$flags , count=$count , offsetSize=$offsetSize , lengthSize=$lengthSize , baseOffsetSize=$baseOffsetSize , indexSize=$indexSize " )
104
+ }
105
+ for (n in 0 until count) {
106
+ val itemID = when (version) {
107
+ 0 , 1 -> blockStream.readU16BE()
108
+ 2 -> blockStream.readS32LE()
109
+ else -> TODO ()
110
+ }
111
+ val method = when (version) {
112
+ 0 -> 0
113
+ 1 , 2 -> blockStream.readU16BE()
114
+ else -> TODO ()
115
+ }
116
+ val dataRefIndex = blockStream.readU16BE()
117
+
118
+ val baseOffset: Long = blockStream.readSize(baseOffsetSize)
119
+ val extentCount = blockStream.readU16BE()
120
+ if (debug) {
121
+ Console .error(" - itemID=$itemID , method=$method , dataRefIndex=$dataRefIndex , baseOffset=$baseOffset , extentCount=$extentCount " )
122
+ }
123
+ val itemInfo = items.getOrPut(itemID) { ItemInfo (itemID) }
124
+ for (m in 0 until extentCount) {
125
+ val extentIndex = blockStream.readSize(indexSize)
126
+ val extentOffset = blockStream.readSize(offsetSize)
127
+ val extentLength = blockStream.readSize(lengthSize)
128
+ val offset = baseOffset + extentOffset
129
+ if (debug) {
130
+ Console .error(" - $extentIndex , $offset , $extentLength " )
131
+ }
132
+ itemInfo.extents.add(ItemExtent (offset, extentLength))
133
+ }
134
+ // Console.error(" - $itemID, $unk2, $offset, $size")
135
+ }
136
+ }
137
+ " iinf" -> {
138
+ val version = blockStream.readU8()
139
+ val flags = blockStream.readU24BE()
140
+ val entryCount = when (version) {
141
+ 0 -> blockStream.readU16BE()
142
+ 1 -> blockStream.readS32BE()
143
+ else -> TODO ()
144
+ }
145
+ for (n in 0 until entryCount) {
146
+ decodeLevel(blockStream, level + 1 )
147
+ }
148
+ }
149
+ " infe" -> {
150
+ val version = blockStream.readU8()
151
+ val flags = blockStream.readU24BE()
152
+ val itemID: Int = when (version) {
153
+ 2 -> blockStream.readU16BE()
154
+ 3 -> blockStream.readS32BE()
155
+ else -> TODO ()
156
+ }
157
+ val protectionIndex = blockStream.readU16BE()
158
+ val itemType = blockStream.readStringz(4 , LATIN1 )
159
+ val itemInfo = items.getOrPut(itemID) { ItemInfo (itemID) }
160
+ itemInfo.type = itemType
161
+ if (debug) Console .error(" infe: itemID=$itemID , protectionIndex=$protectionIndex , itemType=${itemType} " )
162
+ }
35
163
" iprp" -> decodeLevel(blockStream, level + 1 )
36
164
" ipco" -> decodeLevel(blockStream, level + 1 )
37
165
" ispe" -> {
@@ -41,13 +169,18 @@ open class BaseAVIFInfo(vararg exts: String) : ImageFormatSuspend(*exts) {
41
169
}
42
170
" mdat" -> {
43
171
blockStream.skip(4 )
44
- if (blockStream.sliceHere().readStringz(4 , LATIN1 ) == " Exif" ) {
45
- val exif = EXIF .readExif(blockStream.sliceHere())
46
- info.orientation = exif.orientation
47
- }
48
172
}
49
173
}
50
174
}
51
175
}
176
+
177
+ suspend fun checkExif (blockStream : AsyncStream ): Boolean {
178
+ if (blockStream.sliceHere().readStringz(4 , LATIN1 ) == " Exif" ) {
179
+ val exif = EXIF .readExif(blockStream.sliceHere())
180
+ info.orientation = exif.orientation
181
+ return true
182
+ }
183
+ return false
184
+ }
52
185
}
53
186
}
0 commit comments