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 a8b354f27f TIKA-4852: EpubParser emits the OPF cover image as a 
THUMBNAIL embedded document (#3093)
a8b354f27f is described below

commit a8b354f27fb2a229c7ca4bfeebe3c62c010cdb58
Author: Dominik Schmidt <[email protected]>
AuthorDate: Sat Aug 29 18:03:18 2026 +0200

    TIKA-4852: EpubParser emits the OPF cover image as a THUMBNAIL embedded 
document (#3093)
    
    * TIKA-4852 - emit the EPUB cover image as a THUMBNAIL embedded document
    
    The OPF names the cover: EPUB 3 with the cover-image property on the
    manifest item, EPUB 2 with a cover meta naming the item id. Resolve it
    (property first, meta as fallback) and emit that image as the THUMBNAIL,
    like the preview image of the other container formats.
    
    * TIKA-4852 - guard the font check against a manifest item without 
media-type
---
 CHANGES.txt                                        |  4 +++
 .../org/apache/tika/parser/epub/EpubParser.java    | 40 ++++++++++++++++++++--
 .../apache/tika/parser/epub/EpubParserTest.java    | 28 +++++++++++++++
 3 files changed, 69 insertions(+), 3 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index df8c73d3f1..fdb00f6e76 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,9 @@
 Release 4.1.0 - unreleased
 
+   * EpubParser emits the cover image named by the OPF (the EPUB 3
+     cover-image manifest property, or the EPUB 2 cover meta) as a THUMBNAIL
+     embedded document (TIKA-4852).
+
    * 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
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/epub/EpubParser.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/epub/EpubParser.java
index 00c3e79c8b..7428a5b6ae 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/epub/EpubParser.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/epub/EpubParser.java
@@ -282,8 +282,9 @@ public class EpubParser implements Parser {
                     continue;
                 }
                 if (shouldHandleEmbedded(hRefMediaPair.media)) {
-                    handleEmbedded(zipFile, relativePath, hRefMediaPair, 
embeddedDocumentExtractor,
-                            xhtml, metadata, context);
+                    handleEmbedded(zipFile, relativePath, hRefMediaPair,
+                            id.equals(contentOrderScraper.getCoverId()),
+                            embeddedDocumentExtractor, xhtml, metadata, 
context);
                 }
             }
         }
@@ -423,7 +424,12 @@ public class EpubParser implements Parser {
         return true;
     }
 
+    /**
+     * @param cover whether this is the publication's cover image, emitted as
+     *              the {@link 
TikaCoreProperties.EmbeddedResourceType#THUMBNAIL}
+     */
     private void handleEmbedded(ZipFile zipFile, String relativePath, 
HRefMediaPair hRefMediaPair,
+                                boolean cover,
                                 EmbeddedDocumentExtractor 
embeddedDocumentExtractor,
                                 XHTMLContentHandler xhtml, Metadata 
parentMetadata,
                                 ParseContext context)
@@ -442,6 +448,10 @@ public class EpubParser implements Parser {
             embeddedMetadata.set(HttpHeaders.CONTENT_TYPE, 
hRefMediaPair.media);
         }
         embeddedMetadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, fullPath);
+        if (cover) {
+            embeddedMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
+                    
TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString());
+        }
         if (!embeddedDocumentExtractor.shouldParseEmbedded(embeddedMetadata, 
context)) {
             return;
         }
@@ -458,7 +468,8 @@ public class EpubParser implements Parser {
         xhtml.startElement("div", "class", "embedded");
         try {
             boolean outputHtml = true;
-            if (hRefMediaPair.media.contains("font") || 
hRefMediaPair.href.startsWith("fonts")) {
+            if ((hRefMediaPair.media != null && 
hRefMediaPair.media.contains("font"))
+                    || hRefMediaPair.href.startsWith("fonts")) {
                 outputHtml = false;
             }
             embeddedDocumentExtractor
@@ -524,9 +535,20 @@ public class EpubParser implements Parser {
 
         Map<String, HRefMediaPair> locationMap = new HashMap<>();
         List<String> contentItems = new ArrayList<>();
+        //the cover image: EPUB 3 marks its manifest item with the
+        //cover-image property, EPUB 2 names the item id in a cover meta
+        String coverImageItem;
+        String coverMetaItem;
         boolean inManifest = false;
         boolean inSpine = false;
 
+        /**
+         * The manifest id of the cover image, or null if the OPF names none.
+         */
+        String getCoverId() {
+            return coverImageItem != null ? coverImageItem : coverMetaItem;
+        }
+
         @Override
         public void startElement(String uri, String localName, String name, 
Attributes atts)
                 throws SAXException {
@@ -547,8 +569,20 @@ public class EpubParser implements Parser {
                             //swallow
                         }
                         locationMap.put(id, new HRefMediaPair(href, mime));
+                        String properties = 
XMLReaderUtils.getAttrValue("properties", atts);
+                        if (coverImageItem == null && properties != null
+                                && 
Arrays.asList(properties.trim().split("\\s+"))
+                                .contains("cover-image")) {
+                            coverImageItem = id;
+                        }
                     }
                 }
+            } else if ("meta".equalsIgnoreCase(localName) && coverMetaItem == 
null
+                    && "cover".equals(XMLReaderUtils.getAttrValue("name", 
atts))) {
+                String content = XMLReaderUtils.getAttrValue("content", atts);
+                if (!StringUtils.isBlank(content)) {
+                    coverMetaItem = content.trim();
+                }
             }
             if (inSpine) {
                 if ("itemRef".equalsIgnoreCase(localName)) {
diff --git 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/epub/EpubParserTest.java
 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/epub/EpubParserTest.java
index 8ab6c031bc..0e133b3a02 100644
--- 
a/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/epub/EpubParserTest.java
+++ 
b/tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/epub/EpubParserTest.java
@@ -17,6 +17,8 @@
 package org.apache.tika.parser.epub;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.util.Arrays;
@@ -74,6 +76,11 @@ public class EpubParserTest extends TikaTest {
         //test attachments
         assertEquals(2, metadataList.size());
         assertEquals("image/jpeg", 
metadataList.get(1).get(HttpHeaders.CONTENT_TYPE));
+        //the EPUB 2 cover meta names the cover image, emitted as the thumbnail
+        assertEquals("OPS/CoverDesign.jpg",
+                metadataList.get(1).get(TikaCoreProperties.RESOURCE_NAME_KEY));
+        
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
+                
metadataList.get(1).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
         String xml = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
         int tocIndex = xml.indexOf("h3 class=\"toc_heading\">Table of 
Contents<");
         int ch1 = xml.indexOf("<h1>Chapter 1");
@@ -124,6 +131,27 @@ public class EpubParserTest extends TikaTest {
         assertEquals("pre-paginated", 
metadataList.get(0).get(Epub.RENDITION_LAYOUT));
     }
 
+    /**
+     * The EPUB 3 cover-image manifest property names the cover; the other
+     * images are not thumbnails.
+     */
+    @Test
+    public void testEpub3CoverImageIsTheThumbnail() throws Exception {
+        List<Metadata> metadataList = 
getRecursiveMetadata("testEPUB_multi-metadata-vals.epub");
+        Metadata cover = null;
+        for (Metadata m : metadataList) {
+            if 
("epub/images/cover.jpg".equals(m.get(TikaCoreProperties.RESOURCE_NAME_KEY))) {
+                cover = m;
+            } else {
+                
assertNotEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
+                        m.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
+            }
+        }
+        assertNotNull(cover);
+        
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
+                cover.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
+    }
+
     @Test
     public void testMultipleMetadataValues() throws Exception {
         //TIKA_4466

Reply via email to