Skip to content

TIKA-4856: /unpack/thumbnail returns the document thumbnail with its metadata - #3096

Open
dschmidt wants to merge 24 commits into
apache:mainfrom
dschmidt:unpack-thumbnail
Open

TIKA-4856: /unpack/thumbnail returns the document thumbnail with its metadata#3096
dschmidt wants to merge 24 commits into
apache:mainfrom
dschmidt:unpack-thumbnail

Conversation

@dschmidt

@dschmidt dschmidt commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Proof of concept for TIKA-4856, restructured after the discussion in the ticket. The core is the thumbnail defaults and the switch that applies them to the existing endpoints; that is what a client needs to get a document's thumbnail without knowing parser component names. The /unpack/thumbnail endpoint is a convenience on top and can be dropped or reshaped without losing the rest. The shape of the switch (query parameter) and of the config block (top-level key) are open to change.

Why

The thumbnail of a document is one of its embedded documents, typed THUMBNAIL, and /rmeta lists it already. Where a raster image only exists after rendering (the first page of a PDF, the EMF/WMF thumbnail of an Office document), a request needs a parse context that knows the component names, and that context should be the same for every caller.

Thumbnail defaults

ThumbnailDefaults (tika-server-core) holds that context in one place: first PDF page rendered at 96 dpi (maxRenderedPages: 1, so the text is still extracted from the whole document), EMF/WMF thumbnail rendered (that one only, renderOnlyEmbeddedResourceTypes: ["THUMBNAIL"] from #3095).

Three layers, each overriding the one before: built-in defaults, a thumbnail-defaults block in the server config (same shape as a request config), the request's own config part. The defaults are plain JSON parser configurations set with ParseContext.setJsonConfig, so a parser that is not installed never reads them.

?renderThumbnails=true

On /rmeta, /unpack, /unpack/all and /unpack/thumbnail: lays the defaults under the request. Without it a parse is the cheap extraction of stored thumbnails; rendering is opt-in, with the same switch everywhere. The index request of a search service becomes /rmeta/text?renderThumbnails=true: metadata, text, and the thumbnail with its dimensions in one parse.

/unpack/thumbnail (optional)

What only makes sense when the thumbnail is all the caller wants (no text extraction, no OCR, only THUMBNAIL and RENDERING extracted, image capped at 32 MiB), plus the defaults when renderThumbnails=true; a PDF without the switch answers 204. ThumbnailSelector picks the raster THUMBNAIL below the document, the rendering below a vector THUMBNAIL, or the first page RENDERING, and answers as JSON:

{
  "metadata": { "Content-Type": "image/png", "tiff:ImageWidth": "800", "tk:embedded-resource-type": "RENDERING", ... },
  "image": "iVBORw0KGgo..."
}

PDFParserConfig.maxRenderedPages

Bounds the page rendering of both rendering strategies independently of maxPages; without it /rmeta could only render the first page by also cutting the text after it.

Verified

Against a server built from main plus the open thumbnail PRs: docx, xlsx, doc, xls, ppt, pptx, odt, epub, GeoGebra, Pages, Numbers, Keynote, mp3, m4a, flac, ogg, pdf, nef and pef answer with the right image; a zip, a plain jpeg and a doc without a thumbnail answer 204. Raw camera files no longer need their file name since #3099.

Open points

  • thumbnail-defaults is a top-level config key, which meant adding it to the known keys in TikaJsonConfig (tika-serialization). Putting it under server would avoid that at the cost of a field in the server config class; fine either way.
  • embedded-limits.maxDepth is 3 in the fixed context because of TIKA-4857, 2 once that is fixed.

https://issues.apache.org/jira/browse/TIKA-4856

… metadata

Parses in unpack mode with a fixed configuration (the first PDF page and
EMF/WMF images rendered, THUMBNAIL and RENDERING embedded documents
extracted with their metadata), then picks the raster THUMBNAIL directly
below the document, the rendering of a vector THUMBNAIL, or the RENDERING
of the first page, and answers with JSON: the embedded document's metadata
and the image as base64. 204 when the document has no thumbnail.
@dschmidt
dschmidt marked this pull request as ready for review August 29, 2026 16:45
@dschmidt
dschmidt marked this pull request as draft August 29, 2026 18:51
@dschmidt
dschmidt marked this pull request as ready for review August 29, 2026 18:53
@dschmidt
dschmidt marked this pull request as draft August 29, 2026 19:14
@dschmidt

Copy link
Copy Markdown
Contributor Author

Remarked it as draft as I'm wondering if we need a better concept

…unpack, thumbnail-defaults in the server config, PDF maxRenderedPages
# Conflicts:
#	CHANGES.txt
@dschmidt
dschmidt marked this pull request as ready for review August 29, 2026 20:19
@THausherr
THausherr requested a lite review from Copilot August 29, 2026 20:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class thumbnail support to Tika Server by centralizing “thumbnail defaults” (PDF first-page rendering + EMF/WMF thumbnail rendering) and exposing them via query flags and a new convenience endpoint, while also introducing a PDF parser limit to bound rendering without limiting text extraction.

Changes:

  • Introduces ThumbnailDefaults and ThumbnailSelector, enabling consistent server-side thumbnail rendering/selection behavior.
  • Adds ?renderThumbnails=true to /rmeta, /unpack, and /unpack/all, plus a new /unpack/thumbnail endpoint returning {metadata, image(base64)}.
  • Adds PDFParserConfig.maxRenderedPages and enforces it in both PDF rendering paths, with new tests.

Reviewed changes

Copilot reviewed 15 out of 17 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerThumbnailTest.java End-to-end tests for /unpack/thumbnail and /rmeta?renderThumbnails=true.
tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/resource/ThumbnailSelectorTest.java Unit tests for thumbnail selection precedence rules.
tika-server/tika-server-core/src/test/java/org/apache/tika/server/core/resource/ThumbnailDefaultsTest.java Unit tests for built-in defaults, config overrides, and merge behavior.
tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/UnpackerResource.java Adds /unpack/thumbnail and renderThumbnails query param support for unpack endpoints.
tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResource.java Wires ThumbnailDefaults from server config and exposes them to resources.
tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/ThumbnailSelector.java Implements thumbnail selection logic among embedded docs (thumbnail vs rendering fallback).
tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/ThumbnailDefaults.java Defines built-in thumbnail parser JSON defaults + config override/merge/application logic.
tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/RecursiveMetadataResource.java Adds renderThumbnails query param to /rmeta endpoints and applies defaults.
tika-serialization/src/main/java/org/apache/tika/config/loader/TikaJsonConfig.java Adds thumbnail-defaults as a known top-level JSON config key.
tika-parsers/.../src/test/java/org/apache/tika/parser/pdf/PDFMaxRenderedPagesTest.java Tests for maxRenderedPages behavior and JSON config support.
tika-parsers/.../src/main/java/org/apache/tika/parser/pdf/PDFParserConfig.java Adds maxRenderedPages with validation and accessor methods.
tika-parsers/.../src/main/java/org/apache/tika/parser/pdf/PDFParser.java Uses maxRenderedPages to bound rendered page range for PDF rendering.
tika-parsers/.../src/main/java/org/apache/tika/parser/pdf/PDF2XHTML.java Skips per-page rendering after maxRenderedPages for page-end rendering strategy.
docs/modules/ROOT/pages/using-tika/server/index.adoc Documents thumbnail behavior, query flag, endpoint, and config override block.
CHANGES.txt Adds release notes for the new thumbnail capabilities and maxRenderedPages.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@dschmidt

Copy link
Copy Markdown
Contributor Author

Mh, maybe the /thumbnail endpoint should have the renderThumbnails param as well. So we can still opt in to rendering while using the selection logic anyhow. So it's a simple consistent switch for pure extraction or more expensive rendering

@dschmidt

Copy link
Copy Markdown
Contributor Author

Done: /unpack/thumbnail takes renderThumbnails as well. Without it a request is the cheap extraction of stored thumbnails, rendering is opt-in with the same switch everywhere.

# Conflicts:
#	CHANGES.txt

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 17 changed files in this pull request and generated 2 comments.

@THausherr
THausherr requested a lite review from Copilot August 31, 2026 03:35
@THausherr
THausherr requested a lite review from Copilot September 2, 2026 15:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 15 out of 17 changed files in this pull request and generated 5 comments.

…entry, clean up after the test

- ThumbnailDefaults.with(ThumbnailDefaults) serialized each component to a
  JSON string and parsed it back, once per component and per request; both
  overloads now merge the nodes, and a component that only the other side
  has is copied rather than shared.
- the loop that maps the selected metadata back to its zip entry kept
  scanning after it had found it.
- the test left its unpack directory behind, where UnpackerResourceTest
  deletes its own.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It changes server API behavior and parsing/rendering controls across multiple modules (server + PDF parser), warranting final human validation despite only minor actionable feedback.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/UnpackerThumbnailTest.java:203

  • The test asserts response.getMediaType().toString() equals exactly application/json, which can be brittle because toString() may include parameters (e.g., a charset) depending on the JAX-RS provider/CXF behavior. Other server tests typically assert on type/subtype instead, which avoids false failures when parameters are present.
  • Files reviewed: 15/17 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@tballison

tballison commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Argh. I'm sorry. I thought I posted a review yesterday. Something went wrong.

I'm hesitant to add a special endpoint/parameter for thumbnails.

I asked 🤖 to come up with a plan to get you what you want largely with what exists and some of what you've added.

Let me know what you think about this. Also, fellow devs, please join in the conversation.

This is what it came up with:

  What the PR adds vs. what exists

  ┌───────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  │                         PR piece                          │                                               Existing equivalent                                                │
  ├───────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ ThumbnailDefaults (built-in → server block → request,     │ parse-context section in tika-config.json (baseline, loaded by the forked worker from the same file) + the       │
  │ per-component merge)                                      │ multipart config part (runtime delta, overlaid in the worker)                                                    │
  ├───────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ ?renderThumbnails=true query param                        │ sending the same parser blocks in the request's config part                                                      │
  ├───────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ /unpack/thumbnail + ThumbnailSelector                     │ standard-unpack-selector already has includeEmbeddedResourceTypes — filter to THUMBNAIL/RENDERING on plain       │
  │                                                           │ /unpack                                                                                                          │
  ├───────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ thumbnail-defaults top-level key + TikaJsonConfig change  │ not needed                                                                                                       │
  ├───────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
  │ PDFParserConfig.maxRenderedPages                          │ no equivalent — this is the one genuinely missing piece (without it, rendering only page 1 forces maxPages: 1,   │
  │                                                           │ which cuts the text too)                                                                                         │
  └───────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

  So my recommendation: keep maxRenderedPages (+ its test, PDF2XHTML/PDFParser wiring — it's pure parser config, exactly your preferred vehicle), drop the endpoint, the query
  params, ThumbnailDefaults, and ThumbnailSelector, and turn the built-in defaults JSON into a documented recipe (server docs + the ticket). The recipe is the deliverable — it's
  what the contributor actually needed a central place for.

  Literal JSON

  Initialization time — tika-config.json on a server that should render thumbnails on every parse (the forked worker loads this same file, so it applies to all endpoints with no
  request cooperation):

  {
    "parse-context": {
      "pdf-parser": {
        "imageStrategy": "RENDER_PAGES_AT_PAGE_END",
        "maxRenderedPages": 1,
        "ocr": { "dpi": 96, "imageType": "RGB" }
      },
      "emf-parser": { "renderImage": true, "renderOnlyEmbeddedResourceTypes": ["THUMBNAIL"] },
      "wmf-parser": { "renderImage": true, "renderOnlyEmbeddedResourceTypes": ["THUMBNAIL"] }
    }
  }

  Parse time, scenario A — the contributor's search-indexing case (metadata + text + thumbnail in one parse). POST /rmeta/config, multipart parts file + config, config part:

  {
    "parse-context": {
      "pdf-parser": {
        "imageStrategy": "RENDER_PAGES_AT_PAGE_END",
        "maxRenderedPages": 1,
        "ocr": { "dpi": 96, "imageType": "RGB" }
      },
      "emf-parser": { "renderImage": true, "renderOnlyEmbeddedResourceTypes": ["THUMBNAIL"] },
      "wmf-parser": { "renderImage": true, "renderOnlyEmbeddedResourceTypes": ["THUMBNAIL"] }
    }
  }

  Parse time, scenario B — "just give me the thumbnail" (replaces /unpack/thumbnail). POST /unpack, config part:

  {
    "parse-context": {
      "pdf-parser": {
        "imageStrategy": "RENDER_PAGES_AT_PAGE_END",
        "maxRenderedPages": 1,
        "ocr": { "dpi": 96, "imageType": "RGB" }
      },
      "emf-parser": { "renderImage": true, "renderOnlyEmbeddedResourceTypes": ["THUMBNAIL"] },
      "wmf-parser": { "renderImage": true, "renderOnlyEmbeddedResourceTypes": ["THUMBNAIL"] },
      "standard-unpack-selector": { "includeEmbeddedResourceTypes": ["THUMBNAIL", "RENDERING"] },
      "unpack-config": { "suffixStrategy": "DETECTED", "outputFormat": "FRICTIONLESS",
                         "outputMode": "ZIPPED", "includeFullMetadata": true },
      "embedded-limits": { "maxDepth": 3, "maxCount": 20 }
    }
  }

@dschmidt

dschmidt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Just to make sure I get it right:
🤖 suggests to just document what parse-context to use for the specific rendering thumbnail extraction behavior?

It's exactly what I wanted to avoid to be honest - it doesn't make sense as global config (in the metadata request I want OCR, in the thumbnails request I don't), I don't want to hardcode this in my application and I don't want to make it config in my application.

The idea is to make it easy to retrieve thumbnails (as part of a metadata request and also as a standalone "just give me a thumbnail, I don't care").

In my use case (OpenCloud) I want to support previews for as many file formats as possible and Tika itself will always know better what it supports than any third party consumer. Moreover, I would really like to avoid having to maintain configs for different Tika versions,

Maybe we can have some kind of preset functionality?
Tika could provide some default presets like render-thumbnails but users could also provide custom ones via settings ... I'm completely open to any idea or suggestion, I just really want to keep this config out of consuming applications

P.S.: yes, of course we can split maxRenderedPages out of this PR.

@tballison

Copy link
Copy Markdown
Contributor

This would leave client side to arbitrate thumbnail vs rendering. This is an option to leave just one per:

Yes — there's a properly Tika-shaped home for it: ship the priority logic as an UnpackSelector implementation, registered as a @TikaComponent. That's exactly what the pluggable
  selector interface exists for, and the PR's own docs already point people there ("Custom EmbeddedDocumentExtractor is ignored; use UnpackSelector"). The contributor keeps his
  selection logic server-side, it's addressed purely by config name — and it gets something his endpoint never had: it works everywhere pipes UNPACK mode works, including
  tika-async-cli batch runs, not just one tika-server route.

  Concretely: a ThumbnailUnpackSelector next to StandardUnpackSelector in tika-pipes-core, encoding his priority (raster THUMBNAIL at depth 1 → RENDERING below a vector THUMBNAIL →
  first-page RENDERING), selecting at most one document. Parse time:

  {
    "parse-context": {
      "pdf-parser": { "imageStrategy": "RENDER_PAGES_AT_PAGE_END", "maxRenderedPages": 1,
                      "ocr": { "dpi": 96, "imageType": "RGB" } },
      "emf-parser": { "renderImage": true, "renderOnlyEmbeddedResourceTypes": ["THUMBNAIL"] },
      "wmf-parser": { "renderImage": true, "renderOnlyEmbeddedResourceTypes": ["THUMBNAIL"] },
      "thumbnail-unpack-selector": {},
      "unpack-config": { "outputFormat": "FRICTIONLESS", "outputMode": "ZIPPED",
                         "includeFullMetadata": true },
      "embedded-limits": { "maxDepth": 3, "maxCount": 20 }
    }
  }

  Result: a package containing exactly one image plus its metadata — functionally his {metadata, image} response, minus the bespoke route. Or baked into tika-config.json's
  parse-context for a dedicated thumbnail server with zero request config.

@tballison

Copy link
Copy Markdown
Contributor

Let me think some more....

@tballison

Copy link
Copy Markdown
Contributor

Like this?

 {
    "presets": {
      "render-thumbnails": {
        "pdf-parser": { "imageStrategy": "RENDER_PAGES_AT_PAGE_END", "maxRenderedPages": 1,
                        "ocr": { "dpi": 150, "imageType": "RGB" } }
      },
      "our-archival-ocr": {
        "pdf-parser": { "ocr": { "strategy": "OCR_AND_TEXT_EXTRACTION", "dpi": 300 } }
      }
    }
  }

  (First block overrides a built-in by name — admin wins over Tika; second is a purely custom one.)

  Parse time — the reference lives in the config part, next to (and under) the request's own parse-context:

  { "presets": ["render-thumbnails"] }

  on POST /rmeta/text/config for his index request — metadata, text, and thumbnail in one parse. And the standalone "just give me a thumbnail, I don't care" case is POST /unpack
  with:

  { "presets": ["thumbnail"] }

  where thumbnail is a second built-in preset that bundles render-thumbnails plus the selection: thumbnail-unpack-selector (his ThumbnailSelector logic as an UnpackSelector
  component), unpack-config with Frictionless output, and embedded-limits. One image plus its metadata comes back; no /unpack/thumbnail route needed. A request can still layer its
  own overrides: {"presets": ["render-thumbnails"], "parse-context": {"pdf-parser": {...}}} — request wins, per component, which is exactly the layering semantics
  ThumbnailDefaults.applyTo already implements.

@dschmidt

dschmidt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Pretty much, yeah! :)

tbd what should be part of which preset (e.g. how comes disabling OCR into play) but yeah - I want to develop against an abstract concept, not against a gazillion of granular settings

@tballison

Copy link
Copy Markdown
Contributor

This would also allow initialization/baked in settings so that we could still prevent actual users from modifying settings outside of the allowed set of presets.

Do we allow one preset per call, rather than the array? More than one gets confusing to me on precedence.

@dschmidt

dschmidt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Not sure, I think it could be documented in which order they are merged and how (deep-merging objects is usually clear, arrays maybe also merged, unfortunately means they cant be reset)

Certainly easier documentation than documenting all the neccessary knobs to get all possible thumbnails :)

@dschmidt

dschmidt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Then again: a single preset is already much better than no preset support at all :)

@tballison

tballison commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Let's start with a single preset allowed and no customizations. Users have a preset or customization or nothing.

We can add complexity if necessary later?

@dschmidt

dschmidt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

so something like this:

 {
    "presets": ["thumbnails"],
    "parse-context": {
       "pdf-parser": ...
     }
  }

?

Or would you go with singular form "preset" from the beginning on?

@tballison

tballison commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

We should keep /config for literal config... for the sake of network/proxy blocking as an extra security layer.

How about:

PUT /tika/text/preset/render-thumbnails        body = document
PUT /rmeta/text/preset/render-thumbnails       body = document
PUT /unpack/preset/thumbnail                   body = document

@tballison

tballison commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

I'm really worried about debugging precedence with presets AND config, and then also with multiple presets.

Will a single preset be enough?

@dschmidt

dschmidt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Not sure I would want to put that into stone, but - as I said - a single preset is already much better than no preset ... sooo ... 😅

@tballison

Copy link
Copy Markdown
Contributor

If a user can tweak a preset or select multiple presets, they can send a config...

But seriously, this is all off the top of my head. Let me know what you think.

@dschmidt

dschmidt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

We should keep /config for literal config... for the sake of network/proxy blocking as an extra security layer.

How about:

PUT /tika/text/preset/render-thumbnails        body = document
PUT /rmeta/text/preset/render-thumbnails       body = document
PUT /unpack/preset/thumbnail                   body = document

This is a bit awkward to me to be honest, I would prefer the preset in body or as query param behind ? - not in the url like this, that's pretty inflexible and doesnt work with other params at the same time..

apart from that ... I don't care too much tbh. I just care about the outcome (easy thumbnail access :))

@dschmidt

dschmidt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Would you be able/willing to work on this?

I think you have stronger opinions than I have (and know Tika a lot better obviously) and it might be easier for you to steer your 🤖 directly than for you to steer my 🤖 via me as proxy

@tballison

Copy link
Copy Markdown
Contributor

LOL..., sure.

Apologies, but I"m going to start with:

/rmeta/text                →  /rmeta/preset/render-thumbnails/text
  /rmeta                     →  /rmeta/preset/render-thumbnails
  /tika/text                 →  /tika/preset/ocr-heavy/text
  /tika                      →  /tika/preset/ocr-heavy
  /unpack                    →  /unpack/preset/thumbnail
  /unpack/all                →  /unpack/preset/thumbnail/all

@dschmidt

dschmidt commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

ok, boss 😁

@dschmidt dschmidt mentioned this pull request Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants