Skip to content

Commit a8b354f

Browse files
authored
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
1 parent 66d318a commit a8b354f

3 files changed

Lines changed: 69 additions & 3 deletions

File tree

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
Release 4.1.0 - unreleased
22

3+
* EpubParser emits the cover image named by the OPF (the EPUB 3
4+
cover-image manifest property, or the EPUB 2 cover meta) as a THUMBNAIL
5+
embedded document (TIKA-4852).
6+
37
* RawTiffParser marks only the largest embedded JPEG preview as the
48
THUMBNAIL embedded document; the smaller previews of the same image are
59
INLINE images named image-N.jpg. Previously every preview was a

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/main/java/org/apache/tika/parser/epub/EpubParser.java

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,9 @@ private Set<String> bufferedParseZipFile(ZipFile zipFile, ContentHandler bodyHan
282282
continue;
283283
}
284284
if (shouldHandleEmbedded(hRefMediaPair.media)) {
285-
handleEmbedded(zipFile, relativePath, hRefMediaPair, embeddedDocumentExtractor,
286-
xhtml, metadata, context);
285+
handleEmbedded(zipFile, relativePath, hRefMediaPair,
286+
id.equals(contentOrderScraper.getCoverId()),
287+
embeddedDocumentExtractor, xhtml, metadata, context);
287288
}
288289
}
289290
}
@@ -423,7 +424,12 @@ private boolean shouldHandleEmbedded(String media) {
423424
return true;
424425
}
425426

427+
/**
428+
* @param cover whether this is the publication's cover image, emitted as
429+
* the {@link TikaCoreProperties.EmbeddedResourceType#THUMBNAIL}
430+
*/
426431
private void handleEmbedded(ZipFile zipFile, String relativePath, HRefMediaPair hRefMediaPair,
432+
boolean cover,
427433
EmbeddedDocumentExtractor embeddedDocumentExtractor,
428434
XHTMLContentHandler xhtml, Metadata parentMetadata,
429435
ParseContext context)
@@ -442,6 +448,10 @@ private void handleEmbedded(ZipFile zipFile, String relativePath, HRefMediaPair
442448
embeddedMetadata.set(HttpHeaders.CONTENT_TYPE, hRefMediaPair.media);
443449
}
444450
embeddedMetadata.set(TikaCoreProperties.RESOURCE_NAME_KEY, fullPath);
451+
if (cover) {
452+
embeddedMetadata.set(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE,
453+
TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString());
454+
}
445455
if (!embeddedDocumentExtractor.shouldParseEmbedded(embeddedMetadata, context)) {
446456
return;
447457
}
@@ -458,7 +468,8 @@ private void handleEmbedded(ZipFile zipFile, String relativePath, HRefMediaPair
458468
xhtml.startElement("div", "class", "embedded");
459469
try {
460470
boolean outputHtml = true;
461-
if (hRefMediaPair.media.contains("font") || hRefMediaPair.href.startsWith("fonts")) {
471+
if ((hRefMediaPair.media != null && hRefMediaPair.media.contains("font"))
472+
|| hRefMediaPair.href.startsWith("fonts")) {
462473
outputHtml = false;
463474
}
464475
embeddedDocumentExtractor
@@ -524,9 +535,20 @@ private static class ContentOrderScraper extends DefaultHandler {
524535

525536
Map<String, HRefMediaPair> locationMap = new HashMap<>();
526537
List<String> contentItems = new ArrayList<>();
538+
//the cover image: EPUB 3 marks its manifest item with the
539+
//cover-image property, EPUB 2 names the item id in a cover meta
540+
String coverImageItem;
541+
String coverMetaItem;
527542
boolean inManifest = false;
528543
boolean inSpine = false;
529544

545+
/**
546+
* The manifest id of the cover image, or null if the OPF names none.
547+
*/
548+
String getCoverId() {
549+
return coverImageItem != null ? coverImageItem : coverMetaItem;
550+
}
551+
530552
@Override
531553
public void startElement(String uri, String localName, String name, Attributes atts)
532554
throws SAXException {
@@ -547,8 +569,20 @@ public void startElement(String uri, String localName, String name, Attributes a
547569
//swallow
548570
}
549571
locationMap.put(id, new HRefMediaPair(href, mime));
572+
String properties = XMLReaderUtils.getAttrValue("properties", atts);
573+
if (coverImageItem == null && properties != null
574+
&& Arrays.asList(properties.trim().split("\\s+"))
575+
.contains("cover-image")) {
576+
coverImageItem = id;
577+
}
550578
}
551579
}
580+
} else if ("meta".equalsIgnoreCase(localName) && coverMetaItem == null
581+
&& "cover".equals(XMLReaderUtils.getAttrValue("name", atts))) {
582+
String content = XMLReaderUtils.getAttrValue("content", atts);
583+
if (!StringUtils.isBlank(content)) {
584+
coverMetaItem = content.trim();
585+
}
552586
}
553587
if (inSpine) {
554588
if ("itemRef".equalsIgnoreCase(localName)) {

tika-parsers/tika-parsers-standard/tika-parsers-standard-modules/tika-parser-miscoffice-module/src/test/java/org/apache/tika/parser/epub/EpubParserTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.apache.tika.parser.epub;
1818

1919
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertNotEquals;
21+
import static org.junit.jupiter.api.Assertions.assertNotNull;
2022
import static org.junit.jupiter.api.Assertions.assertTrue;
2123

2224
import java.util.Arrays;
@@ -74,6 +76,11 @@ public void testEpubOrder() throws Exception {
7476
//test attachments
7577
assertEquals(2, metadataList.size());
7678
assertEquals("image/jpeg", metadataList.get(1).get(HttpHeaders.CONTENT_TYPE));
79+
//the EPUB 2 cover meta names the cover image, emitted as the thumbnail
80+
assertEquals("OPS/CoverDesign.jpg",
81+
metadataList.get(1).get(TikaCoreProperties.RESOURCE_NAME_KEY));
82+
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
83+
metadataList.get(1).get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
7784
String xml = metadataList.get(0).get(TikaCoreProperties.TIKA_CONTENT);
7885
int tocIndex = xml.indexOf("h3 class=\"toc_heading\">Table of Contents<");
7986
int ch1 = xml.indexOf("<h1>Chapter 1");
@@ -124,6 +131,27 @@ public void testPrePaginated() throws Exception {
124131
assertEquals("pre-paginated", metadataList.get(0).get(Epub.RENDITION_LAYOUT));
125132
}
126133

134+
/**
135+
* The EPUB 3 cover-image manifest property names the cover; the other
136+
* images are not thumbnails.
137+
*/
138+
@Test
139+
public void testEpub3CoverImageIsTheThumbnail() throws Exception {
140+
List<Metadata> metadataList = getRecursiveMetadata("testEPUB_multi-metadata-vals.epub");
141+
Metadata cover = null;
142+
for (Metadata m : metadataList) {
143+
if ("epub/images/cover.jpg".equals(m.get(TikaCoreProperties.RESOURCE_NAME_KEY))) {
144+
cover = m;
145+
} else {
146+
assertNotEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
147+
m.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
148+
}
149+
}
150+
assertNotNull(cover);
151+
assertEquals(TikaCoreProperties.EmbeddedResourceType.THUMBNAIL.toString(),
152+
cover.get(TikaCoreProperties.EMBEDDED_RESOURCE_TYPE));
153+
}
154+
127155
@Test
128156
public void testMultipleMetadataValues() throws Exception {
129157
//TIKA_4466

0 commit comments

Comments
 (0)