diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BMFFConstants.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BMFFConstants.kt index 09048923..ead1bb60 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BMFFConstants.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BMFFConstants.kt @@ -21,11 +21,14 @@ object BMFFConstants { val BMFF_BYTE_ORDER = ByteOrder.BIG_ENDIAN - /* BoxType must be always 4 bytes */ + /** BoxType must be always 4 bytes */ const val TPYE_LENGTH = 4 - /* 4 length bytes + 4 type bytes */ - const val BOX_HEADER_LENGTH = 8 + /** The size is presented as unsinged integer */ + const val SIZE_LENGTH = 4 + + /** 4 size bytes + 4 type bytes */ + const val BOX_HEADER_LENGTH = TPYE_LENGTH + SIZE_LENGTH const val TIFF_HEADER_OFFSET_BYTE_COUNT = 4 diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt index 1cbe9172..11ef8296 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt @@ -24,11 +24,10 @@ import com.ashampoo.kim.format.bmff.BMFFConstants.BMFF_BYTE_ORDER import com.ashampoo.kim.format.bmff.BMFFConstants.TIFF_HEADER_OFFSET_BYTE_COUNT import com.ashampoo.kim.format.bmff.box.FileTypeBox import com.ashampoo.kim.format.bmff.box.MetaBox +import com.ashampoo.kim.format.jxl.JxlReader import com.ashampoo.kim.format.tiff.TiffReader import com.ashampoo.kim.input.ByteArrayByteReader import com.ashampoo.kim.input.ByteReader -import com.ashampoo.kim.input.PositionTrackingByteReader -import com.ashampoo.kim.input.PositionTrackingByteReaderDecorator /** * Reads containers that follow the ISO base media file format @@ -39,17 +38,18 @@ import com.ashampoo.kim.input.PositionTrackingByteReaderDecorator */ object BaseMediaFileFormatImageParser : ImageParser { - override fun parseMetadata(byteReader: ByteReader): ImageMetadata = - parseMetadata(PositionTrackingByteReaderDecorator(byteReader)) - - private fun parseMetadata(byteReader: PositionTrackingByteReader): ImageMetadata { + override fun parseMetadata(byteReader: ByteReader): ImageMetadata { val copyByteReader = CopyByteReader(byteReader) + var position: Long = 0 + val allBoxes = BoxReader.readBoxes( byteReader = copyByteReader, stopAfterMetadataRead = true, - offsetShift = 0 + positionOffset = 0, + offsetShift = 0, + updatePosition = { position = it } ) if (allBoxes.isEmpty()) @@ -66,7 +66,7 @@ object BaseMediaFileFormatImageParser : ImageParser { * This format has EXIF & XMP neatly in dedicated boxes, so we can just extract these. */ if (fileTypeBox.majorBrand == FileTypeBox.JXL_BRAND) - return JxlHandler.createMetadata(allBoxes) + return JxlReader.createMetadata(allBoxes) val metaBox = allBoxes.filterIsInstance().firstOrNull() @@ -98,11 +98,16 @@ object BaseMediaFileFormatImageParser : ImageParser { * in buffer and input everything we read so far in again. * FIXME There must be a better solution. Find it. */ - val byteReaderToUse = if (byteReader.position <= minOffset) + val onPositionBeforeMinimumOffset = position <= minOffset + + val byteReaderToUse = if (onPositionBeforeMinimumOffset) byteReader else ByteArrayByteReader(copyByteReader.getBytes()) + if (!onPositionBeforeMinimumOffset) + position = 0 + var exifBytes: ByteArray? = null var xmp: String? = null @@ -110,14 +115,18 @@ object BaseMediaFileFormatImageParser : ImageParser { when (offset.type) { - MetadataType.EXIF -> - exifBytes = readExifBytes(byteReaderToUse, offset) + MetadataType.EXIF -> { + exifBytes = readExifBytes(byteReaderToUse, position, offset) + position = offset.offset + offset.length + } MetadataType.IPTC -> continue // Unsupported - MetadataType.XMP -> - xmp = readXmpString(byteReaderToUse, offset) + MetadataType.XMP -> { + xmp = readXmpString(byteReaderToUse, position, offset) + position = offset.offset + offset.length + } } } @@ -134,11 +143,16 @@ object BaseMediaFileFormatImageParser : ImageParser { } private fun readExifBytes( - byteReader: PositionTrackingByteReader, + byteReader: ByteReader, + position: Long, offset: MetadataOffset ): ByteArray { - val bytesToSkip = offset.offset - byteReader.position + val bytesToSkip = offset.offset - position + + check(bytesToSkip >= 0) { + "Position must be before extent offset: position=$position offset=$offset" + } byteReader.skipBytes("offset to EXIF extent", bytesToSkip.toInt()) @@ -148,17 +162,23 @@ object BaseMediaFileFormatImageParser : ImageParser { /* Usualy there are 6 bytes skipped, which are the EXIF header. ("Exif.."). */ byteReader.skipBytes("offset to TIFF header", tiffHeaderOffset) - return byteReader.readBytes( + val exifBytesLength = offset.length.toInt() - TIFF_HEADER_OFFSET_BYTE_COUNT - tiffHeaderOffset - ) + + return byteReader.readBytes(exifBytesLength) } private fun readXmpString( - byteReader: PositionTrackingByteReader, + byteReader: ByteReader, + position: Long, offset: MetadataOffset ): String { - val bytesToSkip = offset.offset - byteReader.position + val bytesToSkip = offset.offset - position + + check(bytesToSkip >= 0) { + "Position must be before extent offset: position=$position offset=$offset" + } byteReader.skipBytes("offset to MIME extent", bytesToSkip.toInt()) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt index 2f841977..c4975ca5 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt @@ -18,18 +18,18 @@ package com.ashampoo.kim.format.bmff import com.ashampoo.kim.format.bmff.BMFFConstants.BMFF_BYTE_ORDER import com.ashampoo.kim.format.bmff.box.Box -import com.ashampoo.kim.format.bmff.box.ExifBox import com.ashampoo.kim.format.bmff.box.FileTypeBox import com.ashampoo.kim.format.bmff.box.HandlerReferenceBox import com.ashampoo.kim.format.bmff.box.ItemInfoEntryBox import com.ashampoo.kim.format.bmff.box.ItemInformationBox import com.ashampoo.kim.format.bmff.box.ItemLocationBox -import com.ashampoo.kim.format.bmff.box.JxlParticalCodestreamBox import com.ashampoo.kim.format.bmff.box.MediaDataBox import com.ashampoo.kim.format.bmff.box.MetaBox import com.ashampoo.kim.format.bmff.box.PrimaryItemBox -import com.ashampoo.kim.format.bmff.box.XmlBox -import com.ashampoo.kim.input.PositionTrackingByteReader +import com.ashampoo.kim.format.jxl.box.ExifBox +import com.ashampoo.kim.format.jxl.box.JxlParticalCodestreamBox +import com.ashampoo.kim.format.jxl.box.XmlBox +import com.ashampoo.kim.input.ByteReader /** * Reads ISOBMFF boxes @@ -43,34 +43,42 @@ object BoxReader { * For iPhone HEIC this is possible, but Samsung HEIC has "meta" coming after "mdat" */ fun readBoxes( - byteReader: PositionTrackingByteReader, + byteReader: ByteReader, stopAfterMetadataRead: Boolean = false, - offsetShift: Long = 0 + positionOffset: Long = 0, + offsetShift: Long = 0, + updatePosition: ((Long) -> Unit)? = null ): List { var haveSeenJxlHeaderBox: Boolean = false val boxes = mutableListOf() + var position: Long = positionOffset + while (true) { + val available = byteReader.contentLength - position + /* * Check if there are enough bytes for another box. * If so, we at least need the 8 header bytes. */ - if (byteReader.available < BMFFConstants.BOX_HEADER_LENGTH) + if (available < BMFFConstants.BOX_HEADER_LENGTH) break - val offset: Long = byteReader.position.toLong() + val offset: Long = position /* Note: The length includes the 8 header bytes. */ - val length: Long = + val size: Long = byteReader.read4BytesAsInt("length", BMFF_BYTE_ORDER).toLong() val type = BoxType.of( byteReader.readBytes("type", BMFFConstants.TPYE_LENGTH) ) + position += BMFFConstants.BOX_HEADER_LENGTH + /* * If we read an JXL file and we already have seen the header, * all reamining JXLP boxes are image data that we can skip. @@ -78,39 +86,51 @@ object BoxReader { if (stopAfterMetadataRead && type == BoxType.JXLP && haveSeenJxlHeaderBox) break - val actualLength: Long = when (length) { + var largeSize: Long? = null + + val actualLength: Long = when (size) { /* A vaule of zero indicates that it's the last box. */ - 0L -> byteReader.available + 0L -> available /* A length of 1 indicates that we should read the next 8 bytes to get a long value. */ - 1L -> byteReader.read8BytesAsLong("length", BMFF_BYTE_ORDER) + 1L -> { + largeSize = byteReader.read8BytesAsLong("length", BMFF_BYTE_ORDER) + largeSize + } /* Keep the length we already read. */ - else -> length + else -> size } val nextBoxOffset = offset + actualLength - val remainingBytesToReadInThisBox = (nextBoxOffset - byteReader.position).toInt() + @Suppress("MagicNumber") + if (size == 1L) + position += 8 + + val remainingBytesToReadInThisBox = (nextBoxOffset - position).toInt() val bytes = byteReader.readBytes("data", remainingBytesToReadInThisBox) + position += remainingBytesToReadInThisBox + val globalOffset = offset + offsetShift val box = when (type) { - BoxType.FTYP -> FileTypeBox(globalOffset, actualLength, bytes) - BoxType.META -> MetaBox(globalOffset, actualLength, bytes) - BoxType.HDLR -> HandlerReferenceBox(globalOffset, actualLength, bytes) - BoxType.IINF -> ItemInformationBox(globalOffset, actualLength, bytes) - BoxType.INFE -> ItemInfoEntryBox(globalOffset, actualLength, bytes) - BoxType.ILOC -> ItemLocationBox(globalOffset, actualLength, bytes) - BoxType.PITM -> PrimaryItemBox(globalOffset, actualLength, bytes) - BoxType.MDAT -> MediaDataBox(globalOffset, actualLength, bytes) - BoxType.EXIF -> ExifBox(globalOffset, actualLength, bytes) - BoxType.XML -> XmlBox(globalOffset, actualLength, bytes) - BoxType.JXLP -> JxlParticalCodestreamBox(globalOffset, actualLength, bytes) - else -> Box(type, globalOffset, actualLength, bytes) + BoxType.FTYP -> FileTypeBox(globalOffset, size, largeSize, bytes) + BoxType.META -> MetaBox(globalOffset, size, largeSize, bytes) + BoxType.HDLR -> HandlerReferenceBox(globalOffset, size, largeSize, bytes) + BoxType.IINF -> ItemInformationBox(globalOffset, size, largeSize, bytes) + BoxType.INFE -> ItemInfoEntryBox(globalOffset, size, largeSize, bytes) + BoxType.ILOC -> ItemLocationBox(globalOffset, size, largeSize, bytes) + BoxType.PITM -> PrimaryItemBox(globalOffset, size, largeSize, bytes) + BoxType.MDAT -> MediaDataBox(globalOffset, size, largeSize, bytes) + /* JXL boxes */ + BoxType.EXIF -> ExifBox(globalOffset, size, largeSize, bytes) + BoxType.XML -> XmlBox(globalOffset, size, largeSize, bytes) + BoxType.JXLP -> JxlParticalCodestreamBox(globalOffset, size, largeSize, bytes) + else -> Box(type, globalOffset, size, largeSize, bytes) } boxes.add(box) @@ -135,6 +155,8 @@ object BoxReader { } } + updatePosition?.let { it(position) } + return boxes } } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/CopyByteReader.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/CopyByteReader.kt index c78c9e8c..203048b3 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/CopyByteReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/CopyByteReader.kt @@ -15,24 +15,18 @@ */ package com.ashampoo.kim.format.bmff -import com.ashampoo.kim.input.PositionTrackingByteReader +import com.ashampoo.kim.input.ByteReader import com.ashampoo.kim.output.ByteArrayByteWriter internal class CopyByteReader( - val byteReader: PositionTrackingByteReader -) : PositionTrackingByteReader { + val byteReader: ByteReader +) : ByteReader { private val byteWriter = ByteArrayByteWriter() override val contentLength: Long = byteReader.contentLength - override val position: Int - get() = byteReader.position - - override val available: Long - get() = byteReader.available - fun getBytes(): ByteArray = byteWriter.toByteArray() diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/Box.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/Box.kt index 8ac859c0..3913fe5f 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/Box.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/Box.kt @@ -16,16 +16,32 @@ */ package com.ashampoo.kim.format.bmff.box +import com.ashampoo.kim.format.bmff.BMFFConstants.BOX_HEADER_LENGTH import com.ashampoo.kim.format.bmff.BoxType open class Box( val type: BoxType, val offset: Long, - val length: Long, - /* Payload bytes, not including type & length bytes */ + val size: Long, + val largeSize: Long?, + /** Payload bytes, not including type & length bytes */ val payload: ByteArray ) { + /* + * "size" is an integer that specifies the number of bytes in this box, + * including all its fields and contained boxes; if size is 1 then the + * actual size is in the field largesize; if size is 0, then this + * box is the last one in the file, and its contents extend to the + * end of the file (normally only used for a Media Data Box) + */ + val actualLength: Long = + when (size) { + 0L -> BOX_HEADER_LENGTH.toLong() + payload.size + 1L -> largeSize!! + else -> size + } + override fun toString(): String = - "Box '$type' @ $offset ($length bytes)" + "Box '$type' @ $offset ($actualLength bytes)" } diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/FileTypeBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/FileTypeBox.kt index 0003e491..6735cb84 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/FileTypeBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/FileTypeBox.kt @@ -19,6 +19,7 @@ package com.ashampoo.kim.format.bmff.box import com.ashampoo.kim.common.toFourCCTypeString import com.ashampoo.kim.format.bmff.BMFFConstants import com.ashampoo.kim.format.bmff.BMFFConstants.BMFF_BYTE_ORDER +import com.ashampoo.kim.format.bmff.BMFFConstants.BOX_HEADER_LENGTH import com.ashampoo.kim.format.bmff.BoxType import com.ashampoo.kim.input.ByteArrayByteReader @@ -27,9 +28,10 @@ import com.ashampoo.kim.input.ByteArrayByteReader */ class FileTypeBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.FTYP, offset, length, payload) { +) : Box(BoxType.FTYP, offset, size, largeSize, payload) { val majorBrand: String @@ -49,7 +51,7 @@ class FileTypeBox( .read4BytesAsInt("minorBrand", BMFF_BYTE_ORDER) .toFourCCTypeString() - val brandCount: Int = (length.toInt() - 8 - 8) / 4 + val brandCount: Int = (actualLength.toInt() - BOX_HEADER_LENGTH - 8 - 8) / 4 val brands = mutableListOf() diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/HandlerReferenceBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/HandlerReferenceBox.kt index 78428c91..e55cb135 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/HandlerReferenceBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/HandlerReferenceBox.kt @@ -25,9 +25,10 @@ import com.ashampoo.kim.input.ByteArrayByteReader */ class HandlerReferenceBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.HDLR, offset, length, payload) { +) : Box(BoxType.HDLR, offset, size, largeSize, payload) { val version: Int diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemInfoEntryBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemInfoEntryBox.kt index 7bb20743..aae3fbc1 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemInfoEntryBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemInfoEntryBox.kt @@ -27,9 +27,10 @@ import com.ashampoo.kim.input.ByteArrayByteReader */ class ItemInfoEntryBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.INFE, offset, length, payload) { +) : Box(BoxType.INFE, offset, size, largeSize, payload) { val version: Int diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemInformationBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemInformationBox.kt index 1dd7ca1d..2fcd7453 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemInformationBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemInformationBox.kt @@ -27,9 +27,10 @@ import com.ashampoo.kim.input.ByteArrayByteReader */ class ItemInformationBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.IINF, offset, length, payload), BoxContainer { +) : Box(BoxType.IINF, offset, size, largeSize, payload), BoxContainer { val version: Int @@ -57,7 +58,8 @@ class ItemInformationBox( boxes = BoxReader.readBoxes( byteReader = byteReader, stopAfterMetadataRead = false, - offsetShift = offset + 4 + 2 + if (version == 0) 2 else 4 + positionOffset = 4L + if (version == 0) 2 else 4, + offsetShift = offset + 4 + if (version == 0) 2 else 4 ) val map = mutableMapOf() diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemLocationBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemLocationBox.kt index 0825ceaf..3254bd96 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemLocationBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ItemLocationBox.kt @@ -26,9 +26,10 @@ import com.ashampoo.kim.input.ByteArrayByteReader */ class ItemLocationBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.ILOC, offset, length, payload) { +) : Box(BoxType.ILOC, offset, size, largeSize, payload) { /** * The version of the box. diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/MediaDataBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/MediaDataBox.kt index 99fa98aa..72cb167c 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/MediaDataBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/MediaDataBox.kt @@ -26,9 +26,10 @@ import com.ashampoo.kim.format.bmff.BoxType */ class MediaDataBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.MDAT, offset, length, payload) { +) : Box(BoxType.MDAT, offset, size, largeSize, payload) { override fun toString(): String = "$type Box" diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/MetaBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/MetaBox.kt index 4cc52c42..24e01df1 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/MetaBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/MetaBox.kt @@ -31,9 +31,10 @@ import com.ashampoo.kim.input.ByteArrayByteReader */ class MetaBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.META, offset, length, payload), BoxContainer { +) : Box(BoxType.META, offset, size, largeSize, payload), BoxContainer { val version: Int @@ -58,6 +59,7 @@ class MetaBox( boxes = BoxReader.readBoxes( byteReader = byteReader, stopAfterMetadataRead = false, + positionOffset = 4, offsetShift = offset + 8 ) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/PrimaryItemBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/PrimaryItemBox.kt index 75a56966..cdc93c0a 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/PrimaryItemBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/PrimaryItemBox.kt @@ -26,9 +26,10 @@ import com.ashampoo.kim.input.ByteArrayByteReader */ class PrimaryItemBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.PITM, offset, length, payload) { +) : Box(BoxType.PITM, offset, size, largeSize, payload) { val version: Int diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/JxlHandler.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlReader.kt similarity index 64% rename from src/commonMain/kotlin/com/ashampoo/kim/format/bmff/JxlHandler.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlReader.kt index 921888c9..42e9be5a 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/JxlHandler.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlReader.kt @@ -5,23 +5,23 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. */ -package com.ashampoo.kim.format.bmff +package com.ashampoo.kim.format.jxl import com.ashampoo.kim.format.ImageMetadata import com.ashampoo.kim.format.bmff.box.Box -import com.ashampoo.kim.format.bmff.box.ExifBox -import com.ashampoo.kim.format.bmff.box.XmlBox +import com.ashampoo.kim.format.jxl.box.ExifBox +import com.ashampoo.kim.format.jxl.box.XmlBox import com.ashampoo.kim.model.ImageFormat -internal object JxlHandler { +internal object JxlReader { fun createMetadata(allBoxes: List): ImageMetadata { @@ -29,8 +29,8 @@ internal object JxlHandler { val xmlBox = allBoxes.filterIsInstance().firstOrNull() return ImageMetadata( - imageFormat = ImageFormat.JXL, // could be any ISO BMFF - imageSize = null, // not covered by ISO BMFF + imageFormat = ImageFormat.JXL, + imageSize = null, // TODO https://github.com/Ashampoo/kim/issues/65 exif = exifBox?.tiffContents, exifBytes = exifBox?.exifBytes, iptc = null, // not covered by ISO BMFF diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ExifBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.kt similarity index 72% rename from src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ExifBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.kt index fedc97f8..adf2b64f 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/ExifBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.kt @@ -5,17 +5,18 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. */ -package com.ashampoo.kim.format.bmff.box +package com.ashampoo.kim.format.jxl.box import com.ashampoo.kim.format.bmff.BoxType +import com.ashampoo.kim.format.bmff.box.Box import com.ashampoo.kim.format.tiff.TiffContents import com.ashampoo.kim.format.tiff.TiffReader import com.ashampoo.kim.input.ByteArrayByteReader @@ -25,9 +26,10 @@ import com.ashampoo.kim.input.ByteArrayByteReader */ class ExifBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.EXIF, offset, length, payload) { +) : Box(BoxType.EXIF, offset, size, largeSize, payload) { val version: Int diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/JxlParticalCodestreamBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.kt similarity index 67% rename from src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/JxlParticalCodestreamBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.kt index 4b3d2e90..e03cc4d5 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/JxlParticalCodestreamBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.kt @@ -5,26 +5,28 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. */ -package com.ashampoo.kim.format.bmff.box +package com.ashampoo.kim.format.jxl.box import com.ashampoo.kim.format.bmff.BoxType +import com.ashampoo.kim.format.bmff.box.Box /** * JPEG XL jxlp box */ class JxlParticalCodestreamBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.JXLP, offset, length, payload) { +) : Box(BoxType.JXLP, offset, size, largeSize, payload) { val isHeader: Boolean diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/XmlBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.kt similarity index 58% rename from src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/XmlBox.kt rename to src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.kt index d05aff58..27597b62 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/XmlBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.kt @@ -5,26 +5,28 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing + * permissions and limitations under the License. */ -package com.ashampoo.kim.format.bmff.box +package com.ashampoo.kim.format.jxl.box import com.ashampoo.kim.format.bmff.BoxType +import com.ashampoo.kim.format.bmff.box.Box /** * JPEG XL XML box */ class XmlBox( offset: Long, - length: Long, + size: Long, + largeSize: Long?, payload: ByteArray -) : Box(BoxType.XML, offset, length, payload) { +) : Box(BoxType.XML, offset, size, largeSize, payload) { val xmp: String diff --git a/src/commonMain/kotlin/com/ashampoo/kim/input/ByteArrayByteReader.kt b/src/commonMain/kotlin/com/ashampoo/kim/input/ByteArrayByteReader.kt index 4bff7e15..91b40e7f 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/input/ByteArrayByteReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/input/ByteArrayByteReader.kt @@ -27,17 +27,11 @@ import kotlin.math.min */ class ByteArrayByteReader( private val bytes: ByteArray -) : RandomAccessByteReader, PositionTrackingByteReader { +) : RandomAccessByteReader { override val contentLength: Long = bytes.size.toLong() - override val position: Int - get() = currentPosition - - override val available: Long - get() = contentLength - position - private var currentPosition = 0 override fun readByte(): Byte? { diff --git a/src/commonMain/kotlin/com/ashampoo/kim/input/DefaultRandomAccessByteReader.kt b/src/commonMain/kotlin/com/ashampoo/kim/input/DefaultRandomAccessByteReader.kt index 204d9fbf..ba98e34b 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/input/DefaultRandomAccessByteReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/input/DefaultRandomAccessByteReader.kt @@ -21,17 +21,11 @@ package com.ashampoo.kim.input */ class DefaultRandomAccessByteReader( val byteReader: ByteReader -) : RandomAccessByteReader, PositionTrackingByteReader { +) : RandomAccessByteReader { override val contentLength: Long = byteReader.contentLength - override val position: Int - get() = currentPosition - - override val available: Long - get() = contentLength - position - private var currentPosition: Int = 0 private val buffer: MutableList = ArrayList(INITIAL_SIZE) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/input/PositionTrackingByteReader.kt b/src/commonMain/kotlin/com/ashampoo/kim/input/PositionTrackingByteReader.kt deleted file mode 100644 index e9c1812c..00000000 --- a/src/commonMain/kotlin/com/ashampoo/kim/input/PositionTrackingByteReader.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2024 Ashampoo GmbH & Co. KG - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.ashampoo.kim.input - -interface PositionTrackingByteReader : ByteReader { - - val position: Int - - val available: Long - -} diff --git a/src/commonMain/kotlin/com/ashampoo/kim/input/PositionTrackingByteReaderDecorator.kt b/src/commonMain/kotlin/com/ashampoo/kim/input/PositionTrackingByteReaderDecorator.kt deleted file mode 100644 index 3579303b..00000000 --- a/src/commonMain/kotlin/com/ashampoo/kim/input/PositionTrackingByteReaderDecorator.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2024 Ashampoo GmbH & Co. KG - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.ashampoo.kim.input - -/** - * This is a Decorator to track the current position in reading the file. - * - * The ByteReader interface has many complex implementations, where some - * of them aren't even aware of the current position or the position tracking - * is an implementation detail that should not be exposed. - * Therefore it's better to track the position on a higher level. - */ -class PositionTrackingByteReaderDecorator( - val byteReader: ByteReader -) : PositionTrackingByteReader { - - override val contentLength: Long = - byteReader.contentLength - - override val position: Int - get() = currentPosition - - override val available: Long - get() = byteReader.contentLength - position - - private var currentPosition: Int = 0 - - override fun readByte(): Byte? { - - val byte = byteReader.readByte() - - if (byte != null) - currentPosition++ - - return byte - } - - override fun readBytes(count: Int): ByteArray { - - val bytes = byteReader.readBytes(count) - - currentPosition += bytes.size - - return bytes - } - - override fun close() { - /* Do nothing */ - } -} diff --git a/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt b/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt index 4e1a476a..e9385824 100644 --- a/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt +++ b/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 Ashampoo GmbH & Co. KG + * Copyright 2024 Ashampoo GmbH & Co. KG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package com.ashampoo.kim.format.bmff import com.ashampoo.kim.format.bmff.box.BoxContainer import com.ashampoo.kim.input.ByteArrayByteReader -import com.ashampoo.kim.input.PositionTrackingByteReaderDecorator import com.ashampoo.kim.testdata.KimTestData import kotlin.test.Test import kotlin.test.assertEquals @@ -30,7 +29,7 @@ class BoxReaderTest { val bytes = KimTestData.getBytesOf(KimTestData.HEIC_TEST_IMAGE_INDEX) - val byteReader = PositionTrackingByteReaderDecorator(ByteArrayByteReader(bytes)) + val byteReader = ByteArrayByteReader(bytes) val boxes = BoxReader.readBoxes( byteReader = byteReader, @@ -44,7 +43,7 @@ class BoxReaderTest { assertEquals(48, allBoxes.first { it.type == BoxType.HDLR }.offset) assertEquals(118, allBoxes.first { it.type == BoxType.PITM }.offset) assertEquals(132, allBoxes.first { it.type == BoxType.IINF }.offset) - assertEquals(146, allBoxes.first { it.type == BoxType.INFE }.offset) + assertEquals(144, allBoxes.first { it.type == BoxType.INFE }.offset) assertEquals(2572, allBoxes.first { it.type == BoxType.ILOC }.offset) assertEquals(3404, allBoxes.first { it.type == BoxType.MDAT }.offset) }