This is an automated email from the ASF dual-hosted git repository.
tballison 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 ba43e7d4ce TIKA-4900: lower defaults (#3197)
ba43e7d4ce is described below
commit ba43e7d4ce468e42d78c077c16568329a822cde5
Author: Tim Allison <[email protected]>
AuthorDate: Fri Sep 18 15:05:42 2026 -0400
TIKA-4900: lower defaults (#3197)
---
CHANGES.txt | 3 +++
.../ROOT/pages/configuration/inference.adoc | 24 +++++++++++++---------
.../apache/tika/cli/TikaCLIMediaBindingsTest.java | 12 +++++------
.../apache/tika/parser/inference/MediaConfig.java | 5 +++--
.../tika/inference/EmbedTaskMediaContractTest.java | 8 ++++----
.../apache/tika/inference/EmbedTaskMediaTest.java | 19 ++++++++---------
.../apache/tika/inference/FfmpegSegmenterTest.java | 16 +++++++++------
.../tika/config/loader/InferenceLoaderTest.java | 2 +-
8 files changed, 50 insertions(+), 39 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 75af8ccca4..3336a59aac 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -25,6 +25,9 @@ Release 4.1.0 - unreleased
bytes twice. (TIKA-4901).
* Enable audio/video chunking with ffmpeg and inference (TIKA-4900).
+ The default segment grid is 25 s windows with 5 s overlap: hosted omni
+ engines cap an audio item at 30 s inclusive, so the earlier 30 s default
+ had every audio segment refused.
* Refactor OCR and inference to use engines for inference and
page rendering (TIKA-4889).
diff --git a/docs/modules/ROOT/pages/configuration/inference.adoc
b/docs/modules/ROOT/pages/configuration/inference.adoc
index 641003a3f0..d31a72d2f2 100644
--- a/docs/modules/ROOT/pages/configuration/inference.adoc
+++ b/docs/modules/ROOT/pages/configuration/inference.adoc
@@ -339,11 +339,12 @@ in the parse context:
[source,json]
----
-{ "parse-context": { "media": { "segment": { "seconds": 30, "overlap": 5 },
"maxSegments": 480 } } }
+{ "parse-context": { "media": { "segment": { "seconds": 25, "overlap": 5 },
"maxSegments": 480 } } }
----
-Those are the defaults: 30 s windows, each starting 25 s after the last, so
any event of
-5 s or less is whole in at least one segment; `maxSegments` caps a document,
the rest of the
+Those are the defaults: 25 s windows, each starting 20 s after the last, so
any event of
+5 s or less is whole in at least one segment, and no audio item reaches the 30
s cap the
+hosted omni engines enforce (see <<jina-omni>>); `maxSegments` caps a
document, the rest of the
file is not embedded. A block with `overlap` outside `[0, seconds)`, or
`maxSegments` of 0
or below -1, fails at config load and per request. The segmenter defines the
unit and every binding on the document sees
the same segments, so each segment's chunks share the segment's id: a chunk
carries a
@@ -354,10 +355,10 @@ segment's id. Group by `correlator` to get a segment with
both channels:
[source,json]
----
[
- { "producer": "jina-video", "modality": "visual", "correlator":
"t:25000-55000",
- "vector": "<base64 float32>", "locators": { "temporal": [{"start_ms":
25000, "end_ms": 55000}] } },
- { "producer": "jina-audio", "modality": "audio", "correlator":
"t:25000-55000",
- "vector": "<base64 float32>", "locators": { "temporal": [{"start_ms":
25000, "end_ms": 55000}] } }
+ { "producer": "jina-video", "modality": "visual", "correlator":
"t:20000-45000",
+ "vector": "<base64 float32>", "locators": { "temporal": [{"start_ms":
20000, "end_ms": 45000}] } },
+ { "producer": "jina-audio", "modality": "audio", "correlator":
"t:20000-45000",
+ "vector": "<base64 float32>", "locators": { "temporal": [{"start_ms":
20000, "end_ms": 45000}] } }
]
----
@@ -385,6 +386,7 @@ the wait counted as progress. When the budget runs out
mid-file the task writes
it has embedded, stops, and reports the timeout once as the binding's warning
on the root
document; every later MEDIA binding's first probe fails fast the same way.
+[[jina-omni]]
=== Jina omni
`jina-embeddings-v5-omni-small` (1024 dimensions) and `-nano` (768) embed
text, images,
@@ -423,11 +425,13 @@ comparable across the picture, sound and text fields.
`truncate` and `late_chunk
to text items. `embedding_type` is refused: the engine reads float vectors by
index.
|===
-Jina's documented limits: images 5 MB, PDFs 8 MB and one per request; audio as
WAV, MP3,
-FLAC, OGG, M4A or Opus with no documented duration cap; video as 32 uniformly
sampled frames
+Jina's limits: images 5 MB, PDFs 8 MB and one per request; audio as WAV, MP3,
FLAC, OGG,
+M4A or Opus, capped at 30 s per item, inclusive (a 30.0 s clip is refused with
HTTP 400 and
+the binding's warning names it), which is why the default window is 25 s and a
`media` block
+for this engine should keep `seconds` below 30; video as 32 uniformly sampled
frames
whatever its length, and nothing says the video item's audio track is used,
which is why
Tika sends the sound as its own item. The 32 frames are why the grid matters:
a whole film
-as one input is 32 frames for the film, a 30 s segment is about one frame a
second. Byte
+as one input is 32 frames for the film, a 25 s segment is more than one frame
a second. Byte
caps for audio and video items are not documented; measure before raising
`maxBytes`.
A query is the same call with the query task, and one vector searches every
field:
diff --git
a/tika-app/src/test/java/org/apache/tika/cli/TikaCLIMediaBindingsTest.java
b/tika-app/src/test/java/org/apache/tika/cli/TikaCLIMediaBindingsTest.java
index 977b58bb2b..40ab186231 100644
--- a/tika-app/src/test/java/org/apache/tika/cli/TikaCLIMediaBindingsTest.java
+++ b/tika-app/src/test/java/org/apache/tika/cli/TikaCLIMediaBindingsTest.java
@@ -66,12 +66,12 @@ public class TikaCLIMediaBindingsTest {
assumeTrue(answers("ffmpeg") && answers("ffprobe"), "ffmpeg not on the
PATH");
Path mp4 = tmp.resolve("clip.mp4");
assertEquals(0, ProcessUtils.execute(new ProcessBuilder("ffmpeg",
"-loglevel", "error",
- "-y", "-f", "lavfi", "-i",
"testsrc=duration=70:size=160x120:rate=5",
- "-f", "lavfi", "-i", "sine=frequency=440:duration=70",
+ "-y", "-f", "lavfi", "-i",
"testsrc=duration=65:size=160x120:rate=5",
+ "-f", "lavfi", "-i", "sine=frequency=440:duration=65",
"-c:v", "libx264", "-preset", "ultrafast", "-c:a", "aac",
"-shortest",
mp4.toString()), 60_000, 10_000, 10_000).getExitValue());
try (TikaTestHttpServer server = new TikaTestHttpServer()) {
- // 70 s at 30/5 is three cells; one request per binding at the
default batch size
+ // 65 s at 25/5 is three cells; one request per binding at the
default batch size
server.enqueue(new TikaTestHttpServer.MockResponse(200,
response(3)));
server.enqueue(new TikaTestHttpServer.MockResponse(200,
response(3)));
Path config = tmp.resolve("tika-config.json");
@@ -103,9 +103,9 @@ public class TikaCLIMediaBindingsTest {
assertEquals(6, chunks.size(), "three cells, two channels");
Chunk video2 = chunks.get(1);
Chunk audio2 = chunks.get(4);
- assertEquals(25000,
video2.getLocators().getTemporal().get(0).getStartMs());
- assertEquals(55000,
video2.getLocators().getTemporal().get(0).getEndMs());
- assertEquals("t:25000-55000", video2.getCorrelator());
+ assertEquals(20000,
video2.getLocators().getTemporal().get(0).getStartMs());
+ assertEquals(45000,
video2.getLocators().getTemporal().get(0).getEndMs());
+ assertEquals("t:20000-45000", video2.getCorrelator());
assertEquals(video2.getCorrelator(), audio2.getCorrelator());
assertEquals(Modality.VISUAL, video2.getModality());
assertEquals(Modality.AUDIO, audio2.getModality());
diff --git
a/tika-core/src/main/java/org/apache/tika/parser/inference/MediaConfig.java
b/tika-core/src/main/java/org/apache/tika/parser/inference/MediaConfig.java
index a359ed495a..018c61b964 100644
--- a/tika-core/src/main/java/org/apache/tika/parser/inference/MediaConfig.java
+++ b/tika-core/src/main/java/org/apache/tika/parser/inference/MediaConfig.java
@@ -25,7 +25,8 @@ import org.apache.tika.exception.TikaConfigException;
/**
* The {@code "media"} parse-context block: how audio and video are cut into
segments before
* a MEDIA binding sees them. One grid per document, shared by every binding,
so the vectors of
- * one segment land on one chunk. Defaults: 30 s windows, 5 s overlap, 480
segments.
+ * one segment land on one chunk. Defaults: 25 s windows, 5 s overlap, 480
segments; 25 s
+ * keeps an audio item under the 30 s cap hosted omni engines enforce (Jina
rejects 30.0 s).
*/
@TikaComponent(name = "media", spi = false)
public class MediaConfig implements Serializable, Initializable {
@@ -34,7 +35,7 @@ public class MediaConfig implements Serializable,
Initializable {
public static class Segment implements Serializable {
private static final long serialVersionUID = 1L;
- private int seconds = 30;
+ private int seconds = 25;
private int overlap = 5;
public int getSeconds() {
diff --git
a/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/EmbedTaskMediaContractTest.java
b/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/EmbedTaskMediaContractTest.java
index 2b9ecf8723..2ba1d62d3f 100644
---
a/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/EmbedTaskMediaContractTest.java
+++
b/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/EmbedTaskMediaContractTest.java
@@ -51,7 +51,7 @@ import org.apache.tika.parser.inference.Modality;
*/
public class EmbedTaskMediaContractTest {
- /** Probes every file as 70 s of audio and video; cuts are one-byte files.
*/
+ /** Probes every file as 65 s of audio and video; cuts are one-byte files.
*/
static final class FakeSegmenter implements MediaSegmenter {
final boolean available;
final List<Path> cutDirs = new ArrayList<>();
@@ -59,7 +59,7 @@ public class EmbedTaskMediaContractTest {
Path failProbe;
int timeoutOnCut = -1;
int ioOnCut = -1;
- long durationMs = 70_000;
+ long durationMs = 65_000;
FakeSegmenter(boolean available) {
this.available = available;
@@ -158,7 +158,7 @@ public class EmbedTaskMediaContractTest {
}
}
- /** 70 s at 30/5 is three cells: [0,30] [25,55] [50,70]; batch 2 means
requests of 2 and 1. */
+ /** 65 s at 25/5 is three cells: [0,25] [20,45] [40,65]; batch 2 means
requests of 2 and 1. */
@Test
public void testHappyPathReleasesTheDir() throws Exception {
server.enqueue(new TikaTestHttpServer.MockResponse(200, response(2)));
@@ -184,7 +184,7 @@ public class EmbedTaskMediaContractTest {
List<Chunk> chunks = chunks(unit);
assertEquals(2, chunks.size(), "the good cells keep their vectors");
assertEquals(0,
chunks.get(0).getLocators().getTemporal().get(0).getStartMs());
- assertEquals(50_000,
chunks.get(1).getLocators().getTemporal().get(0).getStartMs());
+ assertEquals(40_000,
chunks.get(1).getLocators().getTemporal().get(0).getStartMs());
assertEquals(3, fake.cuts, "no cell is cut twice");
assertEquals(4, server.getRequestCount());
assertReleased();
diff --git
a/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/EmbedTaskMediaTest.java
b/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/EmbedTaskMediaTest.java
index f51285419f..6ce8385a4c 100644
---
a/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/EmbedTaskMediaTest.java
+++
b/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/EmbedTaskMediaTest.java
@@ -120,10 +120,9 @@ public class EmbedTaskMediaTest {
@Test
public void testAudioAndVideoJoinOnTheCell() throws Exception {
InferenceUnit unit = unit(clip(70, true), "video/mp4");
- // 70 s at 30/5: [0,30] [25,55] [50,70]; batch size 2 -> requests of 2
and 1 per channel
- for (int i = 0; i < 2; i++) {
+ // 70 s at 25/5: [0,25] [20,45] [40,65] [60,70]; batch size 2 -> two
requests of 2 per channel
+ for (int i = 0; i < 4; i++) {
server.enqueue(new TikaTestHttpServer.MockResponse(200,
response(2)));
- server.enqueue(new TikaTestHttpServer.MockResponse(200,
response(1)));
}
EmbedTask task = new EmbedTask();
InferenceBinding video = binding("jina-video", Modality.VISUAL);
@@ -144,19 +143,19 @@ public class EmbedTaskMediaTest {
List<Chunk> chunks = ChunkSerializer.fromJson(
unit.getTarget().get(TikaCoreProperties.TIKA_CHUNKS));
- assertEquals(6, chunks.size(), "one chunk per cell per channel");
+ assertEquals(8, chunks.size(), "one chunk per cell per channel");
Chunk video2 = chunks.get(1);
- Chunk audio2 = chunks.get(4);
- assertEquals(25000,
video2.getLocators().getTemporal().get(0).getStartMs());
- assertEquals(55000,
video2.getLocators().getTemporal().get(0).getEndMs());
- assertEquals(70000,
chunks.get(2).getLocators().getTemporal().get(0).getEndMs());
- assertEquals("t:25000-55000", video2.getCorrelator());
+ Chunk audio2 = chunks.get(5);
+ assertEquals(20000,
video2.getLocators().getTemporal().get(0).getStartMs());
+ assertEquals(45000,
video2.getLocators().getTemporal().get(0).getEndMs());
+ assertEquals(70000,
chunks.get(3).getLocators().getTemporal().get(0).getEndMs());
+ assertEquals("t:20000-45000", video2.getCorrelator());
assertEquals(video2.getCorrelator(), audio2.getCorrelator(), "the
cell's id groups them");
assertEquals("jina-video", video2.getProducer());
assertEquals(Modality.VISUAL, video2.getModality());
assertEquals(Modality.AUDIO, audio2.getModality());
assertEquals(1.0f, video2.getVector()[0], "placed by index");
- assertEquals(0.0f, chunks.get(5).getVector()[0], "second request,
index 0");
+ assertEquals(0.0f, chunks.get(6).getVector()[0], "second audio
request, index 0");
}
/** The request's media block sets the grid; maxSegments cuts the tail. */
diff --git
a/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/FfmpegSegmenterTest.java
b/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/FfmpegSegmenterTest.java
index a3f5a552a3..4201b20ccd 100644
---
a/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/FfmpegSegmenterTest.java
+++
b/tika-parsers/tika-parsers-ml/tika-inference/src/test/java/org/apache/tika/inference/FfmpegSegmenterTest.java
@@ -43,11 +43,14 @@ public class FfmpegSegmenterTest {
@Test
public void testCells() throws Exception {
MediaConfig config = new MediaConfig();
- List<MediaSegmenter.Cell> cells = MediaSegmenter.cells(52_000, config,
Integer.MAX_VALUE);
+ // defaults 25/5: [0,25] [20,44]; the next start (40) is inside the
last cell
+ List<MediaSegmenter.Cell> cells = MediaSegmenter.cells(44_000, config,
Integer.MAX_VALUE);
assertEquals(2, cells.size(), "a tail inside the previous cell's
overlap is not a cell");
- assertEquals(25_000, cells.get(1).startMs());
- assertEquals(52_000, cells.get(1).endMs());
- assertEquals(1, MediaSegmenter.cells(30_000, config,
Integer.MAX_VALUE).size());
+ assertEquals(20_000, cells.get(1).startMs());
+ assertEquals(44_000, cells.get(1).endMs());
+ assertEquals(1, MediaSegmenter.cells(25_000, config,
Integer.MAX_VALUE).size());
+ assertEquals(2, MediaSegmenter.cells(30_000, config,
Integer.MAX_VALUE).size(),
+ "a 30 s file is two cells: no cell may reach a 30 s audio
cap");
assertEquals(1, MediaSegmenter.cells(1_000, config,
Integer.MAX_VALUE).size());
assertEquals(0, MediaSegmenter.cells(0, config,
Integer.MAX_VALUE).size());
// a container may claim any duration: the cap bounds the work, not
the claim
@@ -55,8 +58,9 @@ public class FfmpegSegmenterTest {
config.getSegment().setOverlap(-5);
assertThrows(TikaException.class, () -> MediaSegmenter.cells(52_000,
config, 10),
"a negative overlap would leave gaps");
- config.getSegment().setOverlap(30);
- assertThrows(TikaException.class, () -> MediaSegmenter.cells(52_000,
config, 10));
+ config.getSegment().setOverlap(25);
+ assertThrows(TikaException.class, () -> MediaSegmenter.cells(52_000,
config, 10),
+ "overlap equal to the window is a zero stride");
}
@Test
diff --git
a/tika-serialization/src/test/java/org/apache/tika/config/loader/InferenceLoaderTest.java
b/tika-serialization/src/test/java/org/apache/tika/config/loader/InferenceLoaderTest.java
index 8d1448db22..233f596a4b 100644
---
a/tika-serialization/src/test/java/org/apache/tika/config/loader/InferenceLoaderTest.java
+++
b/tika-serialization/src/test/java/org/apache/tika/config/loader/InferenceLoaderTest.java
@@ -288,7 +288,7 @@ public class InferenceLoaderTest {
assertEquals(10, media.getSegment().getSeconds());
assertEquals(2, media.getSegment().getOverlap());
assertEquals(7, media.getMaxSegments());
- assertEquals(30, new MediaConfig().getSegment().getSeconds(), "default
window");
+ assertEquals(25, new MediaConfig().getSegment().getSeconds(), "default
window");
assertEquals(5, new MediaConfig().getSegment().getOverlap(), "default
overlap");
assertThrows(TikaConfigException.class, () -> load("{" + ENGINES + ",
\"inference\": ["
+ " { \"id\": \"a\", \"engine\": \"one\", \"input\":
\"MEDIA\","