diff options
Diffstat (limited to 'MediaBrowser.Model/Entities')
| -rw-r--r-- | MediaBrowser.Model/Entities/MediaStream.cs | 273 | ||||
| -rw-r--r-- | MediaBrowser.Model/Entities/ProviderIdsExtensions.cs | 89 |
2 files changed, 237 insertions, 125 deletions
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 4491fb5ace..f057714bea 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -260,6 +260,8 @@ namespace MediaBrowser.Model.Entities public string LocalizedLanguage { get; set; } + public string LocalizedOriginal { get; set; } + public string DisplayTitle { get @@ -267,162 +269,167 @@ namespace MediaBrowser.Model.Entities switch (Type) { case MediaStreamType.Audio: - { - var attributes = new List<string>(); - - // Do not display the language code in display titles if unset or set to a special code. Show it in all other cases (possibly expanded). - if (!string.IsNullOrEmpty(Language) && !_specialCodes.Contains(Language, StringComparison.OrdinalIgnoreCase)) { - // Use pre-resolved localized language name, falling back to raw language code. - attributes.Add(StringHelper.FirstToUpper(LocalizedLanguage ?? Language)); - } + var attributes = new List<string>(); - if (!string.IsNullOrEmpty(Profile) && !string.Equals(Profile, "lc", StringComparison.OrdinalIgnoreCase)) - { - attributes.Add(Profile); - } - else if (!string.IsNullOrEmpty(Codec)) - { - attributes.Add(AudioCodec.GetFriendlyName(Codec)); - } + // Do not display the language code in display titles if unset or set to a special code. Show it in all other cases (possibly expanded). + if (!string.IsNullOrEmpty(Language) && !_specialCodes.Contains(Language, StringComparison.OrdinalIgnoreCase)) + { + // Use pre-resolved localized language name, falling back to raw language code. + attributes.Add(StringHelper.FirstToUpper(LocalizedLanguage ?? Language)); + } - if (!string.IsNullOrEmpty(ChannelLayout)) - { - attributes.Add(StringHelper.FirstToUpper(ChannelLayout)); - } - else if (Channels.HasValue) - { - attributes.Add(Channels.Value.ToString(CultureInfo.InvariantCulture) + " ch"); - } + if (!string.IsNullOrEmpty(Profile) && !string.Equals(Profile, "lc", StringComparison.OrdinalIgnoreCase)) + { + attributes.Add(Profile); + } + else if (!string.IsNullOrEmpty(Codec)) + { + attributes.Add(AudioCodec.GetFriendlyName(Codec)); + } - if (IsDefault) - { - attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault); - } + if (!string.IsNullOrEmpty(ChannelLayout)) + { + attributes.Add(StringHelper.FirstToUpper(ChannelLayout)); + } + else if (Channels.HasValue) + { + attributes.Add(Channels.Value.ToString(CultureInfo.InvariantCulture) + " ch"); + } - if (IsExternal) - { - attributes.Add(string.IsNullOrEmpty(LocalizedExternal) ? "External" : LocalizedExternal); - } + if (IsDefault) + { + attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault); + } - if (!string.IsNullOrEmpty(Title)) - { - var result = new StringBuilder(Title); - foreach (var tag in attributes) + if (IsExternal) + { + attributes.Add(string.IsNullOrEmpty(LocalizedExternal) ? "External" : LocalizedExternal); + } + + if (IsOriginal) + { + attributes.Add(string.IsNullOrEmpty(LocalizedOriginal) ? "Original" : LocalizedOriginal); + } + + if (!string.IsNullOrEmpty(Title)) { - // Keep Tags that are not already in Title. - if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) + var result = new StringBuilder(Title); + foreach (var tag in attributes) { - result.Append(" - ").Append(tag); + // Keep Tags that are not already in Title. + if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) + { + result.Append(" - ").Append(tag); + } } + + return result.ToString(); } - return result.ToString(); + return string.Join(" - ", attributes); } - return string.Join(" - ", attributes); - } - case MediaStreamType.Video: - { - var attributes = new List<string>(); + { + var attributes = new List<string>(); - var resolutionText = GetResolutionText(); + var resolutionText = GetResolutionText(); - if (!string.IsNullOrEmpty(resolutionText)) - { - attributes.Add(resolutionText); - } + if (!string.IsNullOrEmpty(resolutionText)) + { + attributes.Add(resolutionText); + } - if (!string.IsNullOrEmpty(Codec)) - { - attributes.Add(Codec.ToUpperInvariant()); - } + if (!string.IsNullOrEmpty(Codec)) + { + attributes.Add(Codec.ToUpperInvariant()); + } - if (VideoDoViTitle is not null) - { - attributes.Add(VideoDoViTitle); - } - else if (VideoRange != VideoRange.Unknown) - { - attributes.Add(VideoRange.ToString()); - } + if (VideoDoViTitle is not null) + { + attributes.Add(VideoDoViTitle); + } + else if (VideoRange != VideoRange.Unknown) + { + attributes.Add(VideoRange.ToString()); + } - if (!string.IsNullOrEmpty(Title)) - { - var result = new StringBuilder(Title); - foreach (var tag in attributes) + if (!string.IsNullOrEmpty(Title)) { - // Keep Tags that are not already in Title. - if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) + var result = new StringBuilder(Title); + foreach (var tag in attributes) { - result.Append(" - ").Append(tag); + // Keep Tags that are not already in Title. + if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) + { + result.Append(" - ").Append(tag); + } } + + return result.ToString(); } - return result.ToString(); + return string.Join(' ', attributes); } - return string.Join(' ', attributes); - } - case MediaStreamType.Subtitle: - { - var attributes = new List<string>(); - - if (!string.IsNullOrEmpty(Language)) - { - // Use pre-resolved localized language name, falling back to raw language code. - attributes.Add(StringHelper.FirstToUpper(LocalizedLanguage ?? Language)); - } - else { - attributes.Add(string.IsNullOrEmpty(LocalizedUndefined) ? "Und" : LocalizedUndefined); - } + var attributes = new List<string>(); - if (IsHearingImpaired == true) - { - attributes.Add(string.IsNullOrEmpty(LocalizedHearingImpaired) ? "Hearing Impaired" : LocalizedHearingImpaired); - } + if (!string.IsNullOrEmpty(Language)) + { + // Use pre-resolved localized language name, falling back to raw language code. + attributes.Add(StringHelper.FirstToUpper(LocalizedLanguage ?? Language)); + } + else + { + attributes.Add(string.IsNullOrEmpty(LocalizedUndefined) ? "Und" : LocalizedUndefined); + } - if (IsDefault) - { - attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault); - } + if (IsHearingImpaired == true) + { + attributes.Add(string.IsNullOrEmpty(LocalizedHearingImpaired) ? "Hearing Impaired" : LocalizedHearingImpaired); + } - if (IsForced) - { - attributes.Add(string.IsNullOrEmpty(LocalizedForced) ? "Forced" : LocalizedForced); - } + if (IsDefault) + { + attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault); + } - if (!string.IsNullOrEmpty(Codec)) - { - attributes.Add(Codec.ToUpperInvariant()); - } + if (IsForced) + { + attributes.Add(string.IsNullOrEmpty(LocalizedForced) ? "Forced" : LocalizedForced); + } - if (IsExternal) - { - attributes.Add(string.IsNullOrEmpty(LocalizedExternal) ? "External" : LocalizedExternal); - } + if (!string.IsNullOrEmpty(Codec)) + { + attributes.Add(Codec.ToUpperInvariant()); + } - if (!string.IsNullOrEmpty(Title)) - { - var result = new StringBuilder(Title); - foreach (var tag in attributes) + if (IsExternal) { - // Keep Tags that are not already in Title. - if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) + attributes.Add(string.IsNullOrEmpty(LocalizedExternal) ? "External" : LocalizedExternal); + } + + if (!string.IsNullOrEmpty(Title)) + { + var result = new StringBuilder(Title); + foreach (var tag in attributes) { - result.Append(" - ").Append(tag); + // Keep Tags that are not already in Title. + if (!Title.Contains(tag, StringComparison.OrdinalIgnoreCase)) + { + result.Append(" - ").Append(tag); + } } + + return result.ToString(); } - return result.ToString(); + return string.Join(" - ", attributes); } - return string.Join(" - ", attributes); - } - default: return null; } @@ -500,6 +507,12 @@ namespace MediaBrowser.Model.Entities public bool IsHearingImpaired { get; set; } /// <summary> + /// Gets or sets a value indicating whether this instance is original. + /// </summary> + /// <value><c>true</c> if this instance is original; otherwise, <c>false</c>.</value> + public bool IsOriginal { get; set; } + + /// <summary> /// Gets or sets the height. /// </summary> /// <value>The height.</value> @@ -631,13 +644,32 @@ namespace MediaBrowser.Model.Entities } } + [JsonIgnore] + public bool IsVobSubSubtitleStream + { + get + { + if (Type != MediaStreamType.Subtitle) + { + return false; + } + + if (string.IsNullOrEmpty(Codec) && !IsExternal) + { + return false; + } + + return IsVobSubFormat(Codec); + } + } + /// <summary> /// Gets a value indicating whether this is a subtitle steam that is extractable by ffmpeg. /// All text-based and pgs subtitles can be extracted. /// </summary> /// <value><c>true</c> if this is a extractable subtitle steam otherwise, <c>false</c>.</value> [JsonIgnore] - public bool IsExtractableSubtitleStream => IsTextSubtitleStream || IsPgsSubtitleStream; + public bool IsExtractableSubtitleStream => IsTextSubtitleStream || IsPgsSubtitleStream || IsVobSubSubtitleStream; /// <summary> /// Gets or sets a value indicating whether [supports external stream]. @@ -715,6 +747,7 @@ namespace MediaBrowser.Model.Entities return codec.Contains("microdvd", StringComparison.OrdinalIgnoreCase) || (!codec.Contains("pgs", StringComparison.OrdinalIgnoreCase) && !codec.Contains("dvdsub", StringComparison.OrdinalIgnoreCase) + && !codec.Contains("vobsub", StringComparison.OrdinalIgnoreCase) && !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase) && !string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase) && !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase)); @@ -728,6 +761,14 @@ namespace MediaBrowser.Model.Entities || string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase); } + public static bool IsVobSubFormat(string format) + { + string codec = format ?? string.Empty; + + return codec.Contains("dvdsub", StringComparison.OrdinalIgnoreCase) + || codec.Contains("vobsub", StringComparison.OrdinalIgnoreCase); + } + public bool SupportsSubtitleConversionTo(string toCodec) { if (!IsTextSubtitleStream) diff --git a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs index 385a86d31c..09eba92d9e 100644 --- a/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs +++ b/MediaBrowser.Model/Entities/ProviderIdsExtensions.cs @@ -1,14 +1,16 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.Linq; +using System.Text.RegularExpressions; namespace MediaBrowser.Model.Entities; /// <summary> /// Class ProviderIdsExtensions. /// </summary> -public static class ProviderIdsExtensions +public static partial class ProviderIdsExtensions { /// <summary> /// Case-insensitive dictionary of <see cref="MetadataProvider"/> string representation. @@ -21,6 +23,27 @@ public static class ProviderIdsExtensions StringComparer.OrdinalIgnoreCase); /// <summary> + /// The known id formats, keyed by provider name. + /// </summary> + private static readonly Dictionary<string, Func<string, bool>> _providerIdValidators = + new(StringComparer.OrdinalIgnoreCase) + { + [MetadataProvider.Imdb.ToString()] = value => ImdbIdRegex().IsMatch(value), + [MetadataProvider.Tmdb.ToString()] = IsPositiveNumber, + [MetadataProvider.TmdbCollection.ToString()] = IsPositiveNumber, + [MetadataProvider.AudioDbArtist.ToString()] = IsPositiveNumber, + [MetadataProvider.AudioDbAlbum.ToString()] = IsPositiveNumber, + + // Every MusicBrainz id is an MBID. + [MetadataProvider.MusicBrainzAlbum.ToString()] = IsGuid, + [MetadataProvider.MusicBrainzAlbumArtist.ToString()] = IsGuid, + [MetadataProvider.MusicBrainzArtist.ToString()] = IsGuid, + [MetadataProvider.MusicBrainzReleaseGroup.ToString()] = IsGuid, + [MetadataProvider.MusicBrainzRecording.ToString()] = IsGuid, + [MetadataProvider.MusicBrainzTrack.ToString()] = IsGuid + }; + + /// <summary> /// Checks if this instance has an id for the given provider. /// </summary> /// <param name="instance">The instance.</param> @@ -102,6 +125,26 @@ public static class ProviderIdsExtensions } /// <summary> + /// Checks whether a value can be an id of the given provider. + /// </summary> + /// <param name="name">The provider name.</param> + /// <param name="value">The provider id.</param> + /// <returns><c>true</c> if the value has a plausible format for the provider; otherwise, <c>false</c>.</returns> + /// <remarks> + /// Providers regularly hand out an id belonging to a different service, e.g. an IMDb person id in the + /// TMDb field. Such an id is not just useless, it also makes the owning provider fail for the item. + /// </remarks> + public static bool IsValidProviderId(string? name, string? value) + { + if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(value)) + { + return false; + } + + return !_providerIdValidators.TryGetValue(name, out var isValid) || isValid(value); + } + + /// <summary> /// Sets a provider id. /// </summary> /// <param name="instance">The instance.</param> @@ -121,6 +164,14 @@ public static class ProviderIdsExtensions return false; } + name = name.Trim(); + value = value.Trim(); + + if (!IsValidProviderId(name, value)) + { + return false; + } + // Ensure it exists instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); @@ -153,7 +204,6 @@ public static class ProviderIdsExtensions /// <param name="instance">The instance.</param> /// <param name="name">The name, this should not contain a '=' character.</param> /// <param name="value">The value.</param> - /// <remarks>Due to how deserialization from the database works the name cannot contain '='.</remarks> public static void SetProviderId(this IHasProviderIds instance, string name, string value) { ArgumentNullException.ThrowIfNull(instance); @@ -166,17 +216,27 @@ public static class ProviderIdsExtensions throw new ArgumentException("Provider id name cannot contain '='", nameof(name)); } - // Ensure it exists - instance.ProviderIds ??= new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + instance.TrySetProviderId(name, value); + } - // Match on internal MetadataProvider enum string values before adding arbitrary providers - if (_metadataProviderEnumDictionary.TryGetValue(name, out var enumValue)) + /// <summary> + /// Replaces all provider ids, dropping the ones that cannot belong to the provider they are filed under. + /// </summary> + /// <param name="instance">The instance.</param> + /// <param name="providerIds">The provider ids to set.</param> + public static void SetProviderIds(this IHasProviderIds instance, IReadOnlyDictionary<string, string>? providerIds) + { + ArgumentNullException.ThrowIfNull(instance); + + instance.ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + if (providerIds is null) { - instance.ProviderIds[enumValue] = value; + return; } - else + + foreach (var (name, value) in providerIds) { - instance.ProviderIds[name] = value; + instance.TrySetProviderId(name, value); } } @@ -213,4 +273,15 @@ public static class ProviderIdsExtensions instance.ProviderIds?.Remove(provider.ToString()); } + + private static bool IsPositiveNumber(string value) + => int.TryParse(value, NumberStyles.None, CultureInfo.InvariantCulture, out var id) && id > 0; + + private static bool IsGuid(string value) + => Guid.TryParse(value, CultureInfo.InvariantCulture, out _); + + // An IMDb id is a type prefix (tt for titles, nm for people, co for companies, ...) followed by + // digits. The prefix is optional because a bare number has always been accepted for a title. + [GeneratedRegex(@"^(tt|nm|co|ev|ch|ni)?[0-9]+$", RegexOptions.IgnoreCase)] + private static partial Regex ImdbIdRegex(); } |
