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