The library bus

No pod this operator creates holds an API credential. Every fact a scanner records about a library, every choice a person makes on a screen, and every position the progress store records reaches the operator over the bus. The bus is the broker media-operator runs, and this page is the contract of this operator’s tree on it: every topic, who writes it, who reads it, and the shape of each payload. Your program can connect the same way, with a plain MQTT client and no Kubernetes credentials. A media browser of another make that publishes a play request gets the same service the project’s browser gets.

The topic base

Every topic extends one base, liken/library by default. The operator reads the base from LIBRARY_TOPIC_BASE and passes it to every pod it creates, so the whole tree moves together when a cluster chooses another base. media-operator’s tree extends liken/media, so the two operators’ trees stay disjoint on the same broker. This page writes topics without the base.

The rules that shape the tree are media-operator’s, and The media bus states them: state is retained and events are not, the topic names the object and the payload does not, and a writer that leaves retained state behind names an availability topic as its MQTT Last Will. This tree follows the same three rules, with one exception: who is watching names no Last Will, because that answer is meant to outlive the process that wrote it.

The topics

Topic Writer Readers Retained Payload
libraries/{namespace}/{name}/status the catalog pod’s reporter the operator yes the library report
catalogs/{namespace}/availability the catalog pod’s reporter the operator yes online or offline
libraries/{namespace}/{name}/refresh the operator the catalog pod’s reporter yes the refresh times
libraries/{namespace}/{name}/missing/{fact}/{job}/{index} a library Job’s close container one pod of the fact’s worker Job yes a work list
libraries/{namespace}/{name}/missing/{fact}/{job}/count a library Job’s close container the operator yes a work list
players/{namespace}/{player}/play the media browser, or any client the operator no the play request
players/{namespace}/{player}/audience the media browser the media browser, and any client that wants to know who is in the room yes who is watching
plays/{namespace}/{play}/audience the operator the progress role, the jellyfin role yes the audience
plays/{namespace}/{play}/final the operator the progress role, the jellyfin role yes the final status
plays/{namespace}/{play}/recorded the progress role the operator yes the recorded mark
plays/{namespace}/{name}/outside the jellyfin role the progress role no an outside play
plays/{namespace}/{name}/mark the media browser the progress role, the jellyfin role yes a mark
plays/{namespace}/{name}/sent the jellyfin role the jellyfin role yes a sent mark
people/{person}/forget the operator every namespace’s progress role yes the forget request
people/{person}/forgotten/{namespace} the progress role the operator yes the forgotten answer
progress/{namespace}/availability the progress role the jellyfin role yes online or offline

The writers are the roles of this operator’s pods. The reporter is the container beside the standing catalog agent in the namespace’s catalog pod. The progress role is the container beside the standing progress agent. The jellyfin role is the pod the operator runs beside the progress store when the namespace’s Catalog names a server in spec.jellyfin . The operator itself holds the one API credential.

Every retained topic is cleared with an empty payload when the object it names is gone, which is how MQTT drops a retained message, so a client that connects later reads nothing for a Library, a Play, or a Person that no longer exists. The operator clears the library, play, and people topics, and each work list once its worker has finished. The progress role clears its own forgotten answer when it reads an empty forget. The media browser clears its own audience topic when the answer on it lapses. The progress role clears each mark once it has recorded it and the retention has run, and the jellyfin role clears its record of a sent mark with the mark.

The library report

libraries/{namespace}/{name}/status

The reporter publishes one report per Library, retained, and rebuilds it whenever the catalog’s runs table changes and while any replicated table keeps changing. The operator folds each report into that Library’s status, so the fields are the ones the Library status carries. The close container of a Library’s Job writes the enrich run as the Job’s last catalog write and exits only when a catalog pod confirms it, so a run in this report with a finish is also the proof that a catalog pod holds every row the Job wrote.

Field Type Meaning
titles integer How many titles the catalog holds for this library.
unidentified integer How many folders the last walk could not identify.
lastWalk RFC 3339 time When the scan run last finished.
lastChange RFC 3339 time When the counts last moved. A report that counts the same rows as the one before carries the same time. A reporter that has just started takes lastWalk.
items integer The item rows the catalog holds for this library after the last walk pruned.
files integer The file rows the catalog holds for this library after the last walk pruned.
walking boolean True while a walk runs, which is a scan run whose start is later than its finish.
removedLastSweep integer How many rows the last full sweep removed.
runs list One entry per worker that has run against this library, sorted by worker. Absent until a worker has run.
gaps map of integers One count per fact of the rows that fact has left to fill, counted with the Library’s refresh times . Absent when no fact has a gap.
oldestAttempts map of RFC 3339 times The oldest attempt this library holds for each fact. The operator reads it against spec.refresh. Absent when no fact has an attempt.
waiting integer How many titles the identity fact left as candidates for a person to choose from.
unresolved integer How many titles no provider could name.
fights integer How many titles a fact left because another writer holds the elements it writes.

Each entry of runs carries worker, job, started, and finished, plus unidentified, removed, and failure where the run has them. failure is one sentence on why the run failed, and it is empty for a run that finished its work.

{
  "titles": 128,
  "unidentified": 9,
  "lastWalk": "2026-08-29T21:04:11Z",
  "lastChange": "2026-08-29T21:04:11Z",
  "items": 128,
  "files": 560,
  "walking": false,
  "removedLastSweep": 3,
  "runs": [
    {"worker": "enrich", "job": "movies-walk-dlcg1z2yyzgw",
     "started": "2026-08-29T21:03:01Z", "finished": "2026-08-29T21:09:42Z"},
    {"worker": "scan", "job": "movies-walk-dlcg1z2yyzgw",
     "started": "2026-08-29T21:03:02Z", "finished": "2026-08-29T21:04:11Z",
     "unidentified": 9, "removed": 3}
  ],
  "gaps": {"art": 4, "identity": 2},
  "oldestAttempts": {"identity": "2026-08-01T03:15:00Z"},
  "waiting": 1,
  "unresolved": 1,
  "fights": 0
}

The refresh times

libraries/{namespace}/{name}/refresh

The reporter counts each gap with the Library’s spec.refresh , so a title a refresh reopened is in gaps. The reporter holds no API credential and cannot read the Library, so the operator publishes the refresh time of each fact the Library names, retained, whenever the times change. The reporter subscribes to the refresh topics of its own namespace and publishes the report again when one changes, so an edit of spec.refresh reaches gaps with no restart of the catalog pod. The payload is the value a Job’s containers read from LIBRARY_REFRESH: one RFC 3339 time per fact. The walk is not a fact, and it is not in the payload. A Library that names no refresh time publishes an empty object.

{"credits": "2026-09-30T00:00:00Z", "appearances": "2026-09-30T00:00:00Z"}

A report that the reporter built before the refresh times reached it counts with none. The operator reads oldestAttempts against spec.refresh as well, so a fact with a refresh that has work left starts its work from such a report too.

A work list

libraries/{namespace}/{name}/missing/{fact}/{job}/{index} and libraries/{namespace}/{name}/missing/{fact}/{job}/count

The videos a heavy fact still needs, for the fact’s worker Job. The close container of the library Job named {job} reads the gap of each heavy fact the Library turns on, trickplay or appearances, from its copy of the catalog. It publishes each video as one retained message, numbered from 0 in the order of the video’s path, and then the count, retained:

{"path": "Harbour Lights/Season 01/Harbour Lights - S01E02.mkv", "size": 1468006400, "durationMs": 2710000}

The path is relative to the Library’s root. The size is the one the walk read, and the worker compares it with the file before it opens it. The length tells the trickplay worker how many thumbnails cover the video. The count is a decimal number, 3. The broker reads one connection’s messages in order, so a count on the broker proves that every video before it is there too. The close container reads its own count back before it writes its enrich run, and an empty gap publishes nothing. One list holds at most 10,000 videos.

The operator subscribes to every count. When the Job named {job} has finished, it starts the fact’s worker as an Indexed Job with one completion per video, and each pod reads the message at its own index. The operator clears every topic of a list when that worker has finished, when a newer list of the same fact replaces a list no worker took, and when the Library is gone. The broker keeps retained messages in memory, so a broker restart drops every list, and the next library Job lists the videos again.

The reporter’s availability

catalogs/{namespace}/availability

online while the catalog pod’s reporter runs. The reporter names this topic as its MQTT Last Will with offline as the payload, publishes online once it connects, and publishes offline as it stops. Both are retained. A pod the kubelet killed reads offline, and every Library of the namespace reads the phase Offline with it. The reports the reporter left behind stand: the counts describe the catalog, and they hold until the next report replaces them.

One reporter serves every Library of its namespace, so there is one availability topic per namespace and none per Library.

The play request

players/{namespace}/{player}/play

A play request is what a person chose on a screen. The browser holds the catalog and no API credential, and the operator holds the credential and no catalog, so the browser resolves the list of files and publishes it here, and the operator joins each path to the Library’s claim and creates the Play . The request is an event and is not retained: a broker that held the last one would replay it to the operator on every reconnect.

The operator names the topic on the browser container as LIBRARY_PLAY_TOPIC. The Player comes from the topic and never from the payload, so a request cannot name a Player other than the one whose topic carried it. The operator creates a Play only for a Player whose idle screen it holds, and only from a Library in that Player’s namespace. A request that fails a check is reported in the operator’s log and dropped, because the screen has no way to answer.

Field Type Required Meaning
library string yes The Library the items come from, as namespace/name. The namespace must be the Player’s own.
slug string no The catalog’s slug for the item the person chose: the movie, or the chosen episode. The operator folds it into the Play’s name, so kubectl get plays reads as titles. A request with no slug names the Play after the Player alone.
items list yes The files to play, in order. At least one.
items[].path string yes The path of the item’s main file, relative to the library root, exactly as the catalog stores it. An empty path, an absolute path, or one that climbs above the root is refused.
items[].presentation object no How the item should look, in the Play’s own spec.items[].presentation field names: type, hint, title, series, season, episode, episodeTitle, year, date, art, trickplay, role, appearances, contributors, and marks. role says what the item is to the work it presents: trailer, or empty for the work itself. appearances is the path of the spans file that the appearances fact wrote for the item’s file, and contributors is the path of the library’s .contributors directory, which the spans file names each portrait under. marks is a list of spans of the item’s file, each with a kind, a start and an end in seconds from the start of the file, and a source, the provider block the span came from. An absent start is the start of the file, and an absent end is the end of the file. The operator carries marks unread, and the display merges the candidates. art, trickplay, appearances, and contributors are paths relative to the library root, and the operator joins them to the claim the way it joins path.
people list of strings no Who is watching, as Person names. Each becomes an owner reference on the Play. A name the cluster does not hold is reported and dropped, and the Play still plays.
aliases map of strings no The work’s ids by provider, such as {"tmdb": "1000001", "imdb": "tt0000001"}. They become the Play’s library.liken.sh/alias.{provider} annotations, and they are the identity the progress store keys on.
season integer no The season number of an episode. It becomes the library.liken.sh/season annotation.
episode integer no The episode number of an episode. It becomes the library.liken.sh/episode annotation.
start string no Where the first item begins, as a decimal count of seconds. It reaches the Play’s spec.start unchanged, because the player parses it and the operator does not. Omit it to start at the beginning.
next object no The work that follows this one, which the player offers on its scrubber. It carries the three lines of the offer card, reason, title, and detail, spelled the way the continue-watching row spells them; art, a path relative to the root of next.library; library, the namespace/name of the next work’s own Library, which may differ from library above; and request, an object the operator copies onto the Play unread. The operator joins art to the claim of next.library the way it joins an item’s art, and the player publishes request back on the Player’s commands topic when a person takes the offer.

The project’s browser publishes one item, the film or the chosen episode, leaves out every empty field, sends start only for a resume, and sends next only where the page the person started from names a natural successor: the next episode of a series, the next film of a set, or the next member of a franchise. A series continues through next and not through the item list, so every episode is its own Play and its own row of progress.

{
  "library": "den/movies",
  "slug": "a-quiet-harbor-2014",
  "items": [
    {
      "path": "A Quiet Harbor (2014)/A Quiet Harbor (2014).mkv",
      "presentation": {
        "type": "video",
        "hint": "movie",
        "title": "A Quiet Harbor",
        "year": 2014,
        "art": "A Quiet Harbor (2014)/poster.jpg",
        "trickplay": "A Quiet Harbor (2014)/A Quiet Harbor (2014).trickplay"
      }
    }
  ],
  "people": ["ada", "grace"],
  "aliases": {"tmdb": "1000001", "imdb": "tt0000001"},
  "start": "600"
}

The Play the operator creates from that request carries one item whose URI is claim://, the Library’s namespace, /, the Library’s claim, /, and the library root joined to the path. The root is an absolute path, so the URI has two slashes after the claim. For a Library in the namespace den on the claim movies with the root /, that is claim://den/movies//A Quiet Harbor (2014)/A Quiet Harbor (2014).mkv. The two people become owner references, the two aliases become annotations, and 600 becomes spec.start.

Who is watching

players/{namespace}/{player}/audience

The room on one screen, as the browser asked a person for it. The browser is both the writer and the reader: it publishes the answer retained, and it reads the broker’s catch-up back when it starts. A screen pod that restarts inside the idle window draws the room it had and asks nobody. The operator names the topic on the browser container as LIBRARY_AUDIENCE_TOPIC, and reads none of it.

Field Type Meaning
people list of objects Who is watching, in the order the answer named them. Each entry carries name, the Person every record keys on, and displayName, the name the screen draws. An empty list is the answer “nobody”, which is an answer and not the absence of one.
at integer The Unix time of the last press, in whole seconds.
{"people": [{"name": "person-a", "displayName": "Person A"}], "at": 1757350000}

The browser writes the message when a person answers the picker, and at most once a minute while a person presses keys. The stamp is what ages, and a second either way changes nothing a reader can act on. The browser republishes what it holds whenever its bus session starts, under the stamp the message already carries, because a reconnect is not a press. When the answer lapses, three hours after the last press, the browser publishes the empty payload, which drops the retained message.

The browser acts on a retained delivery alone, which is the broker’s catch-up on the subscription. A live delivery is the echo of its own write and changes nothing. It takes the answer where at is inside the idle window, and it asks again where the stamp is older. A name the cluster’s Person list does not hold is dropped.

This topic names no availability topic as its Last Will, and that is deliberate. The answer describes the room, not the browser, so it must outlive the browser’s process. A pod the kubelet restarts reads the room back.

The audience

plays/{namespace}/{play}/audience

What the operator holds about a Play that the progress role cannot read for itself. The operator publishes it retained as soon as the Play exists, and again whenever it changes, so a progress role that starts late reads it back from the broker. A Play written by hand with the same annotations and owner references gets the same audience.

Field Type Meaning
player string The Player the Play runs on.
library string The Library the items came from, by name, or absent for a Play this operator did not create.
people list of strings The Person names on the Play’s owner references, or absent for a Play nobody claimed.
aliases map of strings The work’s ids by provider, from the Play’s alias annotations.
season, episode integers The numbers of an episode, absent for a work that has none.
credits list of objects Every credits mark of the Play’s first item, from its presentation: start and end in seconds, each absent where the mark runs from the start or to the end of the file. Absent where the item has none. The jellyfin role reads it for the watched rule.
{
  "player": "den",
  "library": "movies",
  "people": ["ada", "grace"],
  "aliases": {"tmdb": "1000001", "imdb": "tt0000001"},
  "credits": [{"start": 6204.5, "end": 6600}, {"start": 6210}]
}

The final status

plays/{namespace}/{play}/final

The Play’s last status, read off the API by the operator once the Play’s phase is Finished or Failed, or once the Play is deleting. Retained. It closes the gap the bus leaves: a progress role that was down for the last report of a film still records where the film ended. The positions are H:MM:SS, as the Play status carries them.

{"phase": "Finished", "item": 1, "position": "1:52:10", "duration": "1:52:10"}

The recorded mark

plays/{namespace}/{play}/recorded

What the progress role wrote last for one Play, retained. The operator holds a finalizer on every Play and releases it only when ended is true, so a Play is never deleted before its last position is in the store. at is the time of the write, RFC 3339 in UTC.

The mark never goes back to ended: false. Once the row is ended, the progress role writes nothing more for that Play and publishes no new mark, so a position report that reaches the bus after the final leaves the row and the mark where the final put them.

{"item": 1, "position": "1:52:10", "ended": true, "at": "2026-08-29T23:01:14Z"}

An outside play

plays/{namespace}/{name}/outside

One play that ran outside the cluster, on a Jellyfin server. The jellyfin role publishes it from the webhook Jellyfin posts to, from the reconcile it runs each time the progress role comes online, and from a backfill of the server’s history. The progress role records it beside the cluster’s own Plays. It is the one message in the tree that is not retained: a progress role that was down for one post catches the next, and a stop repeats the final position.

The {name} segment is not a Play. It is jellyfin-{userId}-{itemId}, so one person’s progress in one item is one row that moves, and a rewatch moves it again.

The two ids are Jellyfin Guids, in the spelling its API writes: 32 hexadecimal digits with no dashes. The webhook, the reconcile, and the backfill all write that spelling, so the three name one row.

Field Type Meaning
player string jellyfin, in the column that names a Player.
people list of strings The people who watched, as Person names.
aliases map of strings The work’s ids by provider. An episode carries the series’ ids.
season, episode integers The numbers of an episode, and 0 for a work that has neither.
position, duration integers The position and the length, in seconds.
ended boolean True on a stop, which marks the row ended.
at integer The Unix time of the event, which the store writes as the recorded time. A newer at wins over what the row holds.

A work Jellyfin reports as played carries a position at the end of the work, whatever resume point Jellyfin holds. A mark a person set by hand has no position of its own.

{
  "player": "jellyfin",
  "people": ["ada"],
  "aliases": {"tmdb": "1000001", "imdb": "tt0000001"},
  "season": 0,
  "episode": 0,
  "position": 1450,
  "duration": 6730,
  "ended": false,
  "at": 1756508474
}

A mark

plays/{namespace}/{name}/mark

One mark a person set on one title at the media browser: watched, or cleared. It applies to everyone at the screen. The progress role records it as one row of the store, beside the cluster’s own Plays. The jellyfin role reads the same message to send the mark to Jellyfin. A mark can also name a list of a series’ episodes, which Pick up here publishes: see a list of episodes .

The mark is retained, because the person pressed once and nothing repeats the press. A progress role that is down when the person presses reads the mark when it subscribes again. The progress role clears the topic 24 hours after at, once it has recorded the mark. A mark it reads after those 24 hours, it records and clears at once. A payload that is not a mark, it clears at once and records nothing. Every reader receives a retained mark again on each subscription within the 24 hours, so a reader must treat a second delivery as the same mark.

The {name} segment is not a Play. It is mark-{player}-{at}, so each press is one topic and one row. The browser moves a second press within the same second to the next second, so no two marks of one screen share a name. The progress role writes a row only when the store holds no row of that name recorded at at or later, so a mark delivered again never changes a row.

The browser takes the branch of the tree from LIBRARY_PLAYS_TOPIC, {base}/plays/{namespace}, which the operator sets on the browser container, and the Player’s name from MEDIA_PLAYER_NAME.

Field Type Meaning
mark string watched or cleared.
player string The Player whose screen the person pressed on, in the column that names a Player.
people list of strings The people at the screen, as Person names. Empty where nobody answered who is watching.
aliases map of strings The work’s ids by provider, as a play request names them. An episode carries the series’ ids.
season, episode integers The numbers of an episode, and 0 for a work that has neither.
position integer The duration for watched, and 0 for cleared, in seconds.
duration integer The length of the work in seconds: the duration of the audience’s play of the work where one exists, and the catalog’s running time where none does.
at integer The Unix time of the press, which the store writes as the recorded time. The row stands over every play recorded before it, and a later play replaces it.

The row is ended at at, with the phase Finished, because no Play runs behind it.

A mark that names one work carries no episodes field.

{
  "mark": "watched",
  "player": "living-room",
  "people": ["ada"],
  "aliases": {"tvdb": "1000002"},
  "season": 2,
  "episode": 5,
  "position": 2760,
  "duration": 2760,
  "at": 1759140000
}

A list of episodes

Pick up here marks every earlier episode of a series watched and clears every later episode the people at the screen started, in one press. The browser publishes one mark for the whole press, on the same topic and retained the same way, and the mark lists the episodes in episodes. The series’ aliases, the people, the Player, and at are stated once. The mark carries no season, episode, position, or duration of its own: each entry of the list states its own.

Field Type Meaning
episodes list of objects The episodes the mark covers, in series order. Each entry has mark, season, episode, position, and duration, with the meaning each field has on a mark of one work.

An entry’s mark is watched or cleared, and an entry with no mark takes the message’s. The browser states mark on every entry, and states watched on the message. A message whose mark, or whose entry’s mark, is neither of the two is no mark: the progress role clears it and records nothing.

The progress role writes one row for each entry. A watched row is named {name}-s{season}e{episode} with each number in four digits, as in mark-living-room-1759140000-s0001e0004. A cleared row is named {name}-cleared-s{season}e{episode}, as in mark-living-room-1759140000-cleared-s0003e0002. Every row is recorded at at. The browser breaks a tie in recorded time on the row’s name. The four digits make the watched names sort in series order, and every cleared name sorts before every watched name, so the browser reads the last watched episode of the list as the newest, and never a later episode the press cleared. A cleared row stands at position 0, and a thread that stood on it would offer nothing. The role writes each row only when the store holds no row of that name recorded at at or later, so a list delivered again writes only the rows that are missing. It clears the topic once, as it clears a mark of one work.

The jellyfin role writes each entry to each person at the screen, with the entry’s own mark, and publishes one sent mark for the whole list once every entry has landed. An entry Jellyfin does not hold is done with no write.

The press publishes one message, and not one mark for each episode, because each mark’s name takes the second of its press. The browser moves a second press within the same second to the next second, so a mark for each of 100 episodes would carry an at up to 100 seconds in the future. The play the same press starts would read as older than those marks until that time had passed.

{
  "mark": "watched",
  "player": "living-room",
  "people": ["ada"],
  "aliases": {"tvdb": "1000002"},
  "episodes": [
    {"mark": "watched", "season": 1, "episode": 4, "position": 2760, "duration": 2760},
    {"mark": "watched", "season": 2, "episode": 1, "position": 2700, "duration": 2700},
    {"mark": "cleared", "season": 3, "episode": 2, "position": 0, "duration": 2760}
  ],
  "at": 1759140000
}

A sent mark

plays/{namespace}/{name}/sent

The jellyfin role’s record that it sent one mark to Jellyfin, under the mark’s own name. The role publishes it retained once the mark’s write lands for every person Jellyfin holds, and sends no mark whose record it reads. A jellyfin role that restarts within the mark’s 24 hours reads both back, so it does not send the mark again over a toggle a person made in Jellyfin since. Jellyfin clears an item’s last played date on an unplayed toggle, so the date cannot stop a second send alone.

The role clears the record when the progress role clears the mark. A record it reads more than 24 hours after at, it clears at once, because the mark’s clear may have arrived while the role was down.

Field Type Meaning
at integer The at of the mark, the Unix time of the press.
{"at": 1759140000}

The forget request

people/{person}/forget

The operator’s request that one person’s rows go, to every namespace’s progress role at once. A Person is cluster-scoped, so the topic has no namespace. The operator holds a finalizer on every Person, and when the Person is deleted it publishes this request retained with the time it asked, so a store that starts later reads an ask it has not answered. An empty payload is the clear, and every role clears its own answer when it reads one.

{"at": "2026-08-29T21:34:02Z"}

The forgotten answer

people/{person}/forgotten/{namespace}

One namespace’s answer that the person’s rows are gone from its store, retained, with the time of the sweep. The operator releases the Person once every namespace that stands a store has answered, then clears the request and every answer.

{"at": "2026-08-29T21:34:05Z"}

The progress role’s availability

progress/{namespace}/availability

online while the namespace’s progress role runs. The role names this topic as its MQTT Last Will with offline as the payload, on the same terms as the reporter’s. The operator does not read it: it reads the role’s recorded marks, and a mark that stops moving is the signal it acts on. The topic is also on the bus for a client that folds the store’s marks and needs to know whether their writer is gone.

The jellyfin role reads it. Each online starts one reconcile, a read of every Jellyfin user’s played and resumable items published as outside plays, because an outside play is not retained and a progress role that was down lost every post Jellyfin made meanwhile.

What this operator reads from the media tree

The progress role and the jellyfin role read two topics of media-operator’s tree for every Play in their namespace, under liken/media by default and under LIBRARY_MEDIA_TOPIC_BASE when that operator’s base moves: plays/{namespace}/{play}/status, the playback pod’s report with the position, and plays/{namespace}/{play}/availability, its Last Will. The progress role joins the position with the audience above into the rows it records. This operator writes nothing on the media tree.

The media browser reads the Player’s own topics from the same tree, which media-operator names in the Player’s status.idle.bus, and Put the media browser on a screen describes what the browser does with each.