An item with no path of its own reports none of its images #65

Closed
opened 2026-08-04 13:20:40 +02:00 by Claude · 1 comment
Collaborator

An item with no path of its own reports none of its images.

tag::image (crates/jellyfin-http/src/map/tag.rs:28) gives up when the item
row has no Path:

let path = item.path.as_deref()?;

so every image on such an item vanishes from ImageTags. On the measured
library that is 405 of 1113 seasons, each of which does have a stored poster
row — one season carries poster.jpg at 2000×3000 and reports no Primary tag
at all, where upstream reports ed44e03ca1dd098812982e98f75254d0.

Upstream hashes item.Path + image.DateModified.Ticks
(ImageProcessor.cs:408-413). C# concatenates a null string as empty, so a
path-less item still produces a tag — it just hashes the ticks alone.

Naively hashing the empty path does not reproduce it. MD5 of "" + ticks
over the stored DateModified matches neither in UTF-16LE nor UTF-8, so the
exact input has to be derived against upstream's served tags rather than assumed
— the tick conversion or the stored precision is the likely difference.

This also makes a second thing wrong. A season whose own poster goes unreported
looks to inherited::borrow_poster_shape like a season with no poster, so it
borrows the series' shape and reports a PrimaryImageAspectRatio upstream does
not send. That is measurable in the large: /Items?Recursive=true is now
412,095 bytes longer than upstream's, having been 254,898 shorter before the
inheritance landed.

Fixing the tag closes both.

An item with no path of its own reports none of its images. `tag::image` (`crates/jellyfin-http/src/map/tag.rs:28`) gives up when the item row has no `Path`: let path = item.path.as_deref()?; so every image on such an item vanishes from `ImageTags`. On the measured library that is **405 of 1113 seasons**, each of which does have a stored poster row — one season carries `poster.jpg` at 2000×3000 and reports no `Primary` tag at all, where upstream reports `ed44e03ca1dd098812982e98f75254d0`. Upstream hashes `item.Path + image.DateModified.Ticks` (`ImageProcessor.cs:408-413`). C# concatenates a null string as empty, so a path-less item still produces a tag — it just hashes the ticks alone. **Naively hashing the empty path does not reproduce it.** MD5 of `"" + ticks` over the stored `DateModified` matches neither in UTF-16LE nor UTF-8, so the exact input has to be derived against upstream's served tags rather than assumed — the tick conversion or the stored precision is the likely difference. This also makes a second thing wrong. A season whose own poster goes unreported looks to `inherited::borrow_poster_shape` like a season with no poster, so it borrows the series' shape and reports a `PrimaryImageAspectRatio` upstream does not send. That is measurable in the large: `/Items?Recursive=true` is now **412,095 bytes longer** than upstream's, having been 254,898 shorter before the inheritance landed. Fixing the tag closes both.
Author
Collaborator

Fixed, and the negative result in this issue was mine and wrong.

The empty path does reproduce upstream's tag. Season does not override
BaseItem.Path, so a virtual season's path really is null, C# concatenates it
as empty, and the hash input is the ticks alone — "638982150964481748" in
UTF-16LE, MD5, read back as a little-endian guid, giving
ed44e03ca1dd098812982e98f75254d0. That is what Guid::hashed already does.
The only defect was tag::image giving up on a null path before reaching it. My
inability to reproduce it by hand was a tick-conversion slip.

Match rate, checked against live upstream rather than reasoned:

before after
path-less items with an identical ImageTags 7,027 / 7,327 (95.91 %) 7,327 / 7,327 (100 %)
path-ful items 14,568 / 14,568 unchanged
all 300 path-less items carrying a dated image — 300 / 300

Fields differing from upstream over the whole 21,895-item response:

field before after
ImageTags 300 0
PrimaryImageAspectRatio 300 0
ParentPrimaryImageTag / ParentPrimaryImageItemId 5,604 0
ImageBlurHashes 11,014 5,604

Items differing in any field: 12,854 → 7,444, with none newly broken.

The body grew rather than shrank, and that is right: the earlier +412 KB was a
net of ~484 KB we were wrongly omitting against ~900 KB we were wrongly
emitting. Removing the omission stops the two cancelling. Byte-count-toward-
upstream was never the right measure here; per-field differing-item counts are,
and every one moved down or held.

Fixed, and **the negative result in this issue was mine and wrong.** The empty path does reproduce upstream's tag. `Season` does not override `BaseItem.Path`, so a virtual season's path really is null, C# concatenates it as empty, and the hash input is the ticks alone — `"638982150964481748"` in UTF-16LE, MD5, read back as a little-endian guid, giving `ed44e03ca1dd098812982e98f75254d0`. That is what `Guid::hashed` already does. The only defect was `tag::image` giving up on a null path before reaching it. My inability to reproduce it by hand was a tick-conversion slip. Match rate, checked against live upstream rather than reasoned: | | before | after | |---|---|---| | path-less items with an identical `ImageTags` | 7,027 / 7,327 (95.91 %) | **7,327 / 7,327 (100 %)** | | path-ful items | 14,568 / 14,568 | unchanged | | all 300 path-less items carrying a dated image | — | **300 / 300** | Fields differing from upstream over the whole 21,895-item response: | field | before | after | |---|---|---| | `ImageTags` | 300 | **0** | | `PrimaryImageAspectRatio` | 300 | **0** | | `ParentPrimaryImageTag` / `ParentPrimaryImageItemId` | 5,604 | **0** | | `ImageBlurHashes` | 11,014 | 5,604 | Items differing in any field: **12,854 → 7,444**, with none newly broken. The body grew rather than shrank, and that is right: the earlier +412 KB was a net of ~484 KB we were wrongly omitting against ~900 KB we were wrongly emitting. Removing the omission stops the two cancelling. Byte-count-toward- upstream was never the right measure here; per-field differing-item counts are, and every one moved down or held.
Sign in to join this conversation.
No description provided.