Browse Source

fix missing audio and subtitle language codes (#1822)

pull/1823/head
Jason Dove 1 year ago committed by GitHub
parent
commit
56a58d7a84
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 11
      ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs
  3. 12
      ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs
  4. 14
      ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs
  5. 3
      ErsatzTV.Core/Interfaces/Repositories/IMediaItemRepository.cs
  6. 3
      ErsatzTV.Core/Metadata/LanguageCodeAndName.cs
  7. 18
      ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs
  8. 11
      ErsatzTV/Validators/ChannelEditViewModelValidator.cs

2
CHANGELOG.md

@ -44,6 +44,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix adding pad filler to content that is less than one minute in duration - Fix adding pad filler to content that is less than one minute in duration
- Generate unique identifier for virtual HDHomeRun tuner by @raknam - Generate unique identifier for virtual HDHomeRun tuner by @raknam
- This allows a single Plex server to connect to multiple ETV instances - This allows a single Plex server to connect to multiple ETV instances
- Include *all* language codes from media library in preferred audio and subtitle language options
- Language codes where an English name cannot be found will be at the bottom of the list
### Changed ### Changed
- Remove some unnecessary API calls related to media server scanning and paging - Remove some unnecessary API calls related to media server scanning and paging

11
ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs

@ -39,7 +39,6 @@ public class CreateChannelHandler(
private static async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, CreateChannel request) => private static async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, CreateChannel request) =>
(ValidateName(request), await ValidateNumber(dbContext, request), (ValidateName(request), await ValidateNumber(dbContext, request),
await FFmpegProfileMustExist(dbContext, request), await FFmpegProfileMustExist(dbContext, request),
ValidatePreferredAudioLanguage(request),
ValidatePreferredSubtitleLanguage(request), ValidatePreferredSubtitleLanguage(request),
await WatermarkMustExist(dbContext, request), await WatermarkMustExist(dbContext, request),
await FillerPresetMustExist(dbContext, request)) await FillerPresetMustExist(dbContext, request))
@ -48,7 +47,6 @@ public class CreateChannelHandler(
name, name,
number, number,
ffmpegProfileId, ffmpegProfileId,
preferredAudioLanguageCode,
preferredSubtitleLanguageCode, preferredSubtitleLanguageCode,
watermarkId, watermarkId,
fillerPresetId) => fillerPresetId) =>
@ -76,7 +74,7 @@ public class CreateChannelHandler(
ProgressMode = request.ProgressMode, ProgressMode = request.ProgressMode,
StreamingMode = request.StreamingMode, StreamingMode = request.StreamingMode,
Artwork = artwork, Artwork = artwork,
PreferredAudioLanguageCode = preferredAudioLanguageCode, PreferredAudioLanguageCode = request.PreferredAudioLanguageCode,
PreferredAudioTitle = request.PreferredAudioTitle, PreferredAudioTitle = request.PreferredAudioTitle,
PreferredSubtitleLanguageCode = preferredSubtitleLanguageCode, PreferredSubtitleLanguageCode = preferredSubtitleLanguageCode,
SubtitleMode = request.SubtitleMode, SubtitleMode = request.SubtitleMode,
@ -101,13 +99,6 @@ public class CreateChannelHandler(
createChannel.NotEmpty(c => c.Name) createChannel.NotEmpty(c => c.Name)
.Bind(_ => createChannel.NotLongerThan(50)(c => c.Name)); .Bind(_ => createChannel.NotLongerThan(50)(c => c.Name));
private static Validation<BaseError, string> ValidatePreferredAudioLanguage(CreateChannel createChannel) =>
Optional(createChannel.PreferredAudioLanguageCode ?? string.Empty)
.Filter(
lc => string.IsNullOrWhiteSpace(lc) || CultureInfo.GetCultures(CultureTypes.NeutralCultures).Any(
ci => string.Equals(ci.ThreeLetterISOLanguageName, lc, StringComparison.OrdinalIgnoreCase)))
.ToValidation<BaseError>("Preferred audio language code is invalid");
private static Validation<BaseError, string> ValidatePreferredSubtitleLanguage(CreateChannel createChannel) => private static Validation<BaseError, string> ValidatePreferredSubtitleLanguage(CreateChannel createChannel) =>
Optional(createChannel.PreferredSubtitleLanguageCode ?? string.Empty) Optional(createChannel.PreferredSubtitleLanguageCode ?? string.Empty)
.Filter( .Filter(

12
ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs

@ -93,9 +93,8 @@ public class UpdateChannelHandler(
private static async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, UpdateChannel request) => private static async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, UpdateChannel request) =>
(await ChannelMustExist(dbContext, request), ValidateName(request), (await ChannelMustExist(dbContext, request), ValidateName(request),
await ValidateNumber(dbContext, request), await ValidateNumber(dbContext, request))
ValidatePreferredAudioLanguage(request)) .Apply((channelToUpdate, _, _) => channelToUpdate);
.Apply((channelToUpdate, _, _, _) => channelToUpdate);
private static Task<Validation<BaseError, Channel>> ChannelMustExist( private static Task<Validation<BaseError, Channel>> ChannelMustExist(
TvContext dbContext, TvContext dbContext,
@ -130,11 +129,4 @@ public class UpdateChannelHandler(
return BaseError.New("Channel number must be unique"); return BaseError.New("Channel number must be unique");
} }
private static Validation<BaseError, string> ValidatePreferredAudioLanguage(UpdateChannel updateChannel) =>
Optional(updateChannel.PreferredAudioLanguageCode ?? string.Empty)
.Filter(
lc => string.IsNullOrWhiteSpace(lc) || CultureInfo.GetCultures(CultureTypes.NeutralCultures).Any(
ci => string.Equals(ci.ThreeLetterISOLanguageName, lc, StringComparison.OrdinalIgnoreCase)))
.ToValidation<BaseError>("Preferred audio language code is invalid");
} }

14
ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs

@ -1,20 +1,18 @@
using System.Globalization; using System.Globalization;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Metadata;
namespace ErsatzTV.Application.MediaItems; namespace ErsatzTV.Application.MediaItems;
public class GetAllLanguageCodesHandler : IRequestHandler<GetAllLanguageCodes, List<LanguageCodeViewModel>> public class GetAllLanguageCodesHandler(IMediaItemRepository mediaItemRepository)
: IRequestHandler<GetAllLanguageCodes, List<LanguageCodeViewModel>>
{ {
private readonly IMediaItemRepository _mediaItemRepository;
public GetAllLanguageCodesHandler(IMediaItemRepository mediaItemRepository) =>
_mediaItemRepository = mediaItemRepository;
public async Task<List<LanguageCodeViewModel>> Handle( public async Task<List<LanguageCodeViewModel>> Handle(
GetAllLanguageCodes request, GetAllLanguageCodes request,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
List<CultureInfo> cultures = await _mediaItemRepository.GetAllLanguageCodeCultures(); List<LanguageCodeAndName> languageCodes = await mediaItemRepository.GetAllLanguageCodesAndNames();
return cultures.Map(c => new LanguageCodeViewModel(c.ThreeLetterISOLanguageName, c.EnglishName)).ToList(); return languageCodes.Map(c => new LanguageCodeViewModel(c.Code, c.Name)).ToList();
} }
} }

3
ErsatzTV.Core/Interfaces/Repositories/IMediaItemRepository.cs

@ -1,13 +1,14 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Globalization; using System.Globalization;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Metadata;
namespace ErsatzTV.Core.Interfaces.Repositories; namespace ErsatzTV.Core.Interfaces.Repositories;
public interface IMediaItemRepository public interface IMediaItemRepository
{ {
Task<List<CultureInfo>> GetAllKnownCultures(); Task<List<CultureInfo>> GetAllKnownCultures();
Task<List<CultureInfo>> GetAllLanguageCodeCultures(); Task<List<LanguageCodeAndName>> GetAllLanguageCodesAndNames();
Task<List<int>> FlagFileNotFound(LibraryPath libraryPath, string path); Task<List<int>> FlagFileNotFound(LibraryPath libraryPath, string path);
Task<Unit> FlagNormal(MediaItem mediaItem); Task<Unit> FlagNormal(MediaItem mediaItem);
Task<Either<BaseError, Unit>> DeleteItems(List<int> mediaItemIds); Task<Either<BaseError, Unit>> DeleteItems(List<int> mediaItemIds);

3
ErsatzTV.Core/Metadata/LanguageCodeAndName.cs

@ -0,0 +1,3 @@
namespace ErsatzTV.Core.Metadata;
public record LanguageCodeAndName(string Code, string Name);

18
ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs

@ -5,6 +5,7 @@ using ErsatzTV.Core;
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Extensions; using ErsatzTV.Core.Extensions;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Metadata;
using ErsatzTV.Infrastructure.Extensions; using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -41,27 +42,38 @@ public class MediaItemRepository : IMediaItemRepository
return result.ToList(); return result.ToList();
} }
public async Task<List<CultureInfo>> GetAllLanguageCodeCultures() public async Task<List<LanguageCodeAndName>> GetAllLanguageCodesAndNames()
{ {
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
var result = new System.Collections.Generic.HashSet<CultureInfo>(); var result = new System.Collections.Generic.HashSet<LanguageCodeAndName>();
CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures);
List<string> mediaCodes = await GetAllLanguageCodes(); List<string> mediaCodes = await GetAllLanguageCodes();
var unseenCodes = new System.Collections.Generic.HashSet<string>(mediaCodes);
foreach (string mediaCode in mediaCodes) foreach (string mediaCode in mediaCodes)
{ {
foreach (string code in await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCode)) foreach (string code in await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCode))
{ {
Option<CultureInfo> maybeCulture = allCultures.Find( Option<CultureInfo> maybeCulture = allCultures.Find(
c => string.Equals(code, c.ThreeLetterISOLanguageName, StringComparison.OrdinalIgnoreCase)); c => string.Equals(code, c.ThreeLetterISOLanguageName, StringComparison.OrdinalIgnoreCase));
foreach (CultureInfo culture in maybeCulture) foreach (CultureInfo culture in maybeCulture)
{ {
result.Add(culture); unseenCodes.Remove(mediaCode);
unseenCodes.Remove(code);
result.Add(new LanguageCodeAndName(culture.ThreeLetterISOLanguageName, culture.EnglishName));
} }
} }
} }
// every language code from the db must appear in the results
// entries that have no culture info (and thus english name) will just use the code twice
foreach (string mediaCode in unseenCodes.Where(c => !string.IsNullOrWhiteSpace(c)))
{
result.Add(new LanguageCodeAndName(mediaCode, mediaCode));
}
return result.ToList(); return result.ToList();
} }

11
ErsatzTV/Validators/ChannelEditViewModelValidator.cs

@ -15,16 +15,5 @@ public class ChannelEditViewModelValidator : AbstractValidator<ChannelEditViewMo
RuleFor(x => x.Name).NotEmpty(); RuleFor(x => x.Name).NotEmpty();
RuleFor(x => x.Group).NotEmpty(); RuleFor(x => x.Group).NotEmpty();
RuleFor(x => x.FFmpegProfileId).GreaterThan(0); RuleFor(x => x.FFmpegProfileId).GreaterThan(0);
RuleFor(x => x.PreferredAudioLanguageCode)
.Must(
languageCode => CultureInfo.GetCultures(CultureTypes.NeutralCultures)
.Any(
ci => string.Equals(
ci.ThreeLetterISOLanguageName,
languageCode,
StringComparison.OrdinalIgnoreCase)))
.When(vm => !string.IsNullOrWhiteSpace(vm.PreferredAudioLanguageCode))
.WithMessage("Preferred audio language code is invalid");
} }
} }

Loading…
Cancel
Save