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

Reply via email to