A local alternate version is filed but never joined to the title it is a cut of #214

Closed
opened 2026-09-12 19:07:19 +02:00 by Claude · 1 comment
Collaborator

#207 made a scan file a row per alternate version. It does not join that row to the one it is a cut of, so the versions exist and are not served as versions: /Videos/{id} hands back one media source where upstream hands back several.

What is missing

Upstream links a version three ways (upstream/MediaBrowser.Controller/Entities/Video.cs, LibraryManager.ResolveAlternateVersion):

  • PrimaryVersionId on the version's row
  • PresentationUniqueKey, shared with the primary
  • a LinkedChildType.LocalAlternateVersion row

We write the first of nothing: crates/ephyra-db/src/scan/row.rs:20-38 has no PrimaryVersionId column in its insert, and crates/ephyra-db/src/items/writes.rs:624-627 says outright that nothing in this server produces a LocalAlternateVersion. The scan sets OwnerId and stops there.

Why nothing looks broken today

Owner-only filing is safe as far as listings go — the default filter already hides OwnerId IS NOT NULL AND ExtraType IS NULL (crates/ephyra-db/src/query/filters.rs:405-413), so no duplicate film appears in a library view. The row is reachable by id and by nothing else, which is why #207 could land without a user-visible regression.

What it costs

A client asking for a film with two cuts gets one MediaSources entry. crates/ephyra-db/src/items/repository.rs:124-147 reads only ChildType = 3, so the join has no reader either.

This is the half of #207 that its own "Reach" section points at, and it overlaps #203 (the selected source's runtime) and #211 (the selected source's streams): all three want a session or a read to resolve which source is in play.

Verifying

crates/ephyra-http/tests/versions.rs covers serving versions that already exist as rows; the fixture now lays down a real multi-version group (musicvideos/Anthem/, added for #207), so an end-to-end scan-then-read is reachable for the first time.

Found reviewing #207.

#207 made a scan file a row per alternate version. It does not join that row to the one it is a cut of, so the versions exist and are not *served* as versions: `/Videos/{id}` hands back one media source where upstream hands back several. ## What is missing Upstream links a version three ways (`upstream/MediaBrowser.Controller/Entities/Video.cs`, `LibraryManager.ResolveAlternateVersion`): - `PrimaryVersionId` on the version's row - `PresentationUniqueKey`, shared with the primary - a `LinkedChildType.LocalAlternateVersion` row We write the first of nothing: `crates/ephyra-db/src/scan/row.rs:20-38` has no `PrimaryVersionId` column in its insert, and `crates/ephyra-db/src/items/writes.rs:624-627` says outright that nothing in this server produces a `LocalAlternateVersion`. The scan sets `OwnerId` and stops there. ## Why nothing looks broken today Owner-only filing is safe as far as listings go — the default filter already hides `OwnerId IS NOT NULL AND ExtraType IS NULL` (`crates/ephyra-db/src/query/filters.rs:405-413`), so no duplicate film appears in a library view. The row is reachable by id and by nothing else, which is why #207 could land without a user-visible regression. ## What it costs A client asking for a film with two cuts gets one `MediaSources` entry. `crates/ephyra-db/src/items/repository.rs:124-147` reads only `ChildType = 3`, so the join has no reader either. This is the half of #207 that its own "Reach" section points at, and it overlaps #203 (the selected source's runtime) and #211 (the selected source's streams): all three want a session or a read to resolve *which* source is in play. ## Verifying `crates/ephyra-http/tests/versions.rs` covers serving versions that already exist as rows; the fixture now lays down a real multi-version group (`musicvideos/Anthem/`, added for #207), so an end-to-end scan-then-read is reachable for the first time. Found reviewing #207.
Author
Collaborator

Fixed in 74adf6e5, 1c4a86cb and 53ac9c6c, landed on main.

The join. A scan files a cut with PrimaryVersionId, the title's presentation key, and a LocalAlternateVersion link rebuilt from the title's blob after each pass. A rewrite changes the primary and the key only for rows the scan owns, so a merge made by hand survives a rescan, and a row filed by an older pass is joined on the next one. A music video now gets a video blob at all, so its parts and cuts are stored.

The read side. /Items/{id} serves MediaSources and MediaSourceCount for the group, and PlaybackInfo serves and negotiates every version, honouring mediaSourceId on POST and ignoring it on GET as upstream does. Source names use upstream's common-prefix rule, whose own test table is ported from BaseItemTests.cs. A group member the account may not see is left out; the asked row stays.

Verified against upstream's own answers for the fixture's two version groups, by regenerating the answer key with four new columns (owner, primary_version, local_alternate_versions, versions), and by a native end-to-end scan: both groups joined, no foreign-key errors, and a second pass stable.

The review caught two things, both fixed: the session and resume readers could not see the new link type, so playing a scanned cut reported the title's runtime and no streams and did not suppress its siblings from /Items/Resume; and asking a page for MediaSources made a query per row, where the count beside it was batched. Both go through one batched group read now.

Known and left: MediaSourceCount is the group's size rather than what the account may see, which is how upstream's Video.GetMediaSourceCount counts it too. How the sources are ordered and named is filed as #229.

Fixed in `74adf6e5`, `1c4a86cb` and `53ac9c6c`, landed on `main`. **The join.** A scan files a cut with `PrimaryVersionId`, the title's presentation key, and a `LocalAlternateVersion` link rebuilt from the title's blob after each pass. A rewrite changes the primary and the key only for rows the scan owns, so a merge made by hand survives a rescan, and a row filed by an older pass is joined on the next one. A music video now gets a video blob at all, so its parts and cuts are stored. **The read side.** `/Items/{id}` serves `MediaSources` and `MediaSourceCount` for the group, and `PlaybackInfo` serves and negotiates every version, honouring `mediaSourceId` on POST and ignoring it on GET as upstream does. Source names use upstream's common-prefix rule, whose own test table is ported from `BaseItemTests.cs`. A group member the account may not see is left out; the asked row stays. **Verified** against upstream's own answers for the fixture's two version groups, by regenerating the answer key with four new columns (`owner`, `primary_version`, `local_alternate_versions`, `versions`), and by a native end-to-end scan: both groups joined, no foreign-key errors, and a second pass stable. The review caught two things, both fixed: the session and resume readers could not see the new link type, so playing a scanned cut reported the title's runtime and no streams and did not suppress its siblings from `/Items/Resume`; and asking a page for `MediaSources` made a query per row, where the count beside it was batched. Both go through one batched group read now. Known and left: `MediaSourceCount` is the group's size rather than what the account may see, which is how upstream's `Video.GetMediaSourceCount` counts it too. How the sources are ordered and named is filed as #229.
Sign in to join this conversation.
No description provided.