Skip to main content

The segments Extension

Full-text search tells a user which file matched their query. For a three-hundred-page PDF of field notes or an hour of transcribed audio, that is not enough — the user still has to hunt for the match inside the file. The segments extension closes that gap: each search hit can carry a list of segments, structured locations inside the file where the match occurred, so clients can deep-link the user straight to the matching page of a PDF or the matching utterance in a media player.

Segments appear as an optional searchExtra.segments array on search hits. The array is ranked by relevance and capped at an implementation-defined limit.

Declaring the Capability​

An implementation that provides segments declares the extension in /capabilities:

{
"apiVersion": "0.3.0",
"extensions": {
"segments": {}
}
}

The extension currently has no extra details to communicate, so the value is an empty object — presence of the segments key is the declaration.

Clients detect support with a key lookup ("segments" in capabilities.extensions) and MUST degrade gracefully when the key is absent — hide the deep-link affordance, don't fail.

The Response Shape​

Each segment is one of the registered segment types, discriminated by type. Two types are registered:

page​

A match located on a page of a paginated document, such as a PDF.

{
"type": "page",
"page": 3,
"highlight": ["a wordlist of <em>West Alor</em> terms for kinship"]
}
  • page (integer, ≥ 1): the 1-based page number the match occurred on
  • highlight (array of strings): matched text fragments from this page, using the same marking convention as searchExtra.highlight

time-aligned-annotation​

A match located in a time-aligned annotation, such as an ELAN annotation tier.

{
"type": "time-aligned-annotation",
"tier": "A_phrase-segnum-en",
"startMs": 83000,
"endMs": 87500,
"highlight": ["the speaker lists <em>West Alor</em> place names"]
}
  • tier (string): the identifier of the annotation tier the match occurred in (for ELAN, the TIER_ID)
  • startMs / endMs (integers, ≥ 0): the annotation's time range, in milliseconds from the beginning of the media
  • highlight (array of strings): matched text fragments from this annotation

A Full Search Hit​

A search for West Alor might return this hit for a PDF of field notes with an accompanying transcription:

{
"id": "https://catalog.paradisec.org.au/repository/NT1/001/NT1-001-001A.pdf",
"name": "NT1-001-001A.pdf",
"entityType": "http://schema.org/MediaObject",
"searchExtra": {
"score": 0.87,
"highlight": { "content": ["notes on <em>West Alor</em> vocabulary"] },
"segments": [
{
"type": "page",
"page": 3,
"highlight": ["a wordlist of <em>West Alor</em> terms for kinship"]
},
{
"type": "time-aligned-annotation",
"tier": "A_phrase-segnum-en",
"startMs": 83000,
"endMs": 87500,
"highlight": ["the speaker lists <em>West Alor</em> place names"]
}
]
}
}

A client can render "matched on page 3" as a link opening the PDF viewer at that page, and "matched at 1:23" as a link starting media playback at 83 seconds.

Client Rules​

  • Segments are optional per hit. Absent or empty means the hit had no structured content to point into — the hit itself is still valid.
  • Skip unknown types. New segment types are added by revision of this specification. A client that encounters a type it does not recognise MUST skip that segment rather than fail, so deployed clients keep working as the union grows.
  • Expect a cap. Segments are ranked by relevance and capped at an implementation-defined limit per hit, so a full-looking list is not necessarily exhaustive.

New segment types are proposed by pull request against the specification repository — see the registry.

Implementation Notes (Non-Normative)​

This section is guidance, not specification. It sketches one proven way to implement segments with Elasticsearch; any implementation that produces conformant responses is equally valid.

Index-time extraction​

Segments must exist in the index before they can be searched. At ingest time, extract per-segment records from each file:

  • PDFs and other paginated documents: extract text page by page (e.g. with pdftotext or Apache Tika), producing one record per page with its 1-based page number.
  • ELAN files (.eaf): walk each annotation tier, producing one record per annotation with the tier's TIER_ID and the annotation's time slot values in milliseconds.

Mapping​

Store the extracted records as nested documents on the file's entity document, so each segment's fields stay associated with each other:

{
"mappings": {
"properties": {
"segments": {
"type": "nested",
"properties": {
"type": { "type": "keyword" },
"text": { "type": "text" },
"page": { "type": "integer" },
"tier": { "type": "keyword" },
"startMs": { "type": "long" },
"endMs": { "type": "long" }
}
}
}
}
}

Querying​

Combine the entity-level query with a nested query over the segments, and use inner_hits to retrieve the matching segments — inner_hits returns the top-scoring nested documents per hit, which is exactly the ranked, capped list the extension requires:

{
"query": {
"bool": {
"should": [
{ "match": { "content": "West Alor" } },
{
"nested": {
"path": "segments",
"query": { "match": { "segments.text": "West Alor" } },
"inner_hits": {
"size": 5,
"highlight": { "fields": { "segments.text": {} } }
}
}
}
]
}
}
}

Set inner_hits.size to your chosen per-hit segment cap. Each inner hit maps directly onto a response segment: the type field selects page or time-aligned-annotation, the per-type fields come from the nested source, and the highlight fragments become the segment's highlight array.