This is an automated email from the ASF dual-hosted git repository.
THausherr pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git
The following commit(s) were added to refs/heads/main by this push:
new 66d318a7a9 TIKA-4851: RawTiffParser marks only the largest preview as
THUMBNAIL (#3091)
66d318a7a9 is described below
commit 66d318a7a91252d42c48ad3e50b7b18d3ad68c3e
Author: Dominik Schmidt <[email protected]>
AuthorDate: Sat Aug 29 14:07:17 2026 +0200
TIKA-4851: RawTiffParser marks only the largest preview as THUMBNAIL (#3091)
* TIKA-4851 - RawTiffParser: the largest preview is the thumbnail
Emit the largest embedded JPEG preview first, marked THUMBNAIL, and the
smaller previews of the same image after it as INLINE images named
image-N.jpg. Every preview used to be a THUMBNAIL in IFD order, so the
camera's small thumbnail came first for formats that store it in IFD1
ahead of the full-size preview (PEF), and a client had to compare the
dimensions of all previews to find the representative one.
* TIKA-4851 - no ordering claim in CHANGES and javadoc
* TIKA-4851 - javadoc: the rule applies to the extracted previews
---
CHANGES.txt | 6 +++
.../apache/tika/parser/image/RawTiffParser.java | 28 ++++++++++--
.../tika/parser/image/RawTiffParserTest.java | 53 ++++++++++++++--------
3 files changed, 65 insertions(+), 22 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index c4d49ce9cb..df8c73d3f1 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,11 @@
Release 4.1.0 - unreleased
+ * RawTiffParser marks only the largest embedded JPEG preview as the
+ THUMBNAIL embedded document; the smaller previews of the same image are
+ INLINE images named image-N.jpg. Previously every preview was a
+ THUMBNAIL, so a client had to compare them to find the representative
+ one (TIKA-4851).
+
* New exception-reporting parse-context config controls how much of an
exception is written to tk:exception:* metadata for the container and
embedded documents alike: FULL (default), MESSAGE_REDACTED (stack trace
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
index d4a3cb8d07..f982113d56 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/main/java/org/apache/tika/parser/image/RawTiffParser.java
@@ -25,6 +25,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Comparator;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
@@ -68,6 +69,12 @@ import org.apache.tika.sax.XHTMLContentHandler;
* Both classic TIFF and BigTIFF containers (allowed for DNG since spec
* version 1.7) are supported for preview extraction; for BigTIFF, EXIF
* metadata extraction is skipped until metadata-extractor supports it.
+ * <p>
+ * Of the previews that are extracted (see the length limits in
+ * {@link RawTiffParserConfig}), the largest by JPEG byte length is marked
+ * {@link TikaCoreProperties.EmbeddedResourceType#THUMBNAIL}, any smaller
+ * ones are {@link TikaCoreProperties.EmbeddedResourceType#INLINE} images
+ * (TIKA-4851).
*/
@TikaComponent
public class RawTiffParser extends TiffParser {
@@ -172,6 +179,11 @@ public class RawTiffParser extends TiffParser {
if (previews.isEmpty()) {
return;
}
+ //of the extracted previews, the largest by JPEG length is the file's
+ //thumbnail; the smaller ones (the camera's own thumbnail, intermediate
+ //previews) are renderings of the same image and are inline images.
+ //The JPEG length is a reliable proxy for the dimensions here.
+ previews.sort(Comparator.comparingLong(Preview::length).reversed());
EmbeddedDocumentExtractor extractor =
EmbeddedDocumentUtil.getEmbeddedDocumentExtractor(context);
int count = 0;
@@ -179,11 +191,19 @@ public class RawTiffParser extends TiffParser {
try (FileChannel channel = FileChannel.open(tis.getPath())) {
for (Preview preview : previews) {
Metadata previewMetadata = Metadata.newInstance(context);
- previewMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
-
TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString());
previewMetadata.set(HttpHeaders.CONTENT_TYPE, JPEG_MIME);
- EmbeddedDocumentUtil.setGeneratedResourceName(previewMetadata,
- EmbeddedDocumentUtil.EmbeddedResourcePrefix.THUMBNAIL,
count, JPEG_MIME);
+ if (count == 0) {
+
previewMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+
TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString());
+
EmbeddedDocumentUtil.setGeneratedResourceName(previewMetadata,
+
EmbeddedDocumentUtil.EmbeddedResourcePrefix.THUMBNAIL, 0, JPEG_MIME);
+ } else {
+
previewMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+
TikaCoreProperties.EmbeddedResourceType.INLINE.toString());
+
EmbeddedDocumentUtil.setGeneratedResourceName(previewMetadata,
+ EmbeddedDocumentUtil.EmbeddedResourcePrefix.IMAGE,
count - 1,
+ JPEG_MIME);
+ }
count++;
if (!extractor.shouldParseEmbedded(previewMetadata, context)) {
continue;
diff --git
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/RawTiffParserTest.java
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/RawTiffParserTest.java
index dea3c32464..8a8138d757 100644
---
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/RawTiffParserTest.java
+++
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-image-module/src/test/java/org/apache/tika/parser/image/RawTiffParserTest.java
@@ -43,12 +43,28 @@ public class RawTiffParserTest extends TikaTest {
return getRecursiveMetadata(fileName, metadata);
}
- private void assertPreview(Metadata preview, int index, int width, int
height) {
+ /**
+ * The largest extracted preview: the file's thumbnail, which the parser
+ * emits before the smaller ones.
+ */
+ private void assertThumbnail(Metadata preview, int width, int height) {
+ assertPreview(preview,
TikaCoreProperties.EmbeddedResourceType.THUMBNAIL,
+ "thumbnail-0.jpg", width, height);
+ }
+
+ /**
+ * A smaller preview, emitted after the thumbnail as an inline image.
+ */
+ private void assertInlinePreview(Metadata preview, int index, int width,
int height) {
+ assertPreview(preview, TikaCoreProperties.EmbeddedResourceType.INLINE,
+ "image-" + index + ".jpg", width, height);
+ }
+
+ private void assertPreview(Metadata preview,
TikaCoreProperties.EmbeddedResourceType type,
+ String name, int width, int height) {
assertEquals("image/jpeg", preview.get(HttpHeaders.CONTENT_TYPE));
- assertEquals("thumbnail-" + index + ".jpg",
- preview.get(TikaCoreProperties.RESOURCE_NAME_KEY));
-
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
- preview.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
+ assertEquals(name, preview.get(TikaCoreProperties.RESOURCE_NAME_KEY));
+ assertEquals(type.toString(),
preview.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
assertEquals(Integer.toString(width), preview.get(TIFF.IMAGE_WIDTH));
assertEquals(Integer.toString(height), preview.get(TIFF.IMAGE_LENGTH));
}
@@ -63,7 +79,7 @@ public class RawTiffParserTest extends TikaTest {
assertEquals("NIKON CORPORATION", container.get(TIFF.EQUIPMENT_MAKE));
assertEquals("NIKON D3000", container.get(TIFF.EQUIPMENT_MODEL));
- assertPreview(metadataList.get(1), 0, 64, 48);
+ assertThumbnail(metadataList.get(1), 64, 48);
}
@Test
@@ -72,7 +88,7 @@ public class RawTiffParserTest extends TikaTest {
//it is extracted once, not per referencing IFD
List<Metadata> metadataList = parseByName("testNEF_dup.nef");
assertEquals(2, metadataList.size());
- assertPreview(metadataList.get(1), 0, 64, 48);
+ assertThumbnail(metadataList.get(1), 64, 48);
}
@Test
@@ -85,9 +101,9 @@ public class RawTiffParserTest extends TikaTest {
assertEquals("SONY", container.get(TIFF.EQUIPMENT_MAKE));
assertEquals("NEX-6", container.get(TIFF.EQUIPMENT_MODEL));
- //full-size preview from IFD0, then the thumbnail from IFD1
- assertPreview(metadataList.get(1), 0, 64, 48);
- assertPreview(metadataList.get(2), 1, 32, 24);
+ //full-size preview from IFD0, then the camera thumbnail from IFD1
+ assertThumbnail(metadataList.get(1), 64, 48);
+ assertInlinePreview(metadataList.get(2), 0, 32, 24);
}
@Test
@@ -99,9 +115,10 @@ public class RawTiffParserTest extends TikaTest {
assertEquals("image/x-raw-pentax",
container.get(HttpHeaders.CONTENT_TYPE));
assertEquals("PENTAX K-7", container.get(TIFF.EQUIPMENT_MODEL));
- //thumbnail from IFD1, then the full-size preview from IFD2
- assertPreview(metadataList.get(1), 0, 32, 24);
- assertPreview(metadataList.get(2), 1, 64, 48);
+ //the camera thumbnail comes first in the file (IFD1), the full-size
+ //preview second (IFD2): the larger one is still emitted first
+ assertThumbnail(metadataList.get(1), 64, 48);
+ assertInlinePreview(metadataList.get(2), 0, 32, 24);
}
@Test
@@ -115,7 +132,7 @@ public class RawTiffParserTest extends TikaTest {
assertEquals("image/x-raw-adobe",
container.get(HttpHeaders.CONTENT_TYPE));
assertEquals("PENTAX K-x", container.get(TIFF.EQUIPMENT_MODEL));
- assertPreview(metadataList.get(1), 0, 64, 48);
+ assertThumbnail(metadataList.get(1), 64, 48);
}
@Test
@@ -129,8 +146,8 @@ public class RawTiffParserTest extends TikaTest {
assertEquals("Canon EOS 7D", container.get(TIFF.EQUIPMENT_MODEL));
//full-size preview stored as a strip in IFD0, thumbnail from IFD1
- assertPreview(metadataList.get(1), 0, 64, 48);
- assertPreview(metadataList.get(2), 1, 32, 24);
+ assertThumbnail(metadataList.get(1), 64, 48);
+ assertInlinePreview(metadataList.get(2), 0, 32, 24);
}
@Test
@@ -151,7 +168,7 @@ public class RawTiffParserTest extends TikaTest {
assertEquals("image/x-raw-adobe",
container.get(HttpHeaders.CONTENT_TYPE));
assertNull(container.get(TikaCoreProperties.TIKA_META_EXCEPTION_WARNING));
- assertPreview(metadataList.get(1), 0, 64, 48);
+ assertThumbnail(metadataList.get(1), 64, 48);
}
@Test
@@ -203,7 +220,7 @@ public class RawTiffParserTest extends TikaTest {
getRecursiveMetadata("testARW.arw", parser, metadata, new
ParseContext(), false);
assertEquals(2, metadataList.size());
- assertPreview(metadataList.get(1), 0, 64, 48);
+ assertThumbnail(metadataList.get(1), 64, 48);
}
@Test