Repository navigation
Conversation
Providers return a variety of junk for `releasedate`. The client's error handler skips a string which is not a date, as the property is nullable, but an object or an array still fails the whole movie or episode details: the reader throws on it even with the error handled. Add SafeDateTimeConverter, which reads the whole token and yields null for any token that does not hold a usable date, and apply it to the `releasedate` of both VodInfo and EpisodeInfo. It only supports nullable targets, as there is no meaningful date to fall back to. It follows OnlyObjectConverter, the existing converter for the same class of problem: the read is narrowed and the write is left to the serializer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A series or movie whose response deviated from the expected schema was
discarded in full: providers return `[]` instead of `{}` for an item with
no data, `"episodes": []` for a series with no episodes, a non-numeric
episodes key such as "specials", or an occasional field of the wrong type.
Any one of these lost the entire item, including everything that did
parse.
Handle the two cases separately:
- A response whose root is not an object carries no item at all. The two
endpoints which return a single object, get_series_info and get_vod_info,
now share a guard which inspects the root token and returns null for it,
logging which shape arrived, so callers can skip just that item. Their
callers handle null accordingly.
- A failure on a single member is recoverable. The deserialization error
handler already skipped members whose target property is nullable; it now
skips any member, leaving it at its default and keeping the rest of the
response. Unexpected members are logged at warning level, while a nullable
property without a usable value stays at debug as before. An error at the
root of the document has no member to skip, so it is left to propagate and
the guard above reports it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DataVersion is the cache key for the plugin's channels, so it must change when the configuration changes and hold still otherwise. It did neither reliably. Assembly.GetCallingAssembly() in a property getter returns whichever assembly happened to call it and is sensitive to inlining, so the version component could come from Jellyfin rather than from the plugin. Use the plugin assembly explicitly. Configuration.GetHashCode() is an object identity hash. It changes when the configuration object is replaced, but it also changes on every restart, so every cached channel was invalidated and the whole provider re-crawled each time Jellyfin started. Derive the component from the serialized contents of the configuration instead, so it depends on the configuration and nothing else. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some providers bucket every episode under a single arbitrary key of the `episodes` map, so the key says one season while the episodes themselves report another. Those seasons were missing from the library: the season list was built from the map's keys, so only the keys ever appeared as folders, and the episodes inside them carried a season number that disagreed with the folder they were in. Group the episodes by the season they report, and build both the season list and the episode list from that one grouping, so the two cannot disagree. The fallback to the map's key is per episode rather than per series: providers populate `season` unevenly, and an episode whose season is missing or unparseable has to stay with the bucket it came from instead of collecting in a season 0 that does not exist. Report the season an episode was resolved into as its ParentIndexNumber rather than the season the episode claims. For providers which omit `season` the claimed value is 0 on every episode, which told Jellyfin that each one belonged to season 0 no matter which folder it appeared in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The behaviour these fixes depend on is entirely a matter of what a provider sends back, which is awkward to reach through the Jellyfin plugin host, so none of it was covered. Add a xUnit project exercising the parts that decide what ends up in the library: the release date converter, the shared guard and error handling for malformed single-object responses, the grouping which backs both the season and the episode listing, and the configuration component of the channel cache key. Each provider response shape which caused a reported problem is present as a test case. The helpers the tests reach for are already internal, so the plugin assembly gains InternalsVisibleTo rather than widening anything further. The project is marked non-publishable: JPRM publishes every project in the solution into a single output directory, so without it the test binaries land next to the plugin in the release build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deserialization error handler skips a member it cannot parse and leaves it at its default, so an id which arrived as "n/a" or a JSON null becomes 0. That is indistinguishable from real data further down, and surfaces in the library as a channel, movie, series or episode which cannot be played. Xtream ids count up from one, so drop anything lower where the listings are built rather than letting it through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dropping an item whose id could not be parsed is silent, so a provider sending bad ids shows up only as a library which is quietly smaller than it should be. Report the count at warning level for each listing, and for the episodes of a series, so the cause is visible in the log. StreamService now takes an ILogger. The episode grouping is static and is also called from the tests, so it takes one only when the caller has it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A season folder carries the season id it was built with, and GetEpisodes looked that id up in a freshly computed grouping. When the two disagree, as they do for a folder cached before the grouping changed, the lookup missed and the season was returned empty. An empty season reads as a series with nothing to play, and asking Jellyfin to play it posts PlaybackInfo against the folder itself, which throws because a folder carries no media source. Fall back to the episodes stored under that key, and report it, so a stale folder resolves instead of appearing empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Providers send `"cast": null` or `"genre": null` for some series, which overrides the empty default of these properties. Splitting them then threw a NullReferenceException, which failed the listing of the whole category, so it showed no series at all (Kevinjil#267). An empty or blank value did not fail but gave the series a person or genre with an empty name. Read null as no value, and drop empty entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some providers send `last_modified` as a date string instead of a Unix timestamp, which failed the listing of every series of the category (Kevinjil#287). Since "Keep parsing when a provider returns an unexpected JSON shape", the member is skipped instead; cover it with the provider's sample. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Open
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes for provider data which made series, seasons or movies disappear, and for a cache key which re-crawled the provider on every restart. Fixes only, no new features. This branch targets 10.11 like master.
Changes
[]instead of{},"episodes": [], a non-numeric episodes key such as"specials", or a field of the wrong type. A member that cannot be parsed is now skipped and logged, keeping the rest. For Cannot convert invalid value to System.DateTime. Path '[0].last_modified' #287, the provider'slast_modifieddate strings failed the listing of the whole category.releasedate: an object or an array instead of a date failed the whole movie or episode details, even through the error handler; it is now read as no date."cast": nullor"genre": nullthrew aNullReferenceException, which emptied the whole category.episodesmap, so seasons were missing. Episodes are now grouped by the season they report, falling back to the key. This is the bug Fix missing episodes when stored under different dictionary key #284 addressed, with a different approach. A season folder built before this change still resolves through the key it was built with, instead of appearing empty."n/a"ornullwould become 0, giving an item which cannot be played. These items are dropped, and their number is logged.DataVersionusedAssembly.GetCallingAssembly()andConfiguration.GetHashCode(), so it changed on every restart and invalidated every cached channel. It now uses the plugin assembly and a hash of the configuration's contents. Updating to this version invalidates the cache once.Adds a test project (xUnit, net9.0) covering these cases, with samples from the issues.
Testing
On this branch: 599 tests pass, and every commit builds against Jellyfin 10.11. Tested with a real provider on Jellyfin 10.11.11 (Docker) and 12.1 (Debian package).
Written with Claude Code (Claude Opus 5.5).
🤖 Generated with Claude Code