Skip to content

Commit

Permalink
refactor: overhaul tag internals + add content ratings
Browse files Browse the repository at this point in the history
- Overhauled the internals for how we processed tags. We now use the tag hierarchy to filter it locally instead of the flat lists used on the Shoko Server side, which allows us to avoid magical numbers and also gives us more control when searching for tags in the hierarchy.

- Custom tag support have also been overhauled to support a _fake_ hierarchy by using `/` (forward slash) as a separator between levels in the tag name. All top level custom tags without "child" tags are added to the list if custom tags have been enabled, while all tags in the second level or below are treated as if they are of the AniDB hierarchy and will follow the other enabled/disabled tag sources, e.g. if you enable "elements" tags then it will also affect any custom tags starting with "elements/". This overhauled support for custom tags also allows us to better support the next item on the list.

- Added _assumed_ content ratings based on the AniDB tag hierarchy. This will use the `content indicators` tags and `elements` tags in the hierarchy to make an assumption of the content rating for the AniDB anime using the following content indicators;
  - TV-G
  - TV-Y
  - TV-Y7 (optionally with the FV indicator applied)
  - TV-PG (optionally with D, L, S, and/or V indicators applied)
  - TV-14 (optionally with D, L, S, and/or V indicators applied)
  - TV-MA (optionally with L, S, and/or V indicators applied)
  - XXX (only for H)

  We also support custom overrides through custom tags in the form of the regular expressions (regex);

  ```txt
  /^\/?target audience\/(?:TV)?[_\-]?(?:G|Y|Y7|PG|14|MA|XXX)[_\-]?(FV|D|S|L|V){1,5}$/i
  ```

  A few examples that all resolve to the `TV-PG` rating if you can't read regex;
  - `target audience/TV-PG` → `TV-PG`
  - `Target Audience/TV-PG-DV` → `TV-PG` + `D` & `V` indicators applied
  - `Target Audience/TV_PG_ld` → `TV-PG` + `D` & `L` indicators applied
  - `/target audience/pgV` → `TV-PG` + `V` indicator applied
  - `/TARGET AUDIENCE/tvPGsl` → `TV-PG` + `L` & `S` indicators applied

  Where you will override the content rating for the series if the custom tag is applied in Shoko Server/Shoko Desktop.

- Added production locations based on the AniDB tag hierarchy. This is much simpler, as it's a 1:N mapping for the relevant AniDB tags to production locations.

- Overhauled the settings page to accommodate the new settings added in this commit under the Metadata section while also removing the old Tags section. In case it wasn't obvious, **THIS IS A BREAKING CHANGE**. 🙂

**Note**: TMDB content ratings and production locations will be usable when when the data is available in Shoko Server.
  • Loading branch information
revam committed Jun 7, 2024
1 parent 6d10350 commit 7f06b05
Show file tree
Hide file tree
Showing 17 changed files with 1,951 additions and 484 deletions.
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"automagically",
"boxset",
"dlna",
"ecchi",
"emby",
"eroge",
"fanart",
Expand All @@ -20,21 +21,27 @@
"imdbid",
"interrobang",
"jellyfin",
"josei",
"jprm",
"kodomo",
"koma",
"linkbutton",
"manhua",
"manhwa",
"mina",
"nfo",
"nfos",
"outro",
"registrator",
"scrobble",
"scrobbled",
"scrobbling",
"seinen",
"seiyuu",
"shoko",
"shokofin",
"shoujo",
"shounen",
"signalr",
"tmdb",
"tvshow",
Expand Down
8 changes: 7 additions & 1 deletion Shokofin/API/Info/SeasonInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,14 @@ public class SeasonInfo
/// </summary>
public readonly DateTime? LastImportedAt;

public readonly string? AssumedContentRating;

public readonly IReadOnlyList<string> Tags;

public readonly IReadOnlyList<string> Genres;

public readonly IReadOnlyList<string> ProductionLocations;

public readonly IReadOnlyList<string> Studios;

public readonly IReadOnlyList<PersonInfo> Staff;
Expand Down Expand Up @@ -93,7 +97,7 @@ public class SeasonInfo
/// </summary>
public readonly IReadOnlyDictionary<string, RelationType> RelationMap;

public SeasonInfo(Series series, IEnumerable<string> extraIds, DateTime? earliestImportedAt, DateTime? lastImportedAt, List<EpisodeInfo> episodes, List<Role> cast, List<Relation> relations, string[] genres, string[] tags)
public SeasonInfo(Series series, IEnumerable<string> extraIds, DateTime? earliestImportedAt, DateTime? lastImportedAt, List<EpisodeInfo> episodes, List<Role> cast, List<Relation> relations, string[] genres, string[] tags, string[] productionLocations, string? contentRating)
{
var seriesId = series.IDs.Shoko.ToString();
var studios = cast
Expand Down Expand Up @@ -215,8 +219,10 @@ public SeasonInfo(Series series, IEnumerable<string> extraIds, DateTime? earlies
Type = type;
EarliestImportedAt = earliestImportedAt;
LastImportedAt = lastImportedAt;
AssumedContentRating = contentRating;
Tags = tags;
Genres = genres;
ProductionLocations = productionLocations;
Studios = studios;
Staff = staff;
RawEpisodeList = episodes;
Expand Down
13 changes: 7 additions & 6 deletions Shokofin/API/Info/ShowInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,6 @@ public class ShowInfo
.OrderBy(s => s)
.LastOrDefault();

/// <summary>
/// Overall content rating of the show.
/// </summary>
public string? OfficialRating =>
DefaultSeason.AniDB.Restricted ? "XXX" : null;

/// <summary>
/// Custom rating of the show.
/// </summary>
Expand Down Expand Up @@ -107,6 +101,11 @@ public class ShowInfo
/// </summary>
public readonly IReadOnlyList<string> Genres;

/// <summary>
/// All production locations from across all seasons.
/// </summary>
public readonly IReadOnlyList<string> ProductionLocations;

/// <summary>
/// All studios from across all seasons.
/// </summary>
Expand Down Expand Up @@ -179,6 +178,7 @@ public ShowInfo(SeasonInfo seasonInfo, string? collectionId = null)
LastImportedAt = seasonInfo.LastImportedAt;
Tags = seasonInfo.Tags;
Genres = seasonInfo.Genres;
ProductionLocations = seasonInfo.ProductionLocations;
Studios = seasonInfo.Studios;
Staff = seasonInfo.Staff;
SeasonList = new List<SeasonInfo>() { seasonInfo };
Expand Down Expand Up @@ -253,6 +253,7 @@ public ShowInfo(Group group, List<SeasonInfo> seasonList, ILogger logger, bool u
LastImportedAt = seasonList.Select(seasonInfo => seasonInfo.LastImportedAt).Max();
Tags = seasonList.SelectMany(s => s.Tags).Distinct().ToArray();
Genres = seasonList.SelectMany(s => s.Genres).Distinct().ToArray();
ProductionLocations = seasonList.SelectMany(s => s.ProductionLocations).Distinct().ToArray();
Studios = seasonList.SelectMany(s => s.Studios).Distinct().ToArray();
Staff = seasonList.SelectMany(s => s.Staff).DistinctBy(p => new { p.Type, p.Name, p.Role }).ToArray();
SeasonList = seasonList;
Expand Down
62 changes: 60 additions & 2 deletions Shokofin/API/Models/Tag.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;

using TagWeight = Shokofin.Utils.TagFilter.TagWeight;

namespace Shokofin.API.Models;

public class Tag
Expand Down Expand Up @@ -40,7 +44,8 @@ public class Tag
/// <summary>
/// True if the tag is considered a spoiler for all series it appears on.
/// </summary>
public bool IsSpoiler { get; set; }
[JsonPropertyName("IsSpoiler")]
public bool IsGlobalSpoiler { get; set; }

/// <summary>
/// True if the tag is considered a spoiler for that particular series it is
Expand All @@ -51,7 +56,7 @@ public class Tag
/// <summary>
/// How relevant is it to the series
/// </summary>
public int? Weight { get; set; }
public TagWeight? Weight { get; set; }

/// <summary>
/// When the tag info was last updated.
Expand All @@ -63,3 +68,56 @@ public class Tag
/// </summary>
public string Source { get; set; } = string.Empty;
}

public class ResolvedTag : Tag
{
private string? _fullName = null;

public string FullName => _fullName ??= Namespace + Name;

public bool IsParent => Children.Count is > 0;

public bool IsWeightless => Children.Count is 0 && Weight is 0;

/// <summary>
/// True if the tag is considered a spoiler for that particular series it is
/// set on.
/// </summary>
public new bool IsLocalSpoiler;

/// <summary>
/// How relevant is it to the series
/// </summary>
public new TagWeight Weight;

public string Namespace;

public ResolvedTag? Parent;

public IReadOnlyDictionary<string, ResolvedTag> Children;

public IReadOnlyDictionary<string, ResolvedTag> RecursiveNamespacedChildren;

public ResolvedTag(Tag tag, ResolvedTag? parent, Func<string, int, IEnumerable<Tag>?> getChildren, string ns = "/")
{
Id = tag.Id;
ParentId = parent?.Id;
Name = tag.Name;
Description = tag.Description;
IsVerified = tag.IsVerified;
IsGlobalSpoiler = tag.IsGlobalSpoiler || (parent?.IsGlobalSpoiler ?? false);
IsLocalSpoiler = tag.IsLocalSpoiler ?? parent?.IsLocalSpoiler ?? false;
Weight = tag.Weight ?? TagWeight.Weightless;
LastUpdated = tag.LastUpdated;
Source = tag.Source;
Namespace = ns;
Parent = parent;
Children = (getChildren(Source, Id) ?? Array.Empty<Tag>())
.DistinctBy(childTag => childTag.Name)
.Select(childTag => new ResolvedTag(childTag, this, getChildren, ns + tag.Name + "/"))
.ToDictionary(childTag => childTag.Name);
RecursiveNamespacedChildren = Children.Values
.SelectMany(childTag => childTag.RecursiveNamespacedChildren.Values.Prepend(childTag))
.ToDictionary(childTag => childTag.FullName[(ns.Length + Name.Length)..]);
}
}
Loading

0 comments on commit 7f06b05

Please sign in to comment.