feat(metadata): add original-language preferences (#526)
* feat(metadata): add original-language preferences * fix(settings): show metadata exceptions immediately * docs(settings): add metadata language screenshot * fix(settings): make language exceptions responsive * fix(settings): standardize language display names
This commit is contained in:
@@ -228,6 +228,11 @@ func (h *CatalogResourceHandler) HandleGetSeasons(w http.ResponseWriter, r *http
|
||||
}
|
||||
|
||||
if len(seasons) > 0 {
|
||||
if h.items.detailSvc != nil {
|
||||
if localized, locErr := h.items.detailSvc.LocalizeSeasonModels(r.Context(), seasons, filter); locErr == nil && len(localized) == len(seasons) {
|
||||
seasons = localized
|
||||
}
|
||||
}
|
||||
episodesBySeason, err := h.items.episodeRepo.ListBySeriesGroupedBySeason(r.Context(), id)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "internal_error", "Failed to list seasons")
|
||||
@@ -245,7 +250,7 @@ func (h *CatalogResourceHandler) HandleGetSeasons(w http.ResponseWriter, r *http
|
||||
if hasProgressMap {
|
||||
userData = catalog.EpisodeRollupUserData(episodes, progressMap)
|
||||
}
|
||||
sr := h.items.toSeasonResponseFromEpisodes(r, id, s, episodes, userData)
|
||||
sr := h.items.seasonResponseFromEpisodes(r, s, episodes, userData)
|
||||
resp = append(resp, sr)
|
||||
}
|
||||
|
||||
|
||||
@@ -1478,7 +1478,18 @@ func (h *ItemsHandler) toSeasonResponseFromEpisodes(
|
||||
s = localized
|
||||
}
|
||||
}
|
||||
return h.seasonResponseFromEpisodes(r, s, episodes, userData)
|
||||
}
|
||||
|
||||
// seasonResponseFromEpisodes maps a season that has already been localized.
|
||||
// List endpoints use this after LocalizeSeasonModels so they do not repeat the
|
||||
// localization query for every row.
|
||||
func (h *ItemsHandler) seasonResponseFromEpisodes(
|
||||
r *http.Request,
|
||||
s *models.Season,
|
||||
episodes []*models.Episode,
|
||||
userData *catalog.SeasonUserData,
|
||||
) seasonResponse {
|
||||
resp := seasonResponse{
|
||||
ContentID: s.ContentID,
|
||||
SeasonNumber: s.SeasonNumber,
|
||||
@@ -1814,15 +1825,16 @@ func (h *ItemsHandler) accessFilter(r *http.Request) catalog.AccessFilter {
|
||||
|
||||
if scope, ok := access.GetScope(r.Context()); ok {
|
||||
return catalog.AccessFilter{
|
||||
AllowedLibraryIDs: scope.AllowedLibraryIDs,
|
||||
DisabledLibraryIDs: scope.DisabledLibraryIDs,
|
||||
MaxContentRating: scope.MaxContentRating,
|
||||
MaxPlaybackQuality: scope.MaxPlaybackQuality,
|
||||
PresentationLibraryID: presentationLibraryID,
|
||||
ProfilePreferredLanguage: scope.PreferredMetadataLanguage,
|
||||
SelectedFileID: selectedFileID,
|
||||
UserID: apimw.GetUserID(r.Context()),
|
||||
ProfileID: apimw.GetProfileID(r.Context()),
|
||||
AllowedLibraryIDs: scope.AllowedLibraryIDs,
|
||||
DisabledLibraryIDs: scope.DisabledLibraryIDs,
|
||||
MaxContentRating: scope.MaxContentRating,
|
||||
MaxPlaybackQuality: scope.MaxPlaybackQuality,
|
||||
PresentationLibraryID: presentationLibraryID,
|
||||
ProfilePreferredLanguage: scope.PreferredMetadataLanguage,
|
||||
MetadataLanguageOverrides: scope.MetadataLanguageOverrides,
|
||||
SelectedFileID: selectedFileID,
|
||||
UserID: apimw.GetUserID(r.Context()),
|
||||
ProfileID: apimw.GetProfileID(r.Context()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ type SettingValuesHandler struct {
|
||||
}
|
||||
|
||||
type languageSuggestionSource interface {
|
||||
ListOriginalLanguages(context.Context, catalog.BrowseFilters) ([]string, error)
|
||||
ListAudioLanguages(context.Context, catalog.BrowseFilters) ([]string, error)
|
||||
ListSubtitleLanguages(context.Context, catalog.BrowseFilters) ([]string, error)
|
||||
}
|
||||
@@ -1073,16 +1074,18 @@ func (h *SettingValuesHandler) observedLanguageSuggestions(
|
||||
return result
|
||||
}
|
||||
|
||||
wantsAudio, wantsSubtitles := false, false
|
||||
wantsMetadata, wantsAudio, wantsSubtitles := false, false, false
|
||||
for _, eff := range resolved {
|
||||
switch eff.Key {
|
||||
case settingskeys.CatalogMetadataLanguage:
|
||||
wantsMetadata = true
|
||||
case settingskeys.PlaybackAudioLanguage:
|
||||
wantsAudio = true
|
||||
case settingskeys.PlaybackSubtitleLanguage:
|
||||
wantsSubtitles = true
|
||||
}
|
||||
}
|
||||
if !wantsAudio && !wantsSubtitles {
|
||||
if !wantsMetadata && !wantsAudio && !wantsSubtitles {
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1092,6 +1095,15 @@ func (h *SettingValuesHandler) observedLanguageSuggestions(
|
||||
filters.DisabledLibraryIDs = scope.DisabledLibraryIDs
|
||||
filters.MaxContentRating = scope.MaxContentRating
|
||||
}
|
||||
if wantsMetadata {
|
||||
values, err := h.languageSource.ListOriginalLanguages(r.Context(), filters)
|
||||
if err != nil {
|
||||
slog.WarnContext(r.Context(), "settings: listing metadata language suggestions",
|
||||
"component", "settings", "error", err)
|
||||
} else {
|
||||
result[settingskeys.CatalogMetadataLanguage] = values
|
||||
}
|
||||
}
|
||||
if wantsAudio {
|
||||
values, err := h.languageSource.ListAudioLanguages(r.Context(), filters)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,6 +20,8 @@ import (
|
||||
"github.com/Silo-Server/silo-server/internal/catalog"
|
||||
"github.com/Silo-Server/silo-server/internal/models"
|
||||
"github.com/Silo-Server/silo-server/internal/settingscontract"
|
||||
"github.com/Silo-Server/silo-server/internal/settingskeys"
|
||||
"github.com/Silo-Server/silo-server/internal/settingsresolve"
|
||||
"github.com/Silo-Server/silo-server/internal/userdb"
|
||||
"github.com/Silo-Server/silo-server/internal/userstore"
|
||||
)
|
||||
@@ -852,3 +854,52 @@ func TestMergeLanguageSuggestionsUsesExactCurrentAlias(t *testing.T) {
|
||||
t.Errorf("mergeLanguageSuggestions() = %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
type recordingLanguageSuggestionSource struct {
|
||||
filters catalog.BrowseFilters
|
||||
original []string
|
||||
}
|
||||
|
||||
func (s *recordingLanguageSuggestionSource) ListOriginalLanguages(
|
||||
_ context.Context, filters catalog.BrowseFilters,
|
||||
) ([]string, error) {
|
||||
s.filters = filters
|
||||
return s.original, nil
|
||||
}
|
||||
|
||||
func (*recordingLanguageSuggestionSource) ListAudioLanguages(
|
||||
context.Context, catalog.BrowseFilters,
|
||||
) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (*recordingLanguageSuggestionSource) ListSubtitleLanguages(
|
||||
context.Context, catalog.BrowseFilters,
|
||||
) ([]string, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestObservedLanguageSuggestionsIncludesAccessibleOriginalLanguages(t *testing.T) {
|
||||
source := &recordingLanguageSuggestionSource{original: []string{"is", "no"}}
|
||||
handler, _ := newValuesTestHandler(t)
|
||||
handler.SetLanguageSuggestionSource(source)
|
||||
|
||||
req := valuesRequest(http.MethodGet, "/settings/values/effective", nil)
|
||||
req = req.WithContext(access.SetScope(req.Context(), access.Scope{
|
||||
AllowedLibraryIDs: []int{4, 9},
|
||||
DisabledLibraryIDs: []int{12},
|
||||
MaxContentRating: "PG-13",
|
||||
}))
|
||||
observed := handler.observedLanguageSuggestions(req, []settingsresolve.Effective{{
|
||||
Key: settingskeys.CatalogMetadataLanguage,
|
||||
}})
|
||||
|
||||
if !slices.Equal(observed[settingskeys.CatalogMetadataLanguage], []string{"is", "no"}) {
|
||||
t.Fatalf("metadata suggestions = %v", observed[settingskeys.CatalogMetadataLanguage])
|
||||
}
|
||||
if !slices.Equal(source.filters.LibraryIDs, []int{4, 9}) ||
|
||||
!slices.Equal(source.filters.DisabledLibraryIDs, []int{12}) ||
|
||||
source.filters.MaxContentRating != "PG-13" {
|
||||
t.Fatalf("catalog filters = %#v", source.filters)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user