aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model/Entities
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Model/Entities')
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs273
-rw-r--r--MediaBrowser.Model/Entities/ProviderIdsExtensions.cs89
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();
}