From 581d5fa541ff244f8aafad38768f8f21209a904a Mon Sep 17 00:00:00 2001 From: Stefan Oltmann Date: Sun, 28 Jan 2024 18:56:34 +0100 Subject: [PATCH 1/8] Refactor: Moved JXL stuff into own package --- .../bmff/BaseMediaFileFormatImageParser.kt | 3 ++- .../com/ashampoo/kim/format/bmff/BoxReader.kt | 6 ++--- .../{bmff/JxlHandler.kt => jxl/JxlReader.kt} | 22 +++++++++---------- .../kim/format/{bmff => jxl}/box/ExifBox.kt | 13 ++++++----- .../box/JxlParticalCodestreamBox.kt | 13 ++++++----- .../kim/format/{bmff => jxl}/box/XmlBox.kt | 13 ++++++----- 6 files changed, 37 insertions(+), 33 deletions(-) rename src/commonMain/kotlin/com/ashampoo/kim/format/{bmff/JxlHandler.kt => jxl/JxlReader.kt} (61%) rename src/commonMain/kotlin/com/ashampoo/kim/format/{bmff => jxl}/box/ExifBox.kt (76%) rename src/commonMain/kotlin/com/ashampoo/kim/format/{bmff => jxl}/box/JxlParticalCodestreamBox.kt (71%) rename src/commonMain/kotlin/com/ashampoo/kim/format/{bmff => jxl}/box/XmlBox.kt (63%) 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..2b4c0ecf 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt @@ -24,6 +24,7 @@ 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 @@ -66,7 +67,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() 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..c44d9c74 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt @@ -18,17 +18,17 @@ 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.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.PositionTrackingByteReader /** 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 61% 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..2cffcfc8 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/JxlHandler.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlReader.kt @@ -1,27 +1,27 @@ /* - * Copyright 2024 Ashampoo GmbH & Co. KG + * Copyright 2023 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 + * 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 76% 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..8b6c3df1 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 @@ -1,21 +1,22 @@ /* - * Copyright 2024 Ashampoo GmbH & Co. KG + * Copyright 2023 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 + * 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 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 71% 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..ff8501ad 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 @@ -1,21 +1,22 @@ /* - * Copyright 2024 Ashampoo GmbH & Co. KG + * Copyright 2023 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 + * 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 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 63% 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..ba452e9f 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 @@ -1,21 +1,22 @@ /* - * Copyright 2024 Ashampoo GmbH & Co. KG + * Copyright 2023 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 + * 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 From 87870d8e312474820c2262ea6cd0363f6d76fde9 Mon Sep 17 00:00:00 2001 From: Stefan Oltmann Date: Sun, 28 Jan 2024 18:58:51 +0100 Subject: [PATCH 2/8] Fixed copyright year --- src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlReader.kt | 2 +- .../kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.kt | 2 +- .../com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.kt | 2 +- src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.kt | 2 +- .../kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlReader.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlReader.kt index 2cffcfc8..42e9be5a 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/JxlReader.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. diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.kt index 8b6c3df1..b9203940 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.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. diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.kt index ff8501ad..e0c1bb88 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.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. diff --git a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.kt index ba452e9f..3dc7c455 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.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. 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..c25be35d 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. From 0489274e112dc932f57307f4e6ee3be1e2303141 Mon Sep 17 00:00:00 2001 From: Stefan Oltmann Date: Sun, 28 Jan 2024 19:16:40 +0100 Subject: [PATCH 3/8] Second tracking variable --- .../bmff/BaseMediaFileFormatImageParser.kt | 1 + .../com/ashampoo/kim/format/bmff/BoxReader.kt | 16 +++++++++++++++- .../kim/format/bmff/box/ItemInformationBox.kt | 3 ++- .../com/ashampoo/kim/format/bmff/box/MetaBox.kt | 1 + .../ashampoo/kim/format/bmff/BoxReaderTest.kt | 2 +- 5 files changed, 20 insertions(+), 3 deletions(-) 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 2b4c0ecf..2919ba36 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt @@ -50,6 +50,7 @@ object BaseMediaFileFormatImageParser : ImageParser { val allBoxes = BoxReader.readBoxes( byteReader = copyByteReader, stopAfterMetadataRead = true, + positionOffset = 0, offsetShift = 0 ) 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 c44d9c74..8e0645fe 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt @@ -45,6 +45,7 @@ object BoxReader { fun readBoxes( byteReader: PositionTrackingByteReader, stopAfterMetadataRead: Boolean = false, + positionOffset: Int = 0, offsetShift: Long = 0 ): List { @@ -52,6 +53,8 @@ object BoxReader { val boxes = mutableListOf() + var position: Int = positionOffset + while (true) { /* @@ -63,14 +66,20 @@ object BoxReader { val offset: Long = byteReader.position.toLong() + println("$offset == $position") + /* Note: The length includes the 8 header bytes. */ val length: Long = byteReader.read4BytesAsInt("length", BMFF_BYTE_ORDER).toLong() + position += 4 + val type = BoxType.of( byteReader.readBytes("type", BMFFConstants.TPYE_LENGTH) ) + position += BMFFConstants.TPYE_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. @@ -90,12 +99,17 @@ object BoxReader { else -> length } + if (length == 1L) + position += 8 + val nextBoxOffset = offset + actualLength - val remainingBytesToReadInThisBox = (nextBoxOffset - byteReader.position).toInt() + val remainingBytesToReadInThisBox = (nextBoxOffset - position).toInt() val bytes = byteReader.readBytes("data", remainingBytesToReadInThisBox) + position += remainingBytesToReadInThisBox + val globalOffset = offset + offsetShift val box = when (type) { 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..f1fa4c79 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 @@ -57,7 +57,8 @@ class ItemInformationBox( boxes = BoxReader.readBoxes( byteReader = byteReader, stopAfterMetadataRead = false, - offsetShift = offset + 4 + 2 + if (version == 0) 2 else 4 + positionOffset = 4 + 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/MetaBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/box/MetaBox.kt index 4cc52c42..822ce508 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 @@ -58,6 +58,7 @@ class MetaBox( boxes = BoxReader.readBoxes( byteReader = byteReader, stopAfterMetadataRead = false, + positionOffset = 4, offsetShift = offset + 8 ) 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 c25be35d..f559fd88 100644 --- a/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt +++ b/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt @@ -44,7 +44,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) } From e0d5a8858e6fd738f363f84e87cc625022bdb756 Mon Sep 17 00:00:00 2001 From: Stefan Oltmann Date: Sun, 28 Jan 2024 19:25:00 +0100 Subject: [PATCH 4/8] BoxReader: Use regular ByteReader --- .../com/ashampoo/kim/format/bmff/BoxReader.kt | 29 +++++++++---------- .../kim/format/bmff/box/ItemInformationBox.kt | 2 +- 2 files changed, 14 insertions(+), 17 deletions(-) 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 8e0645fe..58643eae 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt @@ -29,7 +29,7 @@ import com.ashampoo.kim.format.bmff.box.PrimaryItemBox 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.PositionTrackingByteReader +import com.ashampoo.kim.input.ByteReader /** * Reads ISOBMFF boxes @@ -43,9 +43,9 @@ 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, - positionOffset: Int = 0, + positionOffset: Long = 0, offsetShift: Long = 0 ): List { @@ -53,33 +53,29 @@ object BoxReader { val boxes = mutableListOf() - var position: Int = positionOffset + 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() - - println("$offset == $position") + val offset: Long = position /* Note: The length includes the 8 header bytes. */ val length: Long = byteReader.read4BytesAsInt("length", BMFF_BYTE_ORDER).toLong() - position += 4 - val type = BoxType.of( byteReader.readBytes("type", BMFFConstants.TPYE_LENGTH) ) - position += BMFFConstants.TPYE_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. @@ -90,7 +86,7 @@ object BoxReader { val actualLength: Long = when (length) { /* 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) @@ -99,11 +95,11 @@ object BoxReader { else -> length } - if (length == 1L) - position += 8 - val nextBoxOffset = offset + actualLength + @Suppress("MagicNumber") + position += 4 + BMFFConstants.TPYE_LENGTH + if (length == 1L) 8 else 0 + val remainingBytesToReadInThisBox = (nextBoxOffset - position).toInt() val bytes = byteReader.readBytes("data", remainingBytesToReadInThisBox) @@ -121,6 +117,7 @@ object BoxReader { BoxType.ILOC -> ItemLocationBox(globalOffset, actualLength, bytes) BoxType.PITM -> PrimaryItemBox(globalOffset, actualLength, bytes) BoxType.MDAT -> MediaDataBox(globalOffset, actualLength, bytes) + /* JXL boxes */ BoxType.EXIF -> ExifBox(globalOffset, actualLength, bytes) BoxType.XML -> XmlBox(globalOffset, actualLength, bytes) BoxType.JXLP -> JxlParticalCodestreamBox(globalOffset, actualLength, bytes) 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 f1fa4c79..e604dad4 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 @@ -57,7 +57,7 @@ class ItemInformationBox( boxes = BoxReader.readBoxes( byteReader = byteReader, stopAfterMetadataRead = false, - positionOffset = 4 + if (version == 0) 2 else 4, + positionOffset = 4L + if (version == 0) 2 else 4, offsetShift = offset + 4 + if (version == 0) 2 else 4 ) From c55a77e94128a24320dcdf2cc8e205fac46be6ed Mon Sep 17 00:00:00 2001 From: Stefan Oltmann Date: Sun, 28 Jan 2024 19:31:10 +0100 Subject: [PATCH 5/8] More use of regular ByteReader --- .../bmff/BaseMediaFileFormatImageParser.kt | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) 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 2919ba36..9f21e807 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt @@ -54,6 +54,8 @@ object BaseMediaFileFormatImageParser : ImageParser { offsetShift = 0 ) + val position = byteReader.position + if (allBoxes.isEmpty()) throw ImageReadException("Illegal ISOBMFF: Has no boxes.") @@ -100,7 +102,7 @@ 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 byteReaderToUse = if (position <= minOffset) byteReader else ByteArrayByteReader(copyByteReader.getBytes()) @@ -113,13 +115,13 @@ object BaseMediaFileFormatImageParser : ImageParser { when (offset.type) { MetadataType.EXIF -> - exifBytes = readExifBytes(byteReaderToUse, offset) + exifBytes = readExifBytes(byteReaderToUse, byteReaderToUse.position.toLong(), offset) MetadataType.IPTC -> continue // Unsupported MetadataType.XMP -> - xmp = readXmpString(byteReaderToUse, offset) + xmp = readXmpString(byteReaderToUse, byteReaderToUse.position.toLong(), offset) } } @@ -136,11 +138,12 @@ 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 byteReader.skipBytes("offset to EXIF extent", bytesToSkip.toInt()) @@ -156,11 +159,12 @@ object BaseMediaFileFormatImageParser : ImageParser { } private fun readXmpString( - byteReader: PositionTrackingByteReader, + byteReader: ByteReader, + position: Long, offset: MetadataOffset ): String { - val bytesToSkip = offset.offset - byteReader.position + val bytesToSkip = offset.offset - position byteReader.skipBytes("offset to MIME extent", bytesToSkip.toInt()) From 0ca44ce3f599889d2fe6df75d0e1799e65dcb9d0 Mon Sep 17 00:00:00 2001 From: Stefan Oltmann Date: Sun, 28 Jan 2024 23:54:10 +0100 Subject: [PATCH 6/8] Refactored to keep original size & largeSize information --- .../ashampoo/kim/format/bmff/BMFFConstants.kt | 9 +++-- .../bmff/BaseMediaFileFormatImageParser.kt | 5 +++ .../com/ashampoo/kim/format/bmff/BoxReader.kt | 39 +++++++++++-------- .../com/ashampoo/kim/format/bmff/box/Box.kt | 22 +++++++++-- .../kim/format/bmff/box/FileTypeBox.kt | 8 ++-- .../format/bmff/box/HandlerReferenceBox.kt | 5 ++- .../kim/format/bmff/box/ItemInfoEntryBox.kt | 5 ++- .../kim/format/bmff/box/ItemInformationBox.kt | 5 ++- .../kim/format/bmff/box/ItemLocationBox.kt | 5 ++- .../kim/format/bmff/box/MediaDataBox.kt | 5 ++- .../ashampoo/kim/format/bmff/box/MetaBox.kt | 5 ++- .../kim/format/bmff/box/PrimaryItemBox.kt | 5 ++- .../ashampoo/kim/format/jxl/box/ExifBox.kt | 5 ++- .../jxl/box/JxlParticalCodestreamBox.kt | 5 ++- .../com/ashampoo/kim/format/jxl/box/XmlBox.kt | 5 ++- 15 files changed, 87 insertions(+), 46 deletions(-) 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 9f21e807..08b2697c 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt @@ -56,6 +56,11 @@ object BaseMediaFileFormatImageParser : ImageParser { val position = byteReader.position + val lengthSum = allBoxes.sumOf { it.actualLength } + + if (position.toLong() != lengthSum) + println("$position != $lengthSum") + if (allBoxes.isEmpty()) throw ImageReadException("Illegal ISOBMFF: Has no boxes.") 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 58643eae..d91add93 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt @@ -69,7 +69,7 @@ object BoxReader { 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( @@ -83,22 +83,27 @@ 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 -> 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 @Suppress("MagicNumber") - position += 4 + BMFFConstants.TPYE_LENGTH + if (length == 1L) 8 else 0 + position += BMFFConstants.BOX_HEADER_LENGTH + if (size == 1L) 8 else 0 val remainingBytesToReadInThisBox = (nextBoxOffset - position).toInt() @@ -109,19 +114,19 @@ object BoxReader { 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.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, actualLength, bytes) - BoxType.XML -> XmlBox(globalOffset, actualLength, bytes) - BoxType.JXLP -> JxlParticalCodestreamBox(globalOffset, actualLength, bytes) - else -> Box(type, globalOffset, actualLength, bytes) + 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) 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 e604dad4..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 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 822ce508..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 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/jxl/box/ExifBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.kt index b9203940..adf2b64f 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/ExifBox.kt @@ -26,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/jxl/box/JxlParticalCodestreamBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.kt index e0c1bb88..e03cc4d5 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/JxlParticalCodestreamBox.kt @@ -23,9 +23,10 @@ import com.ashampoo.kim.format.bmff.box.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/jxl/box/XmlBox.kt b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.kt index 3dc7c455..27597b62 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/jxl/box/XmlBox.kt @@ -23,9 +23,10 @@ import com.ashampoo.kim.format.bmff.box.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 From cc30c03ffd3733daee7dd0f2abe96793f19de032 Mon Sep 17 00:00:00 2001 From: Stefan Oltmann Date: Mon, 29 Jan 2024 00:18:54 +0100 Subject: [PATCH 7/8] BaseMediaFileFormatImageParser now works with regular ByteReader --- .../bmff/BaseMediaFileFormatImageParser.kt | 51 +++++++++++-------- .../com/ashampoo/kim/format/bmff/BoxReader.kt | 10 +++- .../kim/format/bmff/CopyByteReader.kt | 12 ++--- 3 files changed, 41 insertions(+), 32 deletions(-) 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 08b2697c..11ef8296 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BaseMediaFileFormatImageParser.kt @@ -28,8 +28,6 @@ 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 @@ -40,27 +38,20 @@ 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, positionOffset = 0, - offsetShift = 0 + offsetShift = 0, + updatePosition = { position = it } ) - val position = byteReader.position - - val lengthSum = allBoxes.sumOf { it.actualLength } - - if (position.toLong() != lengthSum) - println("$position != $lengthSum") - if (allBoxes.isEmpty()) throw ImageReadException("Illegal ISOBMFF: Has no boxes.") @@ -107,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 (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 @@ -119,14 +115,18 @@ object BaseMediaFileFormatImageParser : ImageParser { when (offset.type) { - MetadataType.EXIF -> - exifBytes = readExifBytes(byteReaderToUse, byteReaderToUse.position.toLong(), offset) + MetadataType.EXIF -> { + exifBytes = readExifBytes(byteReaderToUse, position, offset) + position = offset.offset + offset.length + } MetadataType.IPTC -> continue // Unsupported - MetadataType.XMP -> - xmp = readXmpString(byteReaderToUse, byteReaderToUse.position.toLong(), offset) + MetadataType.XMP -> { + xmp = readXmpString(byteReaderToUse, position, offset) + position = offset.offset + offset.length + } } } @@ -150,6 +150,10 @@ object BaseMediaFileFormatImageParser : ImageParser { 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()) val tiffHeaderOffset = @@ -158,9 +162,10 @@ 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( @@ -171,6 +176,10 @@ object BaseMediaFileFormatImageParser : ImageParser { 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()) val mimeBytes = byteReader.readBytes(offset.length.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 d91add93..c4975ca5 100644 --- a/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt +++ b/src/commonMain/kotlin/com/ashampoo/kim/format/bmff/BoxReader.kt @@ -46,7 +46,8 @@ object BoxReader { byteReader: ByteReader, stopAfterMetadataRead: Boolean = false, positionOffset: Long = 0, - offsetShift: Long = 0 + offsetShift: Long = 0, + updatePosition: ((Long) -> Unit)? = null ): List { var haveSeenJxlHeaderBox: Boolean = false @@ -76,6 +77,8 @@ object BoxReader { 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. @@ -103,7 +106,8 @@ object BoxReader { val nextBoxOffset = offset + actualLength @Suppress("MagicNumber") - position += BMFFConstants.BOX_HEADER_LENGTH + if (size == 1L) 8 else 0 + if (size == 1L) + position += 8 val remainingBytesToReadInThisBox = (nextBoxOffset - position).toInt() @@ -151,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() From 9d15deb215c7c66a7e4a7008755089fa54594b92 Mon Sep 17 00:00:00 2001 From: Stefan Oltmann Date: Mon, 29 Jan 2024 00:21:33 +0100 Subject: [PATCH 8/8] Removed PositionTrackingByteReader.kt & PositionTrackingByteReaderDecorator.kt to reduce complexity. --- .../ashampoo/kim/input/ByteArrayByteReader.kt | 8 +-- .../input/DefaultRandomAccessByteReader.kt | 8 +-- .../kim/input/PositionTrackingByteReader.kt | 24 ------- .../PositionTrackingByteReaderDecorator.kt | 63 ------------------- .../ashampoo/kim/format/bmff/BoxReaderTest.kt | 3 +- 5 files changed, 3 insertions(+), 103 deletions(-) delete mode 100644 src/commonMain/kotlin/com/ashampoo/kim/input/PositionTrackingByteReader.kt delete mode 100644 src/commonMain/kotlin/com/ashampoo/kim/input/PositionTrackingByteReaderDecorator.kt 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 f559fd88..e9385824 100644 --- a/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt +++ b/src/commonTest/kotlin/com/ashampoo/kim/format/bmff/BoxReaderTest.kt @@ -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,