diff options
| author | LogicalPhallacy <44458166+LogicalPhallacy@users.noreply.github.com> | 2019-10-28 21:54:40 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-10-28 21:54:40 -0700 |
| commit | 8edb1c49d8d1835566bd30d8bf5460ab707b1ede (patch) | |
| tree | 03c6b38523efcc4f29691cea3cdc4def0e8d26d8 /MediaBrowser.Controller | |
| parent | 984e415c66cbd995d12ea95a3a9d3e2561ce4869 (diff) | |
| parent | c9f4a74af02e08b895cd6a8b8a408b1c0edfb6c4 (diff) | |
Merge pull request #6 from jellyfin/master
Bringing my branch up to sync
Diffstat (limited to 'MediaBrowser.Controller')
61 files changed, 1201 insertions, 955 deletions
diff --git a/MediaBrowser.Controller/Authentication/AuthenticationException.cs b/MediaBrowser.Controller/Authentication/AuthenticationException.cs new file mode 100644 index 000000000..62eca3ea9 --- /dev/null +++ b/MediaBrowser.Controller/Authentication/AuthenticationException.cs @@ -0,0 +1,29 @@ +using System; + +namespace MediaBrowser.Controller.Authentication +{ + /// <summary> + /// The exception that is thrown when an attempt to authenticate fails. + /// </summary> + public class AuthenticationException : Exception + { + /// <inheritdoc /> + public AuthenticationException() : base() + { + + } + + /// <inheritdoc /> + public AuthenticationException(string message) : base(message) + { + + } + + /// <inheritdoc /> + public AuthenticationException(string message, Exception innerException) + : base(message, innerException) + { + + } + } +} diff --git a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs index 2cf531eed..f5571065f 100644 --- a/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs +++ b/MediaBrowser.Controller/Authentication/IAuthenticationProvider.cs @@ -9,10 +9,9 @@ namespace MediaBrowser.Controller.Authentication string Name { get; } bool IsEnabled { get; } Task<ProviderAuthenticationResult> Authenticate(string username, string password); - Task<bool> HasPassword(User user); + bool HasPassword(User user); Task ChangePassword(User user, string newPassword); void ChangeEasyPassword(User user, string newPassword, string newPasswordHash); - string GetPasswordHash(User user); string GetEasyPasswordHash(User user); } diff --git a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs index 9e5cd8816..2639960e7 100644 --- a/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs +++ b/MediaBrowser.Controller/Authentication/IPasswordResetProvider.cs @@ -12,6 +12,7 @@ namespace MediaBrowser.Controller.Authentication Task<ForgotPasswordResult> StartForgotPasswordProcess(User user, bool isInNetwork); Task<PinRedeemResult> RedeemPasswordResetPin(string pin); } + public class PasswordPinCreationResult { public string PinFile { get; set; } diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index adf03fb66..cdf2ca69e 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -1,10 +1,11 @@ using System; +using System.Globalization; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Channels { @@ -14,14 +15,14 @@ namespace MediaBrowser.Controller.Channels { if (user.Policy.BlockedChannels != null) { - if (user.Policy.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (user.Policy.BlockedChannels.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } } else { - if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (!user.Policy.EnableAllChannels && !user.Policy.EnabledChannels.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } @@ -30,10 +31,10 @@ namespace MediaBrowser.Controller.Channels return base.IsVisible(user); } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; - [IgnoreDataMember] + [JsonIgnore] public override SourceType SourceType => SourceType.Channel; protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query) @@ -60,7 +61,7 @@ namespace MediaBrowser.Controller.Channels public static string GetInternalMetadataPath(string basePath, Guid id) { - return System.IO.Path.Combine(basePath, "channels", id.ToString("N"), "metadata"); + return System.IO.Path.Combine(basePath, "channels", id.ToString("N", CultureInfo.InvariantCulture), "metadata"); } public override bool CanDelete() diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index 4eaecd0a0..a0f9ae46e 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -18,16 +18,6 @@ namespace MediaBrowser.Controller.Drawing IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; } /// <summary> - /// Encodes the image. - /// </summary> - string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat); - - /// <summary> - /// Creates the image collage. - /// </summary> - /// <param name="options">The options.</param> - void CreateImageCollage(ImageCollageOptions options); - /// <summary> /// Gets the name. /// </summary> /// <value>The name.</value> @@ -46,5 +36,16 @@ namespace MediaBrowser.Controller.Drawing bool SupportsImageEncoding { get; } ImageDimensions GetImageSize(string path); + + /// <summary> + /// Encodes the image. + /// </summary> + string EncodeImage(string inputPath, DateTime dateModified, string outputPath, bool autoOrient, ImageOrientation? orientation, int quality, ImageProcessingOptions options, ImageFormat outputFormat); + + /// <summary> + /// Creates the image collage. + /// </summary> + /// <param name="options">The options.</param> + void CreateImageCollage(ImageCollageOptions options); } } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index a11e2186f..a58a11bd1 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -24,7 +24,15 @@ namespace MediaBrowser.Controller.Drawing /// Gets the image enhancers. /// </summary> /// <value>The image enhancers.</value> - IImageEnhancer[] ImageEnhancers { get; } + IReadOnlyCollection<IImageEnhancer> ImageEnhancers { get; set; } + + /// <summary> + /// Gets a value indicating whether [supports image collage creation]. + /// </summary> + /// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value> + bool SupportsImageCollageCreation { get; } + + IImageEncoder ImageEncoder { get; set; } /// <summary> /// Gets the dimensions of the image. @@ -51,18 +59,12 @@ namespace MediaBrowser.Controller.Drawing ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem); /// <summary> - /// Adds the parts. - /// </summary> - /// <param name="enhancers">The enhancers.</param> - void AddParts(IEnumerable<IImageEnhancer> enhancers); - - /// <summary> /// Gets the supported enhancers. /// </summary> /// <param name="item">The item.</param> /// <param name="imageType">Type of the image.</param> /// <returns>IEnumerable{IImageEnhancer}.</returns> - IImageEnhancer[] GetSupportedEnhancers(BaseItem item, ImageType imageType); + IEnumerable<IImageEnhancer> GetSupportedEnhancers(BaseItem item, ImageType imageType); /// <summary> /// Gets the image cache tag. @@ -80,7 +82,7 @@ namespace MediaBrowser.Controller.Drawing /// <param name="image">The image.</param> /// <param name="imageEnhancers">The image enhancers.</param> /// <returns>Guid.</returns> - string GetImageCacheTag(BaseItem item, ItemImageInfo image, IImageEnhancer[] imageEnhancers); + string GetImageCacheTag(BaseItem item, ItemImageInfo image, IReadOnlyCollection<IImageEnhancer> imageEnhancers); /// <summary> /// Processes the image. @@ -109,7 +111,7 @@ namespace MediaBrowser.Controller.Drawing /// <summary> /// Gets the supported image output formats. /// </summary> - /// <returns>IReadOnlyCollection{ImageOutput}.</returns> + /// <returns><see cref="IReadOnlyCollection{ImageOutput}" />.</returns> IReadOnlyCollection<ImageFormat> GetSupportedImageOutputFormats(); /// <summary> @@ -118,14 +120,6 @@ namespace MediaBrowser.Controller.Drawing /// <param name="options">The options.</param> void CreateImageCollage(ImageCollageOptions options); - /// <summary> - /// Gets a value indicating whether [supports image collage creation]. - /// </summary> - /// <value><c>true</c> if [supports image collage creation]; otherwise, <c>false</c>.</value> - bool SupportsImageCollageCreation { get; } - - IImageEncoder ImageEncoder { get; set; } - bool SupportsTransparency(string path); } } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index db432f500..29addf6e6 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Controller.Entities; @@ -33,9 +34,9 @@ namespace MediaBrowser.Controller.Drawing public int Quality { get; set; } - public IImageEnhancer[] Enhancers { get; set; } + public IReadOnlyCollection<IImageEnhancer> Enhancers { get; set; } - public ImageFormat[] SupportedOutputFormats { get; set; } + public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; } public bool AddPlayedIndicator { get; set; } diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 4b6fd58fe..ba693a065 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -57,7 +57,7 @@ namespace MediaBrowser.Controller.Dto /// <param name="options">The options.</param> /// <param name="user">The user.</param> /// <param name="owner">The owner.</param> - BaseItemDto[] GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null); + IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null); /// <summary> /// Gets the item by name dto. diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs index 054df21e5..cacda8140 100644 --- a/MediaBrowser.Controller/Entities/AggregateFolder.cs +++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs @@ -2,13 +2,13 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { @@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.Entities PhysicalLocationsList = Array.Empty<string>(); } - [IgnoreDataMember] + [JsonIgnore] public override bool IsPhysicalRoot => true; public override bool CanDelete() @@ -31,7 +31,7 @@ namespace MediaBrowser.Controller.Entities return false; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => false; /// <summary> @@ -45,7 +45,7 @@ namespace MediaBrowser.Controller.Entities /// <value>The virtual children.</value> public ConcurrentBag<BaseItem> VirtualChildren => _virtualChildren; - [IgnoreDataMember] + [JsonIgnore] public override string[] PhysicalLocations => PhysicalLocationsList; public string[] PhysicalLocationsList { get; set; } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 13a6fe44a..a700d0be4 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities.Audio { @@ -19,15 +20,13 @@ namespace MediaBrowser.Controller.Entities.Audio IHasLookupInfo<SongInfo>, IHasMediaSources { - /// <summary> - /// Gets or sets the artist. - /// </summary> - /// <value>The artist.</value> - [IgnoreDataMember] - public string[] Artists { get; set; } + /// <inheritdoc /> + [JsonIgnore] + public IReadOnlyList<string> Artists { get; set; } - [IgnoreDataMember] - public string[] AlbumArtists { get; set; } + /// <inheritdoc /> + [JsonIgnore] + public IReadOnlyList<string> AlbumArtists { get; set; } public Audio() { @@ -40,22 +39,22 @@ namespace MediaBrowser.Controller.Entities.Audio return 1; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAddingToPlaylist => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => true; - [IgnoreDataMember] + [JsonIgnore] protected override bool SupportsOwnedItems => false; - [IgnoreDataMember] + [JsonIgnore] public override Folder LatestItemsIndexContainer => AlbumEntity; public override bool CanDownload() @@ -63,38 +62,14 @@ namespace MediaBrowser.Controller.Entities.Audio return IsFileProtocol; } - [IgnoreDataMember] - public string[] AllArtists - { - get - { - var list = new string[AlbumArtists.Length + Artists.Length]; - - var index = 0; - foreach (var artist in AlbumArtists) - { - list[index] = artist; - index++; - } - foreach (var artist in Artists) - { - list[index] = artist; - index++; - } - - return list; - - } - } - - [IgnoreDataMember] + [JsonIgnore] public MusicAlbum AlbumEntity => FindParent<MusicAlbum>(); /// <summary> /// Gets the type of the media. /// </summary> /// <value>The type of the media.</value> - [IgnoreDataMember] + [JsonIgnore] public override string MediaType => Model.Entities.MediaType.Audio; /// <summary> @@ -125,7 +100,7 @@ namespace MediaBrowser.Controller.Entities.Audio songKey = Album + "-" + songKey; } - var albumArtist = AlbumArtists.Length == 0 ? null : AlbumArtists[0]; + var albumArtist = AlbumArtists.FirstOrDefault(); if (!string.IsNullOrEmpty(albumArtist)) { songKey = albumArtist + "-" + songKey; diff --git a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs index a269b3486..056f31f78 100644 --- a/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/IHasAlbumArtist.cs @@ -1,14 +1,35 @@ +using System.Collections.Generic; + namespace MediaBrowser.Controller.Entities.Audio { public interface IHasAlbumArtist { - string[] AlbumArtists { get; set; } + IReadOnlyList<string> AlbumArtists { get; set; } } public interface IHasArtist { - string[] AllArtists { get; } + /// <summary> + /// Gets or sets the artists. + /// </summary> + /// <value>The artists.</value> + IReadOnlyList<string> Artists { get; set; } + } + + public static class Extentions + { + public static IEnumerable<string> GetAllArtists<T>(this T item) + where T : IHasArtist, IHasAlbumArtist + { + foreach (var i in item.AlbumArtists) + { + yield return i; + } - string[] Artists { get; set; } + foreach (var i in item.Artists) + { + yield return i; + } + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 5b2939b75..c216176e7 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Dto; @@ -8,7 +9,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities.Audio @@ -18,8 +18,11 @@ namespace MediaBrowser.Controller.Entities.Audio /// </summary> public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<AlbumInfo>, IMetadataContainer { - public string[] AlbumArtists { get; set; } - public string[] Artists { get; set; } + /// <inheritdoc /> + public IReadOnlyList<string> AlbumArtists { get; set; } + + /// <inheritdoc /> + public IReadOnlyList<string> Artists { get; set; } public MusicAlbum() { @@ -27,13 +30,13 @@ namespace MediaBrowser.Controller.Entities.Audio AlbumArtists = Array.Empty<string>(); } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAddingToPlaylist => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => true; - [IgnoreDataMember] + [JsonIgnore] public MusicArtist MusicArtist => GetMusicArtist(new DtoOptions(true)); public MusicArtist GetMusicArtist(DtoOptions options) @@ -41,8 +44,7 @@ namespace MediaBrowser.Controller.Entities.Audio var parents = GetParents(); foreach (var parent in parents) { - var artist = parent as MusicArtist; - if (artist != null) + if (parent is MusicArtist artist) { return artist; } @@ -56,46 +58,23 @@ namespace MediaBrowser.Controller.Entities.Audio return null; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsCumulativeRunTimeTicks => true; - [IgnoreDataMember] - public string[] AllArtists - { - get - { - var list = new string[AlbumArtists.Length + Artists.Length]; - - var index = 0; - foreach (var artist in AlbumArtists) - { - list[index] = artist; - index++; - } - foreach (var artist in Artists) - { - list[index] = artist; - index++; - } - - return list; - } - } - - [IgnoreDataMember] - public string AlbumArtist => AlbumArtists.Length == 0 ? null : AlbumArtists[0]; + [JsonIgnore] + public string AlbumArtist => AlbumArtists.FirstOrDefault(); - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; /// <summary> /// Gets the tracks. /// </summary> /// <value>The tracks.</value> - [IgnoreDataMember] + [JsonIgnore] public IEnumerable<Audio> Tracks => GetRecursiveChildren(i => i is Audio).Cast<Audio>(); protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) @@ -216,8 +195,7 @@ namespace MediaBrowser.Controller.Entities.Audio private async Task RefreshArtists(MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken) { - var all = AllArtists; - foreach (var i in all) + foreach (var i in this.GetAllArtists()) { // This should not be necessary but we're seeing some cases of it if (string.IsNullOrEmpty(i)) diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 2d464bd32..efe0d3cf7 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; @@ -18,25 +18,25 @@ namespace MediaBrowser.Controller.Entities.Audio /// </summary> public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo<ArtistInfo> { - [IgnoreDataMember] + [JsonIgnore] public bool IsAccessedByName => ParentId.Equals(Guid.Empty); - [IgnoreDataMember] + [JsonIgnore] public override bool IsFolder => !IsAccessedByName; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsCumulativeRunTimeTicks => true; - [IgnoreDataMember] + [JsonIgnore] public override bool IsDisplayedAsFolder => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAddingToPlaylist => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => false; public override double GetDefaultPrimaryImageAspectRatio() @@ -60,7 +60,7 @@ namespace MediaBrowser.Controller.Entities.Audio return LibraryManager.GetItemList(query); } - [IgnoreDataMember] + [JsonIgnore] public override IEnumerable<BaseItem> Children { get @@ -117,7 +117,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// If the item is a folder, it returns the folder itself /// </summary> /// <value>The containing folder path.</value> - [IgnoreDataMember] + [JsonIgnore] public override string ContainingFolderPath => Path; /// <summary> @@ -164,7 +164,7 @@ namespace MediaBrowser.Controller.Entities.Audio return info; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; public static string GetPath(string name) diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index d26aaf2bb..537e9630b 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Extensions; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities.Audio @@ -23,13 +23,13 @@ namespace MediaBrowser.Controller.Entities.Audio return GetUserDataKeys()[0]; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAddingToPlaylist => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAncestors => false; - [IgnoreDataMember] + [JsonIgnore] public override bool IsDisplayedAsFolder => true; /// <summary> @@ -37,7 +37,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// If the item is a folder, it returns the folder itself /// </summary> /// <value>The containing folder path.</value> - [IgnoreDataMember] + [JsonIgnore] public override string ContainingFolderPath => Path; public override double GetDefaultPrimaryImageAspectRatio() @@ -55,7 +55,7 @@ namespace MediaBrowser.Controller.Entities.Audio return true; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; public IList<BaseItem> GetTaggedItems(InternalItemsQuery query) diff --git a/MediaBrowser.Controller/Entities/AudioBook.cs b/MediaBrowser.Controller/Entities/AudioBook.cs index 65c8a5fdd..a13873bf9 100644 --- a/MediaBrowser.Controller/Entities/AudioBook.cs +++ b/MediaBrowser.Controller/Entities/AudioBook.cs @@ -1,23 +1,23 @@ using System; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { public class AudioBook : Audio.Audio, IHasSeries, IHasLookupInfo<SongInfo> { - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPositionTicksResume => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => true; - [IgnoreDataMember] + [JsonIgnore] public string SeriesPresentationUniqueKey { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string SeriesName { get; set; } - [IgnoreDataMember] + [JsonIgnore] public Guid SeriesId { get; set; } public string FindSeriesSortName() diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 10a603e42..599d41bb2 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Extensions; @@ -25,7 +26,6 @@ using MediaBrowser.Model.Library; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; using Microsoft.Extensions.Logging; @@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Entities /// The supported image extensions /// </summary> public static readonly string[] SupportedImageExtensions - = new [] { ".png", ".jpg", ".jpeg", ".tbn", ".gif" }; + = new[] { ".png", ".jpg", ".jpeg", ".tbn", ".gif" }; private static readonly List<string> _supportedExtensions = new List<string>(SupportedImageExtensions) { @@ -98,62 +98,62 @@ namespace MediaBrowser.Controller.Entities SampleFolderName }; - [IgnoreDataMember] + [JsonIgnore] public Guid[] ThemeSongIds { get; set; } - [IgnoreDataMember] + [JsonIgnore] public Guid[] ThemeVideoIds { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string PreferredMetadataCountryCode { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string PreferredMetadataLanguage { get; set; } public long? Size { get; set; } public string Container { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string Tagline { get; set; } - [IgnoreDataMember] + [JsonIgnore] public virtual ItemImageInfo[] ImageInfos { get; set; } - [IgnoreDataMember] + [JsonIgnore] public bool IsVirtualItem { get; set; } /// <summary> /// Gets or sets the album. /// </summary> /// <value>The album.</value> - [IgnoreDataMember] + [JsonIgnore] public string Album { get; set; } /// <summary> /// Gets or sets the channel identifier. /// </summary> /// <value>The channel identifier.</value> - [IgnoreDataMember] + [JsonIgnore] public Guid ChannelId { get; set; } - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsAddingToPlaylist => false; - [IgnoreDataMember] + [JsonIgnore] public virtual bool AlwaysScanInternalMetadataPath => false; /// <summary> /// Gets a value indicating whether this instance is in mixed folder. /// </summary> /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsInMixedFolder { get; set; } - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsPlayedStatus => false; - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsPositionTicksResume => false; - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsRemoteImageDownloading => true; private string _name; @@ -161,7 +161,7 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the name. /// </summary> /// <value>The name.</value> - [IgnoreDataMember] + [JsonIgnore] public virtual string Name { get => _name; @@ -174,35 +174,35 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public bool IsUnaired => PremiereDate.HasValue && PremiereDate.Value.ToLocalTime().Date >= DateTime.Now.Date; - [IgnoreDataMember] + [JsonIgnore] public int? TotalBitrate { get; set; } - [IgnoreDataMember] + [JsonIgnore] public ExtraType? ExtraType { get; set; } - [IgnoreDataMember] + [JsonIgnore] public bool IsThemeMedia => ExtraType.HasValue && (ExtraType.Value == Model.Entities.ExtraType.ThemeSong || ExtraType.Value == Model.Entities.ExtraType.ThemeVideo); - [IgnoreDataMember] + [JsonIgnore] public string OriginalTitle { get; set; } /// <summary> /// Gets or sets the id. /// </summary> /// <value>The id.</value> - [IgnoreDataMember] + [JsonIgnore] public Guid Id { get; set; } - [IgnoreDataMember] + [JsonIgnore] public Guid OwnerId { get; set; } /// <summary> /// Gets or sets the audio. /// </summary> /// <value>The audio.</value> - [IgnoreDataMember] + [JsonIgnore] public ProgramAudio? Audio { get; set; } /// <summary> @@ -210,7 +210,7 @@ namespace MediaBrowser.Controller.Entities /// Default is based on the type for everything except actual generic folders. /// </summary> /// <value>The display prefs id.</value> - [IgnoreDataMember] + [JsonIgnore] public virtual Guid DisplayPreferencesId { get @@ -224,10 +224,10 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the path. /// </summary> /// <value>The path.</value> - [IgnoreDataMember] + [JsonIgnore] public virtual string Path { get; set; } - [IgnoreDataMember] + [JsonIgnore] public virtual SourceType SourceType { get @@ -245,7 +245,7 @@ namespace MediaBrowser.Controller.Entities /// Returns the folder containing the item. /// If the item is a folder, it returns the folder itself /// </summary> - [IgnoreDataMember] + [JsonIgnore] public virtual string ContainingFolderPath { get @@ -263,26 +263,26 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the name of the service. /// </summary> /// <value>The name of the service.</value> - [IgnoreDataMember] + [JsonIgnore] public string ServiceName { get; set; } /// <summary> /// If this content came from an external service, the id of the content on that service /// </summary> - [IgnoreDataMember] + [JsonIgnore] public string ExternalId { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string ExternalSeriesId { get; set; } /// <summary> /// Gets or sets the etag. /// </summary> /// <value>The etag.</value> - [IgnoreDataMember] + [JsonIgnore] public string ExternalEtag { get; set; } - [IgnoreDataMember] + [JsonIgnore] public virtual bool IsHidden => false; public BaseItem GetOwner() @@ -295,7 +295,7 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the type of the location. /// </summary> /// <value>The type of the location.</value> - [IgnoreDataMember] + [JsonIgnore] public virtual LocationType LocationType { get @@ -320,7 +320,7 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public MediaProtocol? PathProtocol { get @@ -343,13 +343,13 @@ namespace MediaBrowser.Controller.Entities return current.HasValue && current.Value == protocol; } - [IgnoreDataMember] + [JsonIgnore] public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File); - [IgnoreDataMember] + [JsonIgnore] public bool HasPathProtocol => PathProtocol.HasValue; - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsLocalMetadata { get @@ -363,7 +363,7 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public virtual string FileNameWithoutExtension { get @@ -377,7 +377,7 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public virtual bool EnableAlphaNumericSorting => true; private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1) @@ -418,7 +418,7 @@ namespace MediaBrowser.Controller.Entities /// This is just a helper for convenience /// </summary> /// <value>The primary image path.</value> - [IgnoreDataMember] + [JsonIgnore] public string PrimaryImagePath => this.GetImagePath(ImageType.Primary); public bool IsMetadataFetcherEnabled(LibraryOptions libraryOptions, string name) @@ -503,7 +503,7 @@ namespace MediaBrowser.Controller.Entities foreach (var folder in collectionFolders) { - if (allowed.Contains(folder.Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (allowed.Contains(folder.Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return true; } @@ -544,20 +544,20 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the date created. /// </summary> /// <value>The date created.</value> - [IgnoreDataMember] + [JsonIgnore] public DateTime DateCreated { get; set; } /// <summary> /// Gets or sets the date modified. /// </summary> /// <value>The date modified.</value> - [IgnoreDataMember] + [JsonIgnore] public DateTime DateModified { get; set; } - [IgnoreDataMember] + [JsonIgnore] public DateTime DateLastSaved { get; set; } - [IgnoreDataMember] + [JsonIgnore] public DateTime DateLastRefreshed { get; set; } /// <summary> @@ -583,24 +583,24 @@ namespace MediaBrowser.Controller.Entities return Name; } - [IgnoreDataMember] + [JsonIgnore] public bool IsLocked { get; set; } /// <summary> /// Gets or sets the locked fields. /// </summary> /// <value>The locked fields.</value> - [IgnoreDataMember] + [JsonIgnore] public MetadataFields[] LockedFields { get; set; } /// <summary> /// Gets the type of the media. /// </summary> /// <value>The type of the media.</value> - [IgnoreDataMember] + [JsonIgnore] public virtual string MediaType => null; - [IgnoreDataMember] + [JsonIgnore] public virtual string[] PhysicalLocations { get @@ -619,7 +619,7 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the name of the forced sort. /// </summary> /// <value>The name of the forced sort.</value> - [IgnoreDataMember] + [JsonIgnore] public string ForcedSortName { get => _forcedSortName; @@ -631,7 +631,7 @@ namespace MediaBrowser.Controller.Entities /// Gets the name of the sort. /// </summary> /// <value>The name of the sort.</value> - [IgnoreDataMember] + [JsonIgnore] public string SortName { get @@ -664,10 +664,10 @@ namespace MediaBrowser.Controller.Entities { if (SourceType == SourceType.Channel) { - return System.IO.Path.Combine(basePath, "channels", ChannelId.ToString("N"), Id.ToString("N")); + return System.IO.Path.Combine(basePath, "channels", ChannelId.ToString("N", CultureInfo.InvariantCulture), Id.ToString("N", CultureInfo.InvariantCulture)); } - var idString = Id.ToString("N"); + var idString = Id.ToString("N", CultureInfo.InvariantCulture); basePath = System.IO.Path.Combine(basePath, "library"); @@ -744,7 +744,7 @@ namespace MediaBrowser.Controller.Entities return builder.ToString().RemoveDiacritics(); } - [IgnoreDataMember] + [JsonIgnore] public bool EnableMediaSourceDisplay { get @@ -758,14 +758,14 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public Guid ParentId { get; set; } /// <summary> /// Gets or sets the parent. /// </summary> /// <value>The parent.</value> - [IgnoreDataMember] + [JsonIgnore] public Folder Parent { get => GetParent() as Folder; @@ -822,7 +822,7 @@ namespace MediaBrowser.Controller.Entities return null; } - [IgnoreDataMember] + [JsonIgnore] public virtual Guid DisplayParentId { get @@ -832,7 +832,7 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public BaseItem DisplayParent { get @@ -850,97 +850,97 @@ namespace MediaBrowser.Controller.Entities /// When the item first debuted. For movies this could be premiere date, episodes would be first aired /// </summary> /// <value>The premiere date.</value> - [IgnoreDataMember] + [JsonIgnore] public DateTime? PremiereDate { get; set; } /// <summary> /// Gets or sets the end date. /// </summary> /// <value>The end date.</value> - [IgnoreDataMember] + [JsonIgnore] public DateTime? EndDate { get; set; } /// <summary> /// Gets or sets the official rating. /// </summary> /// <value>The official rating.</value> - [IgnoreDataMember] + [JsonIgnore] public string OfficialRating { get; set; } - [IgnoreDataMember] + [JsonIgnore] public int InheritedParentalRatingValue { get; set; } /// <summary> /// Gets or sets the critic rating. /// </summary> /// <value>The critic rating.</value> - [IgnoreDataMember] + [JsonIgnore] public float? CriticRating { get; set; } /// <summary> /// Gets or sets the custom rating. /// </summary> /// <value>The custom rating.</value> - [IgnoreDataMember] + [JsonIgnore] public string CustomRating { get; set; } /// <summary> /// Gets or sets the overview. /// </summary> /// <value>The overview.</value> - [IgnoreDataMember] + [JsonIgnore] public string Overview { get; set; } /// <summary> /// Gets or sets the studios. /// </summary> /// <value>The studios.</value> - [IgnoreDataMember] + [JsonIgnore] public string[] Studios { get; set; } /// <summary> /// Gets or sets the genres. /// </summary> /// <value>The genres.</value> - [IgnoreDataMember] + [JsonIgnore] public string[] Genres { get; set; } /// <summary> /// Gets or sets the tags. /// </summary> /// <value>The tags.</value> - [IgnoreDataMember] + [JsonIgnore] public string[] Tags { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string[] ProductionLocations { get; set; } /// <summary> /// Gets or sets the home page URL. /// </summary> /// <value>The home page URL.</value> - [IgnoreDataMember] + [JsonIgnore] public string HomePageUrl { get; set; } /// <summary> /// Gets or sets the community rating. /// </summary> /// <value>The community rating.</value> - [IgnoreDataMember] + [JsonIgnore] public float? CommunityRating { get; set; } /// <summary> /// Gets or sets the run time ticks. /// </summary> /// <value>The run time ticks.</value> - [IgnoreDataMember] + [JsonIgnore] public long? RunTimeTicks { get; set; } /// <summary> /// Gets or sets the production year. /// </summary> /// <value>The production year.</value> - [IgnoreDataMember] + [JsonIgnore] public int? ProductionYear { get; set; } /// <summary> @@ -948,20 +948,20 @@ namespace MediaBrowser.Controller.Entities /// This could be episode number, album track number, etc. /// </summary> /// <value>The index number.</value> - [IgnoreDataMember] + [JsonIgnore] public int? IndexNumber { get; set; } /// <summary> /// For an episode this could be the season number, or for a song this could be the disc number. /// </summary> /// <value>The parent index number.</value> - [IgnoreDataMember] + [JsonIgnore] public int? ParentIndexNumber { get; set; } - [IgnoreDataMember] + [JsonIgnore] public virtual bool HasLocalAlternateVersions => false; - [IgnoreDataMember] + [JsonIgnore] public string OfficialRatingForComparison { get @@ -982,7 +982,7 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public string CustomRatingForComparison { get @@ -1095,7 +1095,7 @@ namespace MediaBrowser.Controller.Entities var info = new MediaSourceInfo { - Id = item.Id.ToString("N"), + Id = item.Id.ToString("N", CultureInfo.InvariantCulture), Protocol = protocol ?? MediaProtocol.File, MediaStreams = MediaSourceManager.GetMediaStreams(item.Id), Name = GetMediaSourceName(item), @@ -1113,7 +1113,7 @@ namespace MediaBrowser.Controller.Entities if (info.Protocol == MediaProtocol.File) { - info.ETag = item.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N"); + info.ETag = item.DateModified.Ticks.ToString(CultureInfo.InvariantCulture).GetMD5().ToString("N", CultureInfo.InvariantCulture); } var video = item as Video; @@ -1407,13 +1407,13 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] protected virtual bool SupportsOwnedItems => !ParentId.Equals(Guid.Empty) && IsFileProtocol; - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsPeople => false; - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsThemeMedia => false; /// <summary> @@ -1613,10 +1613,10 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the provider ids. /// </summary> /// <value>The provider ids.</value> - [IgnoreDataMember] + [JsonIgnore] public Dictionary<string, string> ProviderIds { get; set; } - [IgnoreDataMember] + [JsonIgnore] public virtual Folder LatestItemsIndexContainer => null; public virtual double GetDefaultPrimaryImageAspectRatio() @@ -1626,10 +1626,10 @@ namespace MediaBrowser.Controller.Entities public virtual string CreatePresentationUniqueKey() { - return Id.ToString("N"); + return Id.ToString("N", CultureInfo.InvariantCulture); } - [IgnoreDataMember] + [JsonIgnore] public string PresentationUniqueKey { get; set; } public string GetPresentationUniqueKey() @@ -1934,7 +1934,7 @@ namespace MediaBrowser.Controller.Entities return IsVisibleStandaloneInternal(user, true); } - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsInheritedParentImages => false; protected bool IsVisibleStandaloneInternal(User user, bool checkFolders) @@ -1977,10 +1977,10 @@ namespace MediaBrowser.Controller.Entities /// Gets a value indicating whether this instance is folder. /// </summary> /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public virtual bool IsFolder => false; - [IgnoreDataMember] + [JsonIgnore] public virtual bool IsDisplayedAsFolder => false; public virtual string GetClientTypeName() @@ -2045,7 +2045,7 @@ namespace MediaBrowser.Controller.Entities if (itemByPath == null) { - //Logger.LogWarning("Unable to find linked item at path {0}", info.Path); + Logger.LogWarning("Unable to find linked item at path {0}", info.Path); } return itemByPath; @@ -2057,7 +2057,7 @@ namespace MediaBrowser.Controller.Entities if (item == null) { - //Logger.LogWarning("Unable to find linked item at path {0}", info.Path); + Logger.LogWarning("Unable to find linked item at path {0}", info.Path); } return item; @@ -2066,7 +2066,7 @@ namespace MediaBrowser.Controller.Entities return null; } - [IgnoreDataMember] + [JsonIgnore] public virtual bool EnableRememberingTrackSelections => true; /// <summary> @@ -2085,14 +2085,17 @@ namespace MediaBrowser.Controller.Entities if (!current.Contains(name, StringComparer.OrdinalIgnoreCase)) { - if (current.Length == 0) + int curLen = current.Length; + if (curLen == 0) { Studios = new[] { name }; } else { - var list = - Studios = current.Concat(new[] { name }).ToArray(); + var newArr = new string[curLen + 1]; + current.CopyTo(newArr, 0); + newArr[curLen] = name; + Studios = newArr; } } } @@ -2231,8 +2234,12 @@ namespace MediaBrowser.Controller.Entities else { - var currentCount = ImageInfos.Length; - ImageInfos = ImageInfos.Concat(new[] { image }).ToArray(); + var current = ImageInfos; + var currentCount = current.Length; + var newArr = new ItemImageInfo[currentCount + 1]; + current.CopyTo(newArr, 0); + newArr[currentCount] = image; + ImageInfos = newArr; } } @@ -2736,7 +2743,7 @@ namespace MediaBrowser.Controller.Entities { var list = GetEtagValues(user); - return string.Join("|", list.ToArray()).GetMD5().ToString("N"); + return string.Join("|", list.ToArray()).GetMD5().ToString("N", CultureInfo.InvariantCulture); } protected virtual List<string> GetEtagValues(User user) @@ -2769,7 +2776,7 @@ namespace MediaBrowser.Controller.Entities return null; } - [IgnoreDataMember] + [JsonIgnore] public virtual bool IsTopParent { get @@ -2797,10 +2804,10 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsAncestors => true; - [IgnoreDataMember] + [JsonIgnore] public virtual bool StopRefreshIfLocalMetadataFound => true; public virtual IEnumerable<Guid> GetIdsForAncestorQuery() @@ -2871,16 +2878,24 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the remote trailers. /// </summary> /// <value>The remote trailers.</value> - public MediaUrl[] RemoteTrailers { get; set; } + public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; } public IEnumerable<BaseItem> GetExtras() { return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName); } - public IEnumerable<BaseItem> GetExtras(ExtraType[] extraTypes) + public IEnumerable<BaseItem> GetExtras(IReadOnlyCollection<ExtraType> extraTypes) { - return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null && extraTypes.Contains(i.ExtraType.Value)).OrderBy(i => i.SortName); + return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null && extraTypes.Contains(i.ExtraType.Value)); + } + + public IEnumerable<BaseItem> GetTrailers() + { + if (this is IHasTrailers) + return ((IHasTrailers)this).LocalTrailerIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName); + else + return Array.Empty<BaseItem>(); } public IEnumerable<BaseItem> GetDisplayExtras() @@ -2900,7 +2915,7 @@ namespace MediaBrowser.Controller.Entities } // Possible types of extra videos - public static ExtraType[] DisplayExtraTypes = new[] { Model.Entities.ExtraType.BehindTheScenes, Model.Entities.ExtraType.Clip, Model.Entities.ExtraType.DeletedScene, Model.Entities.ExtraType.Interview, Model.Entities.ExtraType.Sample, Model.Entities.ExtraType.Scene }; + public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new[] { Model.Entities.ExtraType.BehindTheScenes, Model.Entities.ExtraType.Clip, Model.Entities.ExtraType.DeletedScene, Model.Entities.ExtraType.Interview, Model.Entities.ExtraType.Sample, Model.Entities.ExtraType.Scene }; public virtual bool SupportsExternalTransfer => false; } diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index 8cdb9695c..62d172fcc 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -1,4 +1,4 @@ -using MediaBrowser.Model.Serialization; +using System.Text.Json.Serialization; namespace MediaBrowser.Controller.Entities { @@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> public abstract class BasePluginFolder : Folder, ICollectionFolder { - [IgnoreDataMember] + [JsonIgnore] public virtual string CollectionType => null; public override bool CanDelete() @@ -21,10 +21,10 @@ namespace MediaBrowser.Controller.Entities return true; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; //public override double? GetDefaultPrimaryImageAspectRatio() diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 7a23d9a66..44c35374d 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -1,21 +1,21 @@ using System; using System.Linq; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { public class Book : BaseItem, IHasLookupInfo<BookInfo>, IHasSeries { - [IgnoreDataMember] + [JsonIgnore] public override string MediaType => Model.Entities.MediaType.Book; - [IgnoreDataMember] + [JsonIgnore] public string SeriesPresentationUniqueKey { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string SeriesName { get; set; } - [IgnoreDataMember] + [JsonIgnore] public Guid SeriesId { get; set; } public string FindSeriesSortName() diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 275052d48..bc5e7467e 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -2,14 +2,13 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; - using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; @@ -33,10 +32,10 @@ namespace MediaBrowser.Controller.Entities PhysicalFolderIds = Array.Empty<Guid>(); } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; public override bool CanDelete() @@ -144,10 +143,10 @@ namespace MediaBrowser.Controller.Entities /// Allow different display preferences for each collection folder /// </summary> /// <value>The display prefs id.</value> - [IgnoreDataMember] + [JsonIgnore] public override Guid DisplayPreferencesId => Id; - [IgnoreDataMember] + [JsonIgnore] public override string[] PhysicalLocations => PhysicalLocationsList; public override bool IsSaveLocalMetadataEnabled() @@ -311,7 +310,7 @@ namespace MediaBrowser.Controller.Entities /// Our children are actually just references to the ones in the physical root... /// </summary> /// <value>The actual children.</value> - [IgnoreDataMember] + [JsonIgnore] public override IEnumerable<BaseItem> Children => GetActualChildren(); public IEnumerable<BaseItem> GetActualChildren() @@ -361,7 +360,7 @@ namespace MediaBrowser.Controller.Entities return result; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; } } diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs index f3bddd29c..d2ca11740 100644 --- a/MediaBrowser.Controller/Entities/Extensions.cs +++ b/MediaBrowser.Controller/Entities/Extensions.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using MediaBrowser.Common.Extensions; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Entities @@ -28,13 +29,17 @@ namespace MediaBrowser.Controller.Entities Url = url }; - if (item.RemoteTrailers.Length == 0) + if (item.RemoteTrailers.Count == 0) { item.RemoteTrailers = new[] { mediaUrl }; } else { - item.RemoteTrailers = item.RemoteTrailers.Concat(new[] { mediaUrl }).ToArray(); + var oldIds = item.RemoteTrailers; + var newIds = new MediaUrl[oldIds.Count + 1]; + oldIds.CopyTo(newIds); + newIds[oldIds.Count] = mediaUrl; + item.RemoteTrailers = newIds; } } } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index c056bc0b4..61cc208d7 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Progress; @@ -17,7 +19,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.IO; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities @@ -38,7 +39,7 @@ namespace MediaBrowser.Controller.Entities public LinkedChild[] LinkedChildren { get; set; } - [IgnoreDataMember] + [JsonIgnore] public DateTime? DateLastMediaAdded { get; set; } public Folder() @@ -46,35 +47,35 @@ namespace MediaBrowser.Controller.Entities LinkedChildren = Array.Empty<LinkedChild>(); } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsThemeMedia => true; - [IgnoreDataMember] + [JsonIgnore] public virtual bool IsPreSorted => false; - [IgnoreDataMember] + [JsonIgnore] public virtual bool IsPhysicalRoot => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => true; /// <summary> /// Gets a value indicating whether this instance is folder. /// </summary> /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public override bool IsFolder => true; - [IgnoreDataMember] + [JsonIgnore] public override bool IsDisplayedAsFolder => true; - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsCumulativeRunTimeTicks => false; - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsDateLastMediaAdded => false; public override bool CanDelete() @@ -99,7 +100,7 @@ namespace MediaBrowser.Controller.Entities return baseResult; } - [IgnoreDataMember] + [JsonIgnore] public override string FileNameWithoutExtension { get @@ -126,7 +127,7 @@ namespace MediaBrowser.Controller.Entities return true; } - [IgnoreDataMember] + [JsonIgnore] protected virtual bool SupportsShortcutChildren => false; /// <summary> @@ -161,14 +162,14 @@ namespace MediaBrowser.Controller.Entities /// Gets the actual children. /// </summary> /// <value>The actual children.</value> - [IgnoreDataMember] + [JsonIgnore] public virtual IEnumerable<BaseItem> Children => LoadChildren(); /// <summary> /// thread-safe access to all recursive children of this folder - without regard to user /// </summary> /// <value>The recursive children.</value> - [IgnoreDataMember] + [JsonIgnore] public IEnumerable<BaseItem> RecursiveChildren => GetRecursiveChildren(); public override bool IsVisible(User user) @@ -177,7 +178,7 @@ namespace MediaBrowser.Controller.Entities { if (user.Policy.BlockedMediaFolders != null) { - if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) || + if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase) || // Backwards compatibility user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) @@ -187,7 +188,7 @@ namespace MediaBrowser.Controller.Entities } else { - if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N", CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { return false; } @@ -665,36 +666,36 @@ namespace MediaBrowser.Controller.Entities query.StartIndex = null; query.Limit = null; - var itemsList = LibraryManager.GetItemList(query); + IEnumerable<BaseItem> itemsList = LibraryManager.GetItemList(query); var user = query.User; if (user != null) { // needed for boxsets - itemsList = itemsList.Where(i => i.IsVisibleStandalone(query.User)).ToList(); + itemsList = itemsList.Where(i => i.IsVisibleStandalone(query.User)); } - BaseItem[] returnItems; + IEnumerable<BaseItem> returnItems; int totalCount = 0; if (query.EnableTotalRecordCount) { - var itemsArray = itemsList.ToArray(); - totalCount = itemsArray.Length; - returnItems = itemsArray; + var itemArray = itemsList.ToArray(); + totalCount = itemArray.Length; + returnItems = itemArray; } else { - returnItems = itemsList.ToArray(); + returnItems = itemsList; } if (limit.HasValue) { - returnItems = returnItems.Skip(startIndex ?? 0).Take(limit.Value).ToArray(); + returnItems = returnItems.Skip(startIndex ?? 0).Take(limit.Value); } else if (startIndex.HasValue) { - returnItems = returnItems.Skip(startIndex.Value).ToArray(); + returnItems = returnItems.Skip(startIndex.Value); } return new QueryResult<BaseItem> @@ -1427,7 +1428,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => i.Item2 != null); } - [IgnoreDataMember] + [JsonIgnore] protected override bool SupportsOwnedItems => base.SupportsOwnedItems || SupportsShortcutChildren; protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) @@ -1594,7 +1595,7 @@ namespace MediaBrowser.Controller.Entities return !IsPlayed(user); } - [IgnoreDataMember] + [JsonIgnore] public virtual bool SupportsUserDataFromChildren { get diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index 44cb62d22..773c7df34 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Extensions; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities @@ -34,13 +34,13 @@ namespace MediaBrowser.Controller.Entities /// If the item is a folder, it returns the folder itself /// </summary> /// <value>The containing folder path.</value> - [IgnoreDataMember] + [JsonIgnore] public override string ContainingFolderPath => Path; - [IgnoreDataMember] + [JsonIgnore] public override bool IsDisplayedAsFolder => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAncestors => false; public override bool IsSaveLocalMetadataEnabled() @@ -61,7 +61,7 @@ namespace MediaBrowser.Controller.Entities return LibraryManager.GetItemList(query); } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; public static string GetPath(string name) diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs index 3bdb9b64a..dd8e3c45f 100644 --- a/MediaBrowser.Controller/Entities/IHasTrailers.cs +++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using MediaBrowser.Model.Entities; namespace MediaBrowser.Controller.Entities @@ -11,29 +10,82 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the remote trailers. /// </summary> /// <value>The remote trailers.</value> - MediaUrl[] RemoteTrailers { get; set; } + IReadOnlyList<MediaUrl> RemoteTrailers { get; set; } /// <summary> /// Gets or sets the local trailer ids. /// </summary> /// <value>The local trailer ids.</value> - Guid[] LocalTrailerIds { get; set; } - Guid[] RemoteTrailerIds { get; set; } + IReadOnlyList<Guid> LocalTrailerIds { get; set; } + + /// <summary> + /// Gets or sets the remote trailer ids. + /// </summary> + /// <value>The remote trailer ids.</value> + IReadOnlyList<Guid> RemoteTrailerIds { get; set; } + Guid Id { get; set; } } + /// <summary> + /// Class providing extension methods for working with <see cref="IHasTrailers" />. + /// </summary> public static class HasTrailerExtensions { /// <summary> + /// Gets the trailer count. + /// </summary> + /// <returns><see cref="IReadOnlyList{Guid}" />.</returns> + public static int GetTrailerCount(this IHasTrailers item) + => item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count; + + /// <summary> /// Gets the trailer ids. /// </summary> - /// <returns>List<Guid>.</returns> - public static List<Guid> GetTrailerIds(this IHasTrailers item) + /// <returns><see cref="IReadOnlyList{Guid}" />.</returns> + public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item) { - var list = item.LocalTrailerIds.ToList(); - list.AddRange(item.RemoteTrailerIds); - return list; + var localIds = item.LocalTrailerIds; + var remoteIds = item.RemoteTrailerIds; + + var all = new Guid[localIds.Count + remoteIds.Count]; + var index = 0; + foreach (var id in localIds) + { + all[index++] = id; + } + + foreach (var id in remoteIds) + { + all[index++] = id; + } + + return all; } + /// <summary> + /// Gets the trailers. + /// </summary> + /// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns> + public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item) + { + var localIds = item.LocalTrailerIds; + var remoteIds = item.RemoteTrailerIds; + var libraryManager = BaseItem.LibraryManager; + + var all = new BaseItem[localIds.Count + remoteIds.Count]; + var index = 0; + foreach (var id in localIds) + { + all[index++] = libraryManager.GetItemById(id); + } + + foreach (var id in remoteIds) + { + all[index++] = libraryManager.GetItemById(id); + } + + return all; + } } } diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs index 848493864..fc46dec2e 100644 --- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs +++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs @@ -1,6 +1,6 @@ using System; +using System.Text.Json.Serialization; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { @@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Entities public int Height { get; set; } - [IgnoreDataMember] + [JsonIgnore] public bool IsLocalFile => Path == null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase); } } diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index bb2d03246..d88c31007 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; +using System.Globalization; +using System.Text.Json.Serialization; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { @@ -11,7 +12,7 @@ namespace MediaBrowser.Controller.Entities public LinkedChildType Type { get; set; } public string LibraryItemId { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string Id { get; set; } /// <summary> @@ -29,7 +30,7 @@ namespace MediaBrowser.Controller.Entities if (string.IsNullOrEmpty(child.Path)) { - child.LibraryItemId = item.Id.ToString("N"); + child.LibraryItemId = item.Id.ToString("N", CultureInfo.InvariantCulture); } return child; @@ -37,7 +38,7 @@ namespace MediaBrowser.Controller.Entities public LinkedChild() { - Id = Guid.NewGuid().ToString("N"); + Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); } } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index a532b5ee9..feaf8c45a 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities.Movies @@ -24,17 +24,20 @@ namespace MediaBrowser.Controller.Entities.Movies DisplayOrder = ItemSortBy.PremiereDate; } - [IgnoreDataMember] + [JsonIgnore] protected override bool FilterLinkedChildrenPerUser => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => true; - public Guid[] LocalTrailerIds { get; set; } - public Guid[] RemoteTrailerIds { get; set; } + /// <inheritdoc /> + public IReadOnlyList<Guid> LocalTrailerIds { get; set; } + + /// <inheritdoc /> + public IReadOnlyList<Guid> RemoteTrailerIds { get; set; } /// <summary> /// Gets or sets the display order. @@ -61,7 +64,8 @@ namespace MediaBrowser.Controller.Entities.Movies { return base.GetNonCachedChildren(directoryService); } - return new List<BaseItem>(); + + return Enumerable.Empty<BaseItem>(); } protected override List<BaseItem> LoadChildren() @@ -75,7 +79,7 @@ namespace MediaBrowser.Controller.Entities.Movies return new List<BaseItem>(); } - [IgnoreDataMember] + [JsonIgnore] private bool IsLegacyBoxSet { get @@ -94,7 +98,7 @@ namespace MediaBrowser.Controller.Entities.Movies } } - [IgnoreDataMember] + [JsonIgnore] public override bool IsPreSorted => true; public override bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders) diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index 20c5b3521..11dc472b6 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Providers; @@ -8,7 +9,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities.Movies { @@ -27,8 +27,11 @@ namespace MediaBrowser.Controller.Entities.Movies RemoteTrailerIds = Array.Empty<Guid>(); } - public Guid[] LocalTrailerIds { get; set; } - public Guid[] RemoteTrailerIds { get; set; } + /// <inheritdoc /> + public IReadOnlyList<Guid> LocalTrailerIds { get; set; } + + /// <inheritdoc /> + public IReadOnlyList<Guid> RemoteTrailerIds { get; set; } /// <summary> /// Gets or sets the name of the TMDB collection. @@ -36,7 +39,7 @@ namespace MediaBrowser.Controller.Entities.Movies /// <value>The name of the TMDB collection.</value> public string TmdbCollectionName { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string CollectionName { get => TmdbCollectionName; @@ -183,7 +186,7 @@ namespace MediaBrowser.Controller.Entities.Movies return list; } - [IgnoreDataMember] + [JsonIgnore] public override bool StopRefreshIfLocalMetadataFound => false; } } diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs index 5bf082b7e..603242063 100644 --- a/MediaBrowser.Controller/Entities/MusicVideo.cs +++ b/MediaBrowser.Controller/Entities/MusicVideo.cs @@ -1,24 +1,23 @@ using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasLookupInfo<MusicVideoInfo> { - [IgnoreDataMember] - public string[] Artists { get; set; } + /// <inheritdoc /> + [JsonIgnore] + public IReadOnlyList<string> Artists { get; set; } public MusicVideo() { Artists = Array.Empty<string>(); } - [IgnoreDataMember] - public string[] AllArtists => Artists; - public override UnratedItem GetBlockUnratedType() { return UnratedItem.Music; diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index dd0183489..d9b4b2206 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities @@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.Entities /// If the item is a folder, it returns the folder itself /// </summary> /// <value>The containing folder path.</value> - [IgnoreDataMember] + [JsonIgnore] public override string ContainingFolderPath => Path; public override bool CanDelete() @@ -63,13 +63,13 @@ namespace MediaBrowser.Controller.Entities return true; } - [IgnoreDataMember] + [JsonIgnore] public override bool EnableAlphaNumericSorting => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAncestors => false; public static string GetPath(string name) diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs index 60c832189..86d62add9 100644 --- a/MediaBrowser.Controller/Entities/Photo.cs +++ b/MediaBrowser.Controller/Entities/Photo.cs @@ -1,21 +1,21 @@ +using System.Text.Json.Serialization; using MediaBrowser.Model.Drawing; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { public class Photo : BaseItem { - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsLocalMetadata => false; - [IgnoreDataMember] + [JsonIgnore] public override string MediaType => Model.Entities.MediaType.Photo; - [IgnoreDataMember] + [JsonIgnore] public override Folder LatestItemsIndexContainer => AlbumEntity; - [IgnoreDataMember] + [JsonIgnore] public PhotoAlbum AlbumEntity { get diff --git a/MediaBrowser.Controller/Entities/PhotoAlbum.cs b/MediaBrowser.Controller/Entities/PhotoAlbum.cs index 4cd0c8b66..b86f1ac2a 100644 --- a/MediaBrowser.Controller/Entities/PhotoAlbum.cs +++ b/MediaBrowser.Controller/Entities/PhotoAlbum.cs @@ -1,16 +1,16 @@ -using MediaBrowser.Model.Serialization; +using System.Text.Json.Serialization; namespace MediaBrowser.Controller.Entities { public class PhotoAlbum : Folder { - [IgnoreDataMember] + [JsonIgnore] public override bool AlwaysScanInternalMetadataPath => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; } } diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index d6da0d48c..068032317 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Extensions; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities @@ -28,13 +28,13 @@ namespace MediaBrowser.Controller.Entities /// If the item is a folder, it returns the folder itself /// </summary> /// <value>The containing folder path.</value> - [IgnoreDataMember] + [JsonIgnore] public override string ContainingFolderPath => Path; - [IgnoreDataMember] + [JsonIgnore] public override bool IsDisplayedAsFolder => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAncestors => false; public override double GetDefaultPrimaryImageAspectRatio() @@ -62,7 +62,7 @@ namespace MediaBrowser.Controller.Entities return LibraryManager.GetItemList(query); } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; public static string GetPath(string name) diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index fb29c07b0..49229fa4b 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -2,11 +2,11 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Serialization; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities.TV @@ -23,8 +23,11 @@ namespace MediaBrowser.Controller.Entities.TV RemoteTrailerIds = Array.Empty<Guid>(); } - public Guid[] LocalTrailerIds { get; set; } - public Guid[] RemoteTrailerIds { get; set; } + /// <inheritdoc /> + public IReadOnlyList<Guid> LocalTrailerIds { get; set; } + + /// <inheritdoc /> + public IReadOnlyList<Guid> RemoteTrailerIds { get; set; } /// <summary> /// Gets the season in which it aired. @@ -46,25 +49,25 @@ namespace MediaBrowser.Controller.Entities.TV return series == null ? SeriesName : series.SortName; } - [IgnoreDataMember] + [JsonIgnore] protected override bool SupportsOwnedItems => IsStacked || MediaSourceCount > 1; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => true; - [IgnoreDataMember] + [JsonIgnore] public int? AiredSeasonNumber => AirsAfterSeasonNumber ?? AirsBeforeSeasonNumber ?? ParentIndexNumber; - [IgnoreDataMember] + [JsonIgnore] public override Folder LatestItemsIndexContainer => Series; - [IgnoreDataMember] + [JsonIgnore] public override Guid DisplayParentId => SeasonId; - [IgnoreDataMember] + [JsonIgnore] protected override bool EnableDefaultVideoUserDataKeys => false; public override double GetDefaultPrimaryImageAspectRatio() @@ -101,7 +104,7 @@ namespace MediaBrowser.Controller.Entities.TV /// This Episode's Series Instance /// </summary> /// <value>The series.</value> - [IgnoreDataMember] + [JsonIgnore] public Series Series { get @@ -115,7 +118,7 @@ namespace MediaBrowser.Controller.Entities.TV } } - [IgnoreDataMember] + [JsonIgnore] public Season Season { get @@ -129,16 +132,16 @@ namespace MediaBrowser.Controller.Entities.TV } } - [IgnoreDataMember] + [JsonIgnore] public bool IsInSeasonFolder => FindParent<Season>() != null; - [IgnoreDataMember] + [JsonIgnore] public string SeriesPresentationUniqueKey { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string SeriesName { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string SeasonName { get; set; } public string FindSeriesPresentationUniqueKey() @@ -221,7 +224,7 @@ namespace MediaBrowser.Controller.Entities.TV return false; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsRemoteImageDownloading { get @@ -235,12 +238,12 @@ namespace MediaBrowser.Controller.Entities.TV } } - [IgnoreDataMember] + [JsonIgnore] public bool IsMissingEpisode => LocationType == LocationType.Virtual; - [IgnoreDataMember] + [JsonIgnore] public Guid SeasonId { get; set; } - [IgnoreDataMember] + [JsonIgnore] public Guid SeriesId { get; set; } public Guid FindSeriesId() diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 5d7c260d1..9c8a469e2 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities.TV @@ -15,22 +15,22 @@ namespace MediaBrowser.Controller.Entities.TV /// </summary> public class Season : Folder, IHasSeries, IHasLookupInfo<SeasonInfo> { - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAddingToPlaylist => true; - [IgnoreDataMember] + [JsonIgnore] public override bool IsPreSorted => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsDateLastMediaAdded => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => true; - [IgnoreDataMember] + [JsonIgnore] public override Guid DisplayParentId => SeriesId; public override double GetDefaultPrimaryImageAspectRatio() @@ -71,7 +71,7 @@ namespace MediaBrowser.Controller.Entities.TV /// This Episode's Series Instance /// </summary> /// <value>The series.</value> - [IgnoreDataMember] + [JsonIgnore] public Series Series { get @@ -85,7 +85,7 @@ namespace MediaBrowser.Controller.Entities.TV } } - [IgnoreDataMember] + [JsonIgnore] public string SeriesPath { get @@ -179,13 +179,13 @@ namespace MediaBrowser.Controller.Entities.TV return UnratedItem.Series; } - [IgnoreDataMember] + [JsonIgnore] public string SeriesPresentationUniqueKey { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string SeriesName { get; set; } - [IgnoreDataMember] + [JsonIgnore] public Guid SeriesId { get; set; } public string FindSeriesPresentationUniqueKey() diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index eae834f6f..76cb9a651 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Dto; @@ -9,7 +11,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities.TV @@ -30,23 +31,26 @@ namespace MediaBrowser.Controller.Entities.TV public DayOfWeek[] AirDays { get; set; } public string AirTime { get; set; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAddingToPlaylist => true; - [IgnoreDataMember] + [JsonIgnore] public override bool IsPreSorted => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsDateLastMediaAdded => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => true; - public Guid[] LocalTrailerIds { get; set; } - public Guid[] RemoteTrailerIds { get; set; } + /// <inheritdoc /> + public IReadOnlyList<Guid> LocalTrailerIds { get; set; } + + /// <inheritdoc /> + public IReadOnlyList<Guid> RemoteTrailerIds { get; set; } /// <summary> /// airdate, dvd or absolute @@ -91,7 +95,7 @@ namespace MediaBrowser.Controller.Entities.TV } var folders = LibraryManager.GetCollectionFolders(this) - .Select(i => i.Id.ToString("N")) + .Select(i => i.Id.ToString("N", CultureInfo.InvariantCulture)) .ToArray(); if (folders.Length == 0) @@ -500,7 +504,7 @@ namespace MediaBrowser.Controller.Entities.TV return list; } - [IgnoreDataMember] + [JsonIgnore] public override bool StopRefreshIfLocalMetadataFound => false; } } diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs index 5bf22d7bc..0b8be90cd 100644 --- a/MediaBrowser.Controller/Entities/Trailer.cs +++ b/MediaBrowser.Controller/Entities/Trailer.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { @@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.Entities return list; } - [IgnoreDataMember] + [JsonIgnore] public override bool StopRefreshIfLocalMetadataFound => false; } } diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 9952ba418..c70ecccf1 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -1,11 +1,12 @@ using System; +using System.Globalization; using System.IO; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities @@ -16,13 +17,6 @@ namespace MediaBrowser.Controller.Entities public class User : BaseItem { public static IUserManager UserManager { get; set; } - public static IXmlSerializer XmlSerializer { get; set; } - - /// <summary> - /// From now on all user paths will be Id-based. - /// This is for backwards compatibility. - /// </summary> - public bool UsesIdForConfigurationPath { get; set; } /// <summary> /// Gets or sets the password. @@ -30,9 +24,8 @@ namespace MediaBrowser.Controller.Entities /// <value>The password.</value> public string Password { get; set; } public string EasyPassword { get; set; } - public string Salt { get; set; } - // Strictly to remove IgnoreDataMember + // Strictly to remove JsonIgnore public override ItemImageInfo[] ImageInfos { get => base.ImageInfos; @@ -43,7 +36,7 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the path. /// </summary> /// <value>The path.</value> - [IgnoreDataMember] + [JsonIgnore] public override string Path { get => ConfigurationDirectoryPath; @@ -72,14 +65,14 @@ namespace MediaBrowser.Controller.Entities /// If the item is a folder, it returns the folder itself /// </summary> /// <value>The containing folder path.</value> - [IgnoreDataMember] + [JsonIgnore] public override string ContainingFolderPath => Path; /// <summary> /// Gets the root folder. /// </summary> /// <value>The root folder.</value> - [IgnoreDataMember] + [JsonIgnore] public Folder RootFolder => LibraryManager.GetUserRootFolder(); /// <summary> @@ -95,7 +88,7 @@ namespace MediaBrowser.Controller.Entities private volatile UserConfiguration _config; private readonly object _configSyncLock = new object(); - [IgnoreDataMember] + [JsonIgnore] public UserConfiguration Configuration { get @@ -118,7 +111,7 @@ namespace MediaBrowser.Controller.Entities private volatile UserPolicy _policy; private readonly object _policySyncLock = new object(); - [IgnoreDataMember] + [JsonIgnore] public UserPolicy Policy { get @@ -147,46 +140,23 @@ namespace MediaBrowser.Controller.Entities /// <exception cref="ArgumentNullException"></exception> public Task Rename(string newName) { - if (string.IsNullOrEmpty(newName)) - { - throw new ArgumentNullException(nameof(newName)); - } - - // If only the casing is changing, leave the file system alone - if (!UsesIdForConfigurationPath && !string.Equals(newName, Name, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrWhiteSpace(newName)) { - UsesIdForConfigurationPath = true; - - // Move configuration - var newConfigDirectory = GetConfigurationDirectoryPath(newName); - var oldConfigurationDirectory = ConfigurationDirectoryPath; - - // Exceptions will be thrown if these paths already exist - if (Directory.Exists(newConfigDirectory)) - { - Directory.Delete(newConfigDirectory, true); - } - - if (Directory.Exists(oldConfigurationDirectory)) - { - Directory.Move(oldConfigurationDirectory, newConfigDirectory); - } - else - { - Directory.CreateDirectory(newConfigDirectory); - } + throw new ArgumentException("Username can't be empty", nameof(newName)); } Name = newName; - return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)) - { - ReplaceAllMetadata = true, - ImageRefreshMode = MetadataRefreshMode.FullRefresh, - MetadataRefreshMode = MetadataRefreshMode.FullRefresh, - ForceSave = true + return RefreshMetadata( + new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)) + { + ReplaceAllMetadata = true, + ImageRefreshMode = MetadataRefreshMode.FullRefresh, + MetadataRefreshMode = MetadataRefreshMode.FullRefresh, + ForceSave = true - }, CancellationToken.None); + }, + CancellationToken.None); } public override void UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken) @@ -198,7 +168,7 @@ namespace MediaBrowser.Controller.Entities /// Gets the path to the user's configuration directory /// </summary> /// <value>The configuration directory path.</value> - [IgnoreDataMember] + [JsonIgnore] public string ConfigurationDirectoryPath => GetConfigurationDirectoryPath(Name); public override double GetDefaultPrimaryImageAspectRatio() @@ -215,22 +185,9 @@ namespace MediaBrowser.Controller.Entities { var parentPath = ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath; - // Legacy - if (!UsesIdForConfigurationPath) - { - if (string.IsNullOrEmpty(username)) - { - throw new ArgumentNullException(nameof(username)); - } - - var safeFolderName = FileSystem.GetValidFilename(username); - - return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, safeFolderName); - } - // TODO: Remove idPath and just use usernamePath for future releases var usernamePath = System.IO.Path.Combine(parentPath, username); - var idPath = System.IO.Path.Combine(parentPath, Id.ToString("N")); + var idPath = System.IO.Path.Combine(parentPath, Id.ToString("N", CultureInfo.InvariantCulture)); if (!Directory.Exists(usernamePath) && Directory.Exists(idPath)) { Directory.Move(idPath, usernamePath); @@ -295,7 +252,7 @@ namespace MediaBrowser.Controller.Entities return false; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; public long InternalId { get; set; } diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs index f7136bdf2..ab425ee0f 100644 --- a/MediaBrowser.Controller/Entities/UserItemData.cs +++ b/MediaBrowser.Controller/Entities/UserItemData.cs @@ -1,5 +1,5 @@ using System; -using MediaBrowser.Model.Serialization; +using System.Text.Json.Serialization; namespace MediaBrowser.Controller.Entities { @@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.Entities /// This should never be serialized. /// </summary> /// <value><c>null</c> if [likes] contains no value, <c>true</c> if [likes]; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool? Likes { get diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 7fe8df8af..7fcf48a48 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { @@ -33,10 +33,10 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => false; private void ClearCache() @@ -75,10 +75,10 @@ namespace MediaBrowser.Controller.Entities return GetChildren(user, true).Count; } - [IgnoreDataMember] + [JsonIgnore] protected override bool SupportsShortcutChildren => true; - [IgnoreDataMember] + [JsonIgnore] public override bool IsPreSorted => true; protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 4a6d32dce..fd045f0dd 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using System.Threading.Tasks; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { @@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Entities public static ITVSeriesManager TVSeriesManager; - [IgnoreDataMember] + [JsonIgnore] public string CollectionType => ViewType; public override IEnumerable<Guid> GetIdsForAncestorQuery() @@ -40,10 +40,10 @@ namespace MediaBrowser.Controller.Entities return list; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => false; public override int GetChildCount(User user) @@ -167,7 +167,7 @@ namespace MediaBrowser.Controller.Entities return Task.CompletedTask; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; } } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index e483c8f34..454bdc4ae 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities.Movies; @@ -987,7 +988,7 @@ namespace MediaBrowser.Controller.Entities private UserView GetUserViewWithName(string name, string type, string sortName, BaseItem parent) { - return _userViewManager.GetUserSubView(parent.Id, parent.Id.ToString("N"), type, sortName); + return _userViewManager.GetUserSubView(parent.Id, parent.Id.ToString("N", CultureInfo.InvariantCulture), type, sortName); } private UserView GetUserView(string type, string localizationKey, string sortName, BaseItem parent) diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 8379dcc09..60906bdb0 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Library; @@ -13,7 +14,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Entities { @@ -25,23 +25,23 @@ namespace MediaBrowser.Controller.Entities ISupportsPlaceHolders, IHasMediaSources { - [IgnoreDataMember] + [JsonIgnore] public string PrimaryVersionId { get; set; } public string[] AdditionalParts { get; set; } public string[] LocalAlternateVersions { get; set; } public LinkedChild[] LinkedAlternateVersions { get; set; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPositionTicksResume { get @@ -90,7 +90,7 @@ namespace MediaBrowser.Controller.Entities return base.CreatePresentationUniqueKey(); } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsThemeMedia => true; /// <summary> @@ -180,10 +180,10 @@ namespace MediaBrowser.Controller.Entities return IsFileProtocol; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAddingToPlaylist => true; - [IgnoreDataMember] + [JsonIgnore] public int MediaSourceCount { get @@ -200,10 +200,10 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public bool IsStacked => AdditionalParts.Length > 0; - [IgnoreDataMember] + [JsonIgnore] public override bool HasLocalAlternateVersions => LocalAlternateVersions.Length > 0; public IEnumerable<Guid> GetAdditionalPartIds() @@ -218,7 +218,7 @@ namespace MediaBrowser.Controller.Entities public static ILiveTvManager LiveTvManager { get; set; } - [IgnoreDataMember] + [JsonIgnore] public override SourceType SourceType { get @@ -247,7 +247,7 @@ namespace MediaBrowser.Controller.Entities return base.CanDelete(); } - [IgnoreDataMember] + [JsonIgnore] public bool IsCompleteMedia { get @@ -261,7 +261,7 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] protected virtual bool EnableDefaultVideoUserDataKeys => true; public override List<string> GetUserDataKeys() @@ -338,7 +338,7 @@ namespace MediaBrowser.Controller.Entities .OrderBy(i => i.SortName); } - [IgnoreDataMember] + [JsonIgnore] public override string ContainingFolderPath { get @@ -360,7 +360,7 @@ namespace MediaBrowser.Controller.Entities } } - [IgnoreDataMember] + [JsonIgnore] public override string FileNameWithoutExtension { get @@ -432,14 +432,14 @@ namespace MediaBrowser.Controller.Entities /// Gets a value indicating whether [is3 D]. /// </summary> /// <value><c>true</c> if [is3 D]; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool Is3D => Video3DFormat.HasValue; /// <summary> /// Gets the type of the media. /// </summary> /// <value>The type of the media.</value> - [IgnoreDataMember] + [JsonIgnore] public override string MediaType => Model.Entities.MediaType.Video; protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index 13e82fada..a01ef5c31 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; -using MediaBrowser.Model.Serialization; +using System.Text.Json.Serialization; using Microsoft.Extensions.Logging; namespace MediaBrowser.Controller.Entities @@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Entities /// If the item is a folder, it returns the folder itself /// </summary> /// <value>The containing folder path.</value> - [IgnoreDataMember] + [JsonIgnore] public override string ContainingFolderPath => Path; public override double GetDefaultPrimaryImageAspectRatio() @@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Entities return value; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAncestors => false; public override bool CanDelete() @@ -72,7 +72,7 @@ namespace MediaBrowser.Controller.Entities return null; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople => false; public static string GetPath(string name) diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 3f8cc0b83..61b2c15ae 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -83,7 +83,7 @@ namespace MediaBrowser.Controller void EnableLoopback(string appName); - WakeOnLanInfo[] GetWakeOnLanInfo(); + IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo(); string ExpandVirtualPath(string path); string ReverseVirtualPath(string path); diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 7f7370893..bbedc0442 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -23,6 +23,12 @@ namespace MediaBrowser.Controller.Library IEnumerable<User> Users { get; } /// <summary> + /// Gets the user ids. + /// </summary> + /// <value>The users ids.</value> + IEnumerable<Guid> UsersIds { get; } + + /// <summary> /// Occurs when [user updated]. /// </summary> event EventHandler<GenericEventArgs<User>> UserUpdated; @@ -92,7 +98,7 @@ namespace MediaBrowser.Controller.Library /// <returns>User.</returns> /// <exception cref="ArgumentNullException">name</exception> /// <exception cref="ArgumentException"></exception> - Task<User> CreateUser(string name); + User CreateUser(string name); /// <summary> /// Deletes the user. @@ -101,7 +107,7 @@ namespace MediaBrowser.Controller.Library /// <returns>Task.</returns> /// <exception cref="ArgumentNullException">user</exception> /// <exception cref="ArgumentException"></exception> - Task DeleteUser(User user); + void DeleteUser(User user); /// <summary> /// Resets the password. diff --git a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs index c719ad81c..2ea0a748e 100644 --- a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs +++ b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs @@ -10,10 +10,15 @@ namespace MediaBrowser.Controller.LiveTv public interface IListingsProvider { string Name { get; } + string Type { get; } + Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken); + Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings); + Task<List<NameIdPair>> GetLineups(ListingsProviderInfo info, string country, string location); + Task<List<ChannelInfo>> GetChannels(ListingsProviderInfo info, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index f99df6c7c..e02c387e4 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -221,7 +221,7 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="fields">The fields.</param> /// <param name="user">The user.</param> /// <returns>Task.</returns> - Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> programs, ItemFields[] fields, User user = null); + Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, ItemFields[] fields, User user = null); /// <summary> /// Saves the tuner host. @@ -258,7 +258,7 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="items">The items.</param> /// <param name="options">The options.</param> /// <param name="user">The user.</param> - void AddChannelInfo(List<Tuple<BaseItemDto, LiveTvChannel>> items, DtoOptions options, User user); + void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user); Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken); Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken); diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index 55f47aae9..60391bb83 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -2,13 +2,13 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Text.Json.Serialization; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.LiveTv { @@ -31,13 +31,13 @@ namespace MediaBrowser.Controller.LiveTv return UnratedItem.LiveTvChannel; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPositionTicksResume => false; - [IgnoreDataMember] + [JsonIgnore] public override SourceType SourceType => SourceType.LiveTV; - [IgnoreDataMember] + [JsonIgnore] public override bool EnableRememberingTrackSelections => false; /// <summary> @@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.LiveTv /// <value>The type of the channel.</value> public ChannelType ChannelType { get; set; } - [IgnoreDataMember] + [JsonIgnore] public override LocationType LocationType => LocationType.Remote; protected override string CreateSortName() @@ -70,7 +70,7 @@ namespace MediaBrowser.Controller.LiveTv return (Number ?? string.Empty) + "-" + (Name ?? string.Empty); } - [IgnoreDataMember] + [JsonIgnore] public override string MediaType => ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; public override string GetClientTypeName() @@ -89,7 +89,7 @@ namespace MediaBrowser.Controller.LiveTv var info = new MediaSourceInfo { - Id = Id.ToString("N"), + Id = Id.ToString("N", CultureInfo.InvariantCulture), Protocol = PathProtocol ?? MediaProtocol.File, MediaStreams = new List<MediaStream>(), Name = Name, @@ -111,7 +111,7 @@ namespace MediaBrowser.Controller.LiveTv protected override string GetInternalMetadataPath(string basePath) { - return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N"), "metadata"); + return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N", CultureInfo.InvariantCulture), "metadata"); } public override bool CanDelete() @@ -119,45 +119,45 @@ namespace MediaBrowser.Controller.LiveTv return false; } - [IgnoreDataMember] + [JsonIgnore] public bool IsMovie { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is sports. /// </summary> /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsSports { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is series. /// </summary> /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsSeries { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is news. /// </summary> /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsNews { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is kids. /// </summary> /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase); - [IgnoreDataMember] + [JsonIgnore] public bool IsRepeat { get; set; } /// <summary> /// Gets or sets the episode title. /// </summary> /// <value>The episode title.</value> - [IgnoreDataMember] + [JsonIgnore] public string EpisodeTitle { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 8bde6a5da..13df85aed 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Text.Json.Serialization; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Providers; @@ -8,7 +10,6 @@ using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Providers; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.LiveTv { @@ -62,79 +63,79 @@ namespace MediaBrowser.Controller.LiveTv } } - [IgnoreDataMember] + [JsonIgnore] public override SourceType SourceType => SourceType.LiveTV; /// <summary> /// The start date of the program, in UTC. /// </summary> - [IgnoreDataMember] + [JsonIgnore] public DateTime StartDate { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is repeat. /// </summary> /// <value><c>true</c> if this instance is repeat; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsRepeat { get; set; } /// <summary> /// Gets or sets the episode title. /// </summary> /// <value>The episode title.</value> - [IgnoreDataMember] + [JsonIgnore] public string EpisodeTitle { get; set; } - [IgnoreDataMember] + [JsonIgnore] public string ShowId { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is movie. /// </summary> /// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsMovie { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is sports. /// </summary> /// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsSports => Tags.Contains("Sports", StringComparer.OrdinalIgnoreCase); /// <summary> /// Gets or sets a value indicating whether this instance is series. /// </summary> /// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsSeries { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is live. /// </summary> /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsLive => Tags.Contains("Live", StringComparer.OrdinalIgnoreCase); /// <summary> /// Gets or sets a value indicating whether this instance is news. /// </summary> /// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsNews => Tags.Contains("News", StringComparer.OrdinalIgnoreCase); /// <summary> /// Gets or sets a value indicating whether this instance is kids. /// </summary> /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase); /// <summary> /// Gets or sets a value indicating whether this instance is premiere. /// </summary> /// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsPremiere => Tags.Contains("Premiere", StringComparer.OrdinalIgnoreCase); /// <summary> @@ -142,10 +143,10 @@ namespace MediaBrowser.Controller.LiveTv /// If the item is a folder, it returns the folder itself /// </summary> /// <value>The containing folder path.</value> - [IgnoreDataMember] + [JsonIgnore] public override string ContainingFolderPath => Path; - //[IgnoreDataMember] + //[JsonIgnore] //public override string MediaType //{ // get @@ -154,7 +155,7 @@ namespace MediaBrowser.Controller.LiveTv // } //} - [IgnoreDataMember] + [JsonIgnore] public bool IsAiring { get @@ -165,7 +166,7 @@ namespace MediaBrowser.Controller.LiveTv } } - [IgnoreDataMember] + [JsonIgnore] public bool HasAired { get @@ -188,7 +189,7 @@ namespace MediaBrowser.Controller.LiveTv protected override string GetInternalMetadataPath(string basePath) { - return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N")); + return System.IO.Path.Combine(basePath, "livetv", Id.ToString("N", CultureInfo.InvariantCulture)); } public override bool CanDelete() @@ -196,7 +197,7 @@ namespace MediaBrowser.Controller.LiveTv return false; } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPeople { get @@ -211,7 +212,7 @@ namespace MediaBrowser.Controller.LiveTv } } - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsAncestors => false; private LiveTvOptions GetConfiguration() diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs index 2fea6a8cb..46774b2b7 100644 --- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json.Serialization; using MediaBrowser.Model.LiveTv; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.LiveTv { @@ -129,10 +129,10 @@ namespace MediaBrowser.Controller.LiveTv /// Gets or sets a value indicating whether this instance is live. /// </summary> /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value> - [IgnoreDataMember] + [JsonIgnore] public bool IsLive => Tags.Contains("Live", StringComparer.OrdinalIgnoreCase); - [IgnoreDataMember] + [JsonIgnore] public bool IsPremiere => Tags.Contains("Premiere", StringComparer.OrdinalIgnoreCase); public int? ProductionYear { get; set; } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 01893f1b5..c6bca2518 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -19,6 +19,7 @@ <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> + <GenerateDocumentationFile>true</GenerateDocumentationFile> </PropertyGroup> </Project> diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b9ccc93ef..d896a7aef 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Text; using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; @@ -22,8 +23,17 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; private readonly ISubtitleEncoder _subtitleEncoder; - // private readonly IApplicationPaths _appPaths; - // private readonly IAssemblyInfo _assemblyInfo; + + private static readonly string[] _videoProfiles = new[] + { + "ConstrainedBaseline", + "Baseline", + "Extended", + "Main", + "High", + "ProgressiveHigh", + "ConstrainedHigh" + }; public EncodingHelper(IMediaEncoder mediaEncoder, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder) { @@ -33,9 +43,13 @@ namespace MediaBrowser.Controller.MediaEncoding } public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) - { - var defaultEncoder = "libx264"; + => GetH264OrH265Encoder("libx264", "h264", state, encodingOptions); + + public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) + => GetH264OrH265Encoder("libx265", "hevc", state, encodingOptions); + private string GetH264OrH265Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptions encodingOptions) + { // Only use alternative encoders for video files. // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. @@ -45,35 +59,30 @@ namespace MediaBrowser.Controller.MediaEncoding var codecMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { - {"qsv", "h264_qsv"}, - {"h264_qsv", "h264_qsv"}, - {"nvenc", "h264_nvenc"}, - {"amf", "h264_amf"}, - {"omx", "h264_omx"}, - {"h264_v4l2m2m", "h264_v4l2m2m"}, - {"mediacodec", "h264_mediacodec"}, - {"vaapi", "h264_vaapi"} + {"qsv", hwEncoder + "_qsv"}, + {hwEncoder + "_qsv", hwEncoder + "_qsv"}, + {"nvenc", hwEncoder + "_nvenc"}, + {"amf", hwEncoder + "_amf"}, + {"omx", hwEncoder + "_omx"}, + {hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m"}, + {"mediacodec", hwEncoder + "_mediacodec"}, + {"vaapi", hwEncoder + "_vaapi"} }; if (!string.IsNullOrEmpty(hwType) - && encodingOptions.EnableHardwareEncoding && codecMap.ContainsKey(hwType)) + && encodingOptions.EnableHardwareEncoding + && codecMap.ContainsKey(hwType) + && CheckVaapi(state, hwType, encodingOptions)) { - if (CheckVaapi(state, hwType, encodingOptions)) - { - var preferredEncoder = codecMap[hwType]; + var preferredEncoder = codecMap[hwType]; - if (_mediaEncoder.SupportsEncoder(preferredEncoder)) - { - return preferredEncoder; - } + if (_mediaEncoder.SupportsEncoder(preferredEncoder)) + { + return preferredEncoder; } } - } - // Avoid performing a second attempt when the first one - // hasn't tried hardware encoding anyway. - encodingOptions.EnableHardwareEncoding = false; return defaultEncoder; } @@ -98,15 +107,13 @@ namespace MediaBrowser.Controller.MediaEncoding { var videoStream = state.VideoStream; - if (videoStream != null) + // vaapi will throw an error with this input + // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99. + if (string.Equals(videoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) { - // vaapi will throw an error with this input - // [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99. - if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase)) - { - return false; - } + return false; } + return true; } @@ -119,18 +126,27 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { + if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) + { + return GetH265Encoder(state, encodingOptions); + } + if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) { return GetH264Encoder(state, encodingOptions); } + if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) { return "libvpx"; } + if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase)) { return "wmv2"; } + if (string.Equals(codec, "theora", StringComparison.OrdinalIgnoreCase)) { return "libtheora"; @@ -149,11 +165,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// <returns>System.String.</returns> public string GetUserAgentParam(EncodingJobInfo state) { - string useragent = null; - - state.RemoteHttpHeaders.TryGetValue("User-Agent", out useragent); - - if (!string.IsNullOrEmpty(useragent)) + if (state.RemoteHttpHeaders.TryGetValue("User-Agent", out string useragent)) { return "-user_agent \"" + useragent + "\""; } @@ -180,50 +192,62 @@ namespace MediaBrowser.Controller.MediaEncoding { return null; } + if (string.Equals(container, "wmv", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "mts", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "vob", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "mpg", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "mpeg", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "rec", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "dvr-ms", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "ogm", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "divx", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "rmvb", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(container, "rtp", StringComparison.OrdinalIgnoreCase)) { return null; @@ -251,10 +275,12 @@ namespace MediaBrowser.Controller.MediaEncoding { return null; } + if (string.Equals(codec, "aac_latm", StringComparison.OrdinalIgnoreCase)) { return null; } + if (string.Equals(codec, "eac3", StringComparison.OrdinalIgnoreCase)) { return null; @@ -279,30 +305,37 @@ namespace MediaBrowser.Controller.MediaEncoding { return "mp3"; } + if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) { return "aac"; } + if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase)) { return "wma"; } + if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } + if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } + if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } + if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } + if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; @@ -324,14 +357,17 @@ namespace MediaBrowser.Controller.MediaEncoding { return "wmv"; } + if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) { return "vpx"; } + if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) { return "theora"; } + if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase)) { return "h264"; @@ -342,19 +378,9 @@ namespace MediaBrowser.Controller.MediaEncoding public int GetVideoProfileScore(string profile) { - var list = new[] - { - "ConstrainedBaseline", - "Baseline", - "Extended", - "Main", - "High", - "ProgressiveHigh", - "ConstrainedHigh" - }; - // strip spaces because they may be stripped out on the query string - return Array.FindIndex(list, t => string.Equals(t, profile.Replace(" ", ""), StringComparison.OrdinalIgnoreCase)); + profile = profile.Replace(" ", ""); + return Array.FindIndex(_videoProfiles, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); } public string GetInputPathArgument(EncodingJobInfo state) @@ -362,14 +388,19 @@ namespace MediaBrowser.Controller.MediaEncoding var protocol = state.InputProtocol; var mediaPath = state.MediaPath ?? string.Empty; - var inputPath = new[] { mediaPath }; - - if (state.IsInputVideo) + string[] inputPath; + if (state.IsInputVideo + && !(state.VideoType == VideoType.Iso && state.IsoMount == null)) { - if (!(state.VideoType == VideoType.Iso && state.IsoMount == null)) - { - inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, mediaPath, state.InputProtocol, state.IsoMount, state.PlayableStreamFileNames); - } + inputPath = MediaEncoderHelpers.GetInputArgument( + _fileSystem, + mediaPath, + state.IsoMount, + state.PlayableStreamFileNames); + } + else + { + inputPath = new[] { mediaPath }; } return _mediaEncoder.GetInputArgument(inputPath, protocol); @@ -388,18 +419,22 @@ namespace MediaBrowser.Controller.MediaEncoding { return "aac -strict experimental"; } + if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase)) { return "libmp3lame"; } + if (string.Equals(codec, "vorbis", StringComparison.OrdinalIgnoreCase)) { return "libvorbis"; } + if (string.Equals(codec, "wma", StringComparison.OrdinalIgnoreCase)) { return "wmav2"; } + if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) { return "libopus"; @@ -413,54 +448,59 @@ namespace MediaBrowser.Controller.MediaEncoding /// </summary> public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions) { - var request = state.BaseRequest; + var arg = new StringBuilder(); - var arg = string.Format("-i {0}", GetInputPathArgument(state)); - - if (state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) + if (state.IsVideoRequest + && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { - if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; + var hwOutputFormat = "vaapi"; + + if (hasGraphicalSubs) { - if (state.VideoStream != null && state.VideoStream.Width.HasValue) - { - // This is hacky but not sure how to get the exact subtitle resolution - int height = Convert.ToInt32((double)state.VideoStream.Width.Value / 16.0 * 9.0); + hwOutputFormat = "yuv420p"; + } - arg += string.Format(" -canvas_size {0}:{1}", state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture), height.ToString(CultureInfo.InvariantCulture)); - } + arg.Append("-hwaccel vaapi -hwaccel_output_format ") + .Append(hwOutputFormat) + .Append(" -vaapi_device ") + .Append(encodingOptions.VaapiDevice) + .Append(' '); + } - var subtitlePath = state.SubtitleStream.Path; + arg.Append("-i ") + .Append(GetInputPathArgument(state)); - if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase)) - { - var idxFile = Path.ChangeExtension(subtitlePath, ".idx"); - if (File.Exists(idxFile)) - { - subtitlePath = idxFile; - } - } + if (state.SubtitleStream != null + && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode + && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) + { + if (state.VideoStream != null && state.VideoStream.Width.HasValue) + { + // This is hacky but not sure how to get the exact subtitle resolution + int height = Convert.ToInt32(state.VideoStream.Width.Value / 16.0 * 9.0); - arg += " -i \"" + subtitlePath + "\""; + arg.Append(" -canvas_size ") + .Append(state.VideoStream.Width.Value.ToString(CultureInfo.InvariantCulture)) + .Append(':') + .Append(height.ToString(CultureInfo.InvariantCulture)); } - } - if (state.IsVideoRequest) - { - if (GetVideoEncoder(state, encodingOptions).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1) - { - var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - var hwOutputFormat = "vaapi"; + var subtitlePath = state.SubtitleStream.Path; - if (hasGraphicalSubs) + if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase)) + { + var idxFile = Path.ChangeExtension(subtitlePath, ".idx"); + if (File.Exists(idxFile)) { - hwOutputFormat = "yuv420p"; + subtitlePath = idxFile; } - - arg = "-hwaccel vaapi -hwaccel_output_format " + hwOutputFormat + " -vaapi_device " + encodingOptions.VaapiDevice + " " + arg; } + + arg.Append(" -i \"").Append(subtitlePath).Append('\"'); } - return arg.Trim(); + return arg.ToString(); } /// <summary> @@ -472,8 +512,32 @@ namespace MediaBrowser.Controller.MediaEncoding { var codec = stream.Codec ?? string.Empty; - return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || - codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; + return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 + || codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; + } + + public bool IsH265(MediaStream stream) + { + var codec = stream.Codec ?? string.Empty; + + return codec.IndexOf("265", StringComparison.OrdinalIgnoreCase) != -1 + || codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1; + } + + public string GetBitStreamArgs(MediaStream stream) + { + if (IsH264(stream)) + { + return "-bsf:v h264_mp4toannexb"; + } + else if (IsH265(stream)) + { + return "-bsf:v hevc_mp4toannexb"; + } + else + { + return null; + } } public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec) @@ -486,26 +550,38 @@ namespace MediaBrowser.Controller.MediaEncoding { // With vpx when crf is used, b:v becomes a max rate // https://trac.ffmpeg.org/wiki/vpxEncodingGuide. - return string.Format(" -maxrate:v {0} -bufsize:v {1} -b:v {0}", bitrate.Value.ToString(_usCulture), (bitrate.Value * 2).ToString(_usCulture)); + return string.Format( + CultureInfo.InvariantCulture, + " -maxrate:v {0} -bufsize:v {1} -b:v {0}", + bitrate.Value, + bitrate.Value * 2); } if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase)) { - return string.Format(" -b:v {0}", bitrate.Value.ToString(_usCulture)); + return string.Format( + CultureInfo.InvariantCulture, + " -b:v {0}", + bitrate.Value); } - if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) { // h264 - return string.Format(" -maxrate {0} -bufsize {1}", - bitrate.Value.ToString(_usCulture), - (bitrate.Value * 2).ToString(_usCulture)); + return string.Format( + CultureInfo.InvariantCulture, + " -maxrate {0} -bufsize {1}", + bitrate.Value, + bitrate.Value * 2); } // h264 - return string.Format(" -b:v {0} -maxrate {0} -bufsize {1}", - bitrate.Value.ToString(_usCulture), - (bitrate.Value * 2).ToString(_usCulture)); + return string.Format( + CultureInfo.InvariantCulture, + " -b:v {0} -maxrate {0} -bufsize {1}", + bitrate.Value, + bitrate.Value * 2); } return string.Empty; @@ -515,8 +591,10 @@ namespace MediaBrowser.Controller.MediaEncoding { // Clients may direct play higher than level 41, but there's no reason to transcode higher if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel) - && string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) - && requestLevel > 41) + && requestLevel > 41 + && (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase))) { return "41"; } @@ -536,7 +614,7 @@ namespace MediaBrowser.Controller.MediaEncoding // hls always copies timestamps var setPtsParam = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive ? string.Empty - : string.Format(",setpts=PTS -{0}/TB", seconds.ToString(_usCulture)); + : string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds); // TODO // var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf"); @@ -620,49 +698,53 @@ namespace MediaBrowser.Controller.MediaEncoding /// <summary> /// Gets the video bitrate to specify on the command line /// </summary> - public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultH264Preset) + public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset) { var param = string.Empty; var isVc1 = state.VideoStream != null && string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); + var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase); - if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265) { - if (!string.IsNullOrEmpty(encodingOptions.H264Preset)) + if (!string.IsNullOrEmpty(encodingOptions.EncoderPreset)) { - param += "-preset " + encodingOptions.H264Preset; + param += "-preset " + encodingOptions.EncoderPreset; } else { - param += "-preset " + defaultH264Preset; + param += "-preset " + defaultPreset; } - if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51) + int encodeCrf = encodingOptions.H264Crf; + if (isLibX265) { - param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture); + encodeCrf = encodingOptions.H265Crf; } - else + + if (encodeCrf >= 0 && encodeCrf <= 51) { - param += " -crf 23"; + param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture); } - } - - else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) - { - param += "-preset fast"; + else + { + string defaultCrf = "23"; + if (isLibX265) + { + defaultCrf = "28"; + } - param += " -crf 28"; + param += " -crf " + defaultCrf; + } } - - // h264 (h264_qsv) - else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) // h264 (h264_qsv) { string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" }; - if (valid_h264_qsv.Contains(encodingOptions.H264Preset, StringComparer.OrdinalIgnoreCase)) + if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparer.OrdinalIgnoreCase)) { - param += "-preset " + encodingOptions.H264Preset; + param += "-preset " + encodingOptions.EncoderPreset; } else { @@ -672,11 +754,10 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -look_ahead 0"; } - - // h264 (h264_nvenc) - else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) { - switch (encodingOptions.H264Preset) + switch (encodingOptions.EncoderPreset) { case "veryslow": @@ -705,9 +786,7 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - - // webm - else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm { // Values 0-3, 0 being highest quality but slower var profileScore = 0; @@ -733,18 +812,14 @@ namespace MediaBrowser.Controller.MediaEncoding qmin, qmax); } - else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase)) { param += "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2"; } - - // asf/wmv - else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase)) // asf/wmv { param += "-qmin 2"; } - else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase)) { param += "-mbd 2"; @@ -760,21 +835,21 @@ namespace MediaBrowser.Controller.MediaEncoding var targetVideoCodec = state.ActualOutputVideoCodec; - var request = state.BaseRequest; var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault(); // vaapi does not support Baseline profile, force Constrained Baseline in this case, // which is compatible (and ugly) - if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && - profile != null && profile.ToLowerInvariant().Contains("baseline")) + if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && profile != null + && profile.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) != -1) { - profile = "constrained_baseline"; + profile = "constrained_baseline"; } if (!string.IsNullOrEmpty(profile)) { - if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { // not supported by h264_omx param += " -profile:v " + profile; @@ -789,8 +864,9 @@ namespace MediaBrowser.Controller.MediaEncoding // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 - if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { switch (level) { @@ -826,10 +902,11 @@ namespace MediaBrowser.Controller.MediaEncoding break; } } - // nvenc doesn't decode with param -level set ?! - else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) { - //param += ""; + // nvenc doesn't decode with param -level set ?! + // TODO: } else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)) { @@ -842,10 +919,15 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -x264opts:0 subme=0:me_range=4:rc_lookahead=10:me=dia:no_chroma_me:8x8dct=0:partitions=none"; } - if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && - !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) + { + // todo + } + + if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)) { param = "-pix_fmt yuv420p " + param; } @@ -867,12 +949,10 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - if (videoStream.IsInterlaced) + if (videoStream.IsInterlaced + && state.DeInterlace(videoStream.Codec, false)) { - if (state.DeInterlace(videoStream.Codec, false)) - { - return false; - } + return false; } if (videoStream.IsAnamorphic ?? false) @@ -884,24 +964,23 @@ namespace MediaBrowser.Controller.MediaEncoding } // Can't stream copy if we're burning in subtitles - if (request.SubtitleStreamIndex.HasValue) + if (request.SubtitleStreamIndex.HasValue + && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { - if (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) - { - return false; - } + return false; } - if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase)) + if (string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase) + && videoStream.IsAVC.HasValue + && !videoStream.IsAVC.Value + && request.RequireAvc) { - if (videoStream.IsAVC.HasValue && !videoStream.IsAVC.Value && request.RequireAvc) - { - return false; - } + return false; } // Source and target codecs must match - if (string.IsNullOrEmpty(videoStream.Codec) || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(videoStream.Codec) + || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase)) { return false; } @@ -931,21 +1010,17 @@ namespace MediaBrowser.Controller.MediaEncoding } // Video width must fall within requested value - if (request.MaxWidth.HasValue) + if (request.MaxWidth.HasValue + && (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value)) { - if (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value) - { - return false; - } + return false; } // Video height must fall within requested value - if (request.MaxHeight.HasValue) + if (request.MaxHeight.HasValue + && (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value)) { - if (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value) - { - return false; - } + return false; } // Video framerate must fall within requested value @@ -961,12 +1036,10 @@ namespace MediaBrowser.Controller.MediaEncoding } // Video bitrate must fall within requested value - if (request.VideoBitRate.HasValue) + if (request.VideoBitRate.HasValue + && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value)) { - if (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value) - { - return false; - } + return false; } var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec); @@ -979,35 +1052,31 @@ namespace MediaBrowser.Controller.MediaEncoding } var maxRefFrames = state.GetRequestedMaxRefFrames(videoStream.Codec); - if (maxRefFrames.HasValue) + if (maxRefFrames.HasValue + && videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value) { - if (videoStream.RefFrames.HasValue && videoStream.RefFrames.Value > maxRefFrames.Value) - { - return false; - } + return false; } // If a specific level was requested, the source must match or be less than var level = state.GetRequestedLevel(videoStream.Codec); - if (!string.IsNullOrEmpty(level)) + if (!string.IsNullOrEmpty(level) + && double.TryParse(level, NumberStyles.Any, _usCulture, out var requestLevel)) { - if (double.TryParse(level, NumberStyles.Any, _usCulture, out var requestLevel)) + if (!videoStream.Level.HasValue) { - if (!videoStream.Level.HasValue) - { - //return false; - } + //return false; + } - if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel) - { - return false; - } + if (videoStream.Level.HasValue && videoStream.Level.Value > requestLevel) + { + return false; } } - if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase) && - string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase) && - !(videoStream.IsAVC ?? false)) + if (string.Equals(state.InputContainer, "avi", StringComparison.OrdinalIgnoreCase) + && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase) + && !(videoStream.IsAVC ?? false)) { // see Coach S01E01 - Kelly and the Professor(0).avi return false; @@ -1016,7 +1085,7 @@ namespace MediaBrowser.Controller.MediaEncoding return request.EnableAutoStreamCopy; } - public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, string[] supportedAudioCodecs) + public bool CanStreamCopyAudio(EncodingJobInfo state, MediaStream audioStream, IEnumerable<string> supportedAudioCodecs) { var request = state.BaseRequest; @@ -1026,16 +1095,16 @@ namespace MediaBrowser.Controller.MediaEncoding } var maxBitDepth = state.GetRequestedAudioBitDepth(audioStream.Codec); - if (maxBitDepth.HasValue) + if (maxBitDepth.HasValue + && audioStream.BitDepth.HasValue + && audioStream.BitDepth.Value > maxBitDepth.Value) { - if (audioStream.BitDepth.HasValue && audioStream.BitDepth.Value > maxBitDepth.Value) - { - return false; - } + return false; } // Source and target codecs must match - if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(audioStream.Codec) + || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase)) { return false; } @@ -1048,6 +1117,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return false; } + if (audioStream.Channels.Value > channels.Value) { return false; @@ -1061,6 +1131,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return false; } + if (audioStream.SampleRate.Value > request.AudioSampleRate.Value) { return false; @@ -1074,6 +1145,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return false; } + if (audioStream.BitRate.Value > request.AudioBitRate.Value) { return false; @@ -1089,29 +1161,29 @@ namespace MediaBrowser.Controller.MediaEncoding if (videoStream != null) { - var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue && - request.Height.Value > videoStream.Height.Value && request.Width.HasValue && videoStream.Width.HasValue && - request.Width.Value > videoStream.Width.Value; + var isUpscaling = request.Height.HasValue + && videoStream.Height.HasValue + && request.Height.Value > videoStream.Height.Value + && request.Width.HasValue + && videoStream.Width.HasValue + && request.Width.Value > videoStream.Width.Value; // Don't allow bitrate increases unless upscaling - if (!isUpscaling) + if (!isUpscaling && bitrate.HasValue && videoStream.BitRate.HasValue) { - if (bitrate.HasValue && videoStream.BitRate.HasValue) - { - bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value); - } + bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value); } - } - - if (bitrate.HasValue) - { - var inputVideoCodec = videoStream.Codec; - bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec); - // If a max bitrate was requested, don't let the scaled bitrate exceed it - if (request.VideoBitRate.HasValue) + if (bitrate.HasValue) { - bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value); + var inputVideoCodec = videoStream.Codec; + bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec); + + // If a max bitrate was requested, don't let the scaled bitrate exceed it + if (request.VideoBitRate.HasValue) + { + bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value); + } } } @@ -1127,7 +1199,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (sourceBitrate <= 3000000) { - sourceBitrate = Convert.ToInt32(sourceBitrate * 2); + sourceBitrate *= 2; } var bitrate = Math.Min(sourceBitrate, requestedBitrate); @@ -1137,12 +1209,13 @@ namespace MediaBrowser.Controller.MediaEncoding private static double GetVideoBitrateScaleFactor(string codec) { - if (StringHelper.EqualsIgnoreCase(codec, "h265") || - StringHelper.EqualsIgnoreCase(codec, "hevc") || - StringHelper.EqualsIgnoreCase(codec, "vp9")) + if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) + || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)) { return .5; } + return 1; } @@ -1169,21 +1242,15 @@ namespace MediaBrowser.Controller.MediaEncoding scaleFactor = Math.Max(scaleFactor, 2); } - var newBitrate = scaleFactor * bitrate; - - return Convert.ToInt32(newBitrate); + return Convert.ToInt32(scaleFactor * bitrate); } public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream) { if (request.AudioBitRate.HasValue) { - // Make sure we don't request a bitrate higher than the source - var currentBitrate = audioStream == null ? request.AudioBitRate.Value : audioStream.BitRate ?? request.AudioBitRate.Value; - // Don't encode any higher than this return Math.Min(384000, request.AudioBitRate.Value); - //return Math.Min(currentBitrate, request.AudioBitRate.Value); } return null; @@ -1196,12 +1263,14 @@ namespace MediaBrowser.Controller.MediaEncoding var filters = new List<string>(); // Boost volume to 200% when downsampling from 6ch to 2ch - if (channels.HasValue && channels.Value <= 2) + if (channels.HasValue + && channels.Value <= 2 + && state.AudioStream != null + && state.AudioStream.Channels.HasValue + && state.AudioStream.Channels.Value > 5 + && !encodingOptions.DownMixAudioBoost.Equals(1)) { - if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5 && !encodingOptions.DownMixAudioBoost.Equals(1)) - { - filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture)); - } + filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture)); } var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive; @@ -1209,12 +1278,16 @@ namespace MediaBrowser.Controller.MediaEncoding { var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds; - filters.Add(string.Format("asetpts=PTS-{0}/TB", Math.Round(seconds).ToString(_usCulture))); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "asetpts=PTS-{0}/TB", + Math.Round(seconds))); } if (filters.Count > 0) { - return "-af \"" + string.Join(",", filters.ToArray()) + "\""; + return "-af \"" + string.Join(",", filters) + "\""; } return string.Empty; @@ -1231,18 +1304,17 @@ namespace MediaBrowser.Controller.MediaEncoding { var request = state.BaseRequest; - var inputChannels = audioStream == null - ? null - : audioStream.Channels; + var inputChannels = audioStream?.Channels; if (inputChannels <= 0) { inputChannels = null; } - int? transcoderChannelLimit = null; var codec = outputAudioCodec ?? string.Empty; + + int? transcoderChannelLimit; if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1) { // wmav2 currently only supports two channel output @@ -1291,6 +1363,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return val2; } + if (!val2.HasValue) { return val1; @@ -1364,7 +1437,10 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.VideoStream != null) { - args += string.Format("-map 0:{0}", state.VideoStream.Index); + args += string.Format( + CultureInfo.InvariantCulture, + "-map 0:{0}", + state.VideoStream.Index); } else { @@ -1374,7 +1450,10 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.AudioStream != null) { - args += string.Format(" -map 0:{0}", state.AudioStream.Index); + args += string.Format( + CultureInfo.InvariantCulture, + " -map 0:{0}", + state.AudioStream.Index); } else @@ -1389,7 +1468,10 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (subtitleMethod == SubtitleDeliveryMethod.Embed) { - args += string.Format(" -map 0:{0}", state.SubtitleStream.Index); + args += string.Format( + CultureInfo.InvariantCulture, + " -map 0:{0}", + state.SubtitleStream.Index); } else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream) { @@ -1441,7 +1523,10 @@ namespace MediaBrowser.Controller.MediaEncoding var request = state.BaseRequest; // Add resolution params, if specified - if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue) + if (request.Width.HasValue + || request.Height.HasValue + || request.MaxHeight.HasValue + || request.MaxWidth.HasValue) { outputSizeParam = GetOutputSizeParam(state, options, outputVideoCodec).TrimEnd('"'); @@ -1463,12 +1548,15 @@ namespace MediaBrowser.Controller.MediaEncoding } } - if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0) + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && outputSizeParam.Length == 0) { outputSizeParam = ",format=nv12|vaapi,hwupload"; // Add parameters to use VAAPI with burn-in subttiles (GH issue #642) - if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { + if (state.SubtitleStream != null + && state.SubtitleStream.IsTextSubtitleStream + && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode) { outputSizeParam += ",hwmap=mode=read+write+direct"; } } @@ -1477,7 +1565,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue) { - videoSizeParam = string.Format("scale={0}:{1}", state.VideoStream.Width.Value.ToString(_usCulture), state.VideoStream.Height.Value.ToString(_usCulture)); + videoSizeParam = string.Format( + CultureInfo.InvariantCulture, + "scale={0}:{1}", + state.VideoStream.Width.Value, + state.VideoStream.Height.Value); videoSizeParam += ":force_original_aspect_ratio=decrease"; } @@ -1490,15 +1582,18 @@ namespace MediaBrowser.Controller.MediaEncoding ? 0 : state.SubtitleStream.Index; - return string.Format(" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"", - mapPrefix.ToString(_usCulture), - subtitleStreamIndex.ToString(_usCulture), - state.VideoStream.Index.ToString(_usCulture), + return string.Format( + CultureInfo.InvariantCulture, + " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"", + mapPrefix, + subtitleStreamIndex, + state.VideoStream.Index, outputSizeParam, videoSizeParam); } - private ValueTuple<int?, int?> GetFixedOutputSize(int? videoWidth, + private (int? width, int? height) GetFixedOutputSize( + int? videoWidth, int? videoHeight, int? requestedWidth, int? requestedHeight, @@ -1507,11 +1602,11 @@ namespace MediaBrowser.Controller.MediaEncoding { if (!videoWidth.HasValue && !requestedWidth.HasValue) { - return new ValueTuple<int?, int?>(null, null); + return (null, null); } if (!videoHeight.HasValue && !requestedHeight.HasValue) { - return new ValueTuple<int?, int?>(null, null); + return (null, null); } decimal inputWidth = Convert.ToDecimal(videoWidth ?? requestedWidth); @@ -1531,7 +1626,7 @@ namespace MediaBrowser.Controller.MediaEncoding outputWidth = 2 * Math.Truncate(outputWidth / 2); outputHeight = 2 * Math.Truncate(outputHeight / 2); - return new ValueTuple<int?, int?>(Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); + return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight)); } public List<string> GetScalingFilters(int? videoWidth, @@ -1545,9 +1640,17 @@ namespace MediaBrowser.Controller.MediaEncoding int? requestedMaxHeight) { var filters = new List<string>(); - var fixedOutputSize = GetFixedOutputSize(videoWidth, videoHeight, requestedWidth, requestedHeight, requestedMaxWidth, requestedMaxHeight); - - if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && fixedOutputSize.Item1.HasValue && fixedOutputSize.Item2.HasValue) + var (width, height) = GetFixedOutputSize( + videoWidth, + videoHeight, + requestedWidth, + requestedHeight, + requestedMaxWidth, + requestedMaxHeight); + + if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) + && width.HasValue + && height.HasValue) { // Work around vaapi's reduced scaling features var scaler = "scale_vaapi"; @@ -1555,15 +1658,26 @@ namespace MediaBrowser.Controller.MediaEncoding // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions // (outputWidth, outputHeight). The user may request precise output dimensions or maximum // output dimensions. Output dimensions are guaranteed to be even. - var outputWidth = fixedOutputSize.Item1.Value; - var outputHeight = fixedOutputSize.Item2.Value; + var outputWidth = width.Value; + var outputHeight = height.Value; - if (!videoWidth.HasValue || outputWidth != videoWidth.Value || !videoHeight.HasValue || outputHeight != videoHeight.Value) + if (!videoWidth.HasValue + || outputWidth != videoWidth.Value + || !videoHeight.HasValue + || outputHeight != videoHeight.Value) { - filters.Add(string.Format("{0}=w={1}:h={2}", scaler, outputWidth.ToString(_usCulture), outputHeight.ToString(_usCulture))); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "{0}=w={1}:h={2}", + scaler, + outputWidth, + outputHeight)); } } - else if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1 && fixedOutputSize.Item1.HasValue && fixedOutputSize.Item2.HasValue) + else if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1 + && width.HasValue + && height.HasValue) { // Nothing to do, it's handled as an input resize filter } @@ -1579,7 +1693,12 @@ namespace MediaBrowser.Controller.MediaEncoding var widthParam = requestedWidth.Value.ToString(_usCulture); var heightParam = requestedHeight.Value.ToString(_usCulture); - filters.Add(string.Format("scale=trunc({0}/64)*64:trunc({1}/2)*2", widthParam, heightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc({0}/64)*64:trunc({1}/2)*2", + widthParam, + heightParam)); } else { @@ -1595,11 +1714,21 @@ namespace MediaBrowser.Controller.MediaEncoding if (isExynosV4L2) { - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/64)*64:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/64)*64:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", + maxWidthParam, + maxHeightParam)); } else { - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", maxWidthParam, maxHeightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2", + maxWidthParam, + maxHeightParam)); } } @@ -1615,7 +1744,11 @@ namespace MediaBrowser.Controller.MediaEncoding { var widthParam = requestedWidth.Value.ToString(_usCulture); - filters.Add(string.Format("scale={0}:trunc(ow/a/2)*2", widthParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale={0}:trunc(ow/a/2)*2", + widthParam)); } } @@ -1626,11 +1759,19 @@ namespace MediaBrowser.Controller.MediaEncoding if (isExynosV4L2) { - filters.Add(string.Format("scale=trunc(oh*a/64)*64:{0}", heightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(oh*a/64)*64:{0}", + heightParam)); } else { - filters.Add(string.Format("scale=trunc(oh*a/2)*2:{0}", heightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(oh*a/2)*2:{0}", + heightParam)); } } @@ -1641,11 +1782,19 @@ namespace MediaBrowser.Controller.MediaEncoding if (isExynosV4L2) { - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/64)*64:trunc(ow/dar/2)*2", maxWidthParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/64)*64:trunc(ow/dar/2)*2", + maxWidthParam)); } else { - filters.Add(string.Format("scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", maxWidthParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2", + maxWidthParam)); } } @@ -1656,11 +1805,19 @@ namespace MediaBrowser.Controller.MediaEncoding if (isExynosV4L2) { - filters.Add(string.Format("scale=trunc(oh*a/64)*64:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(oh*a/64)*64:min(max(iw/dar\\,ih)\\,{0})", + maxHeightParam)); } else { - filters.Add(string.Format("scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", maxHeightParam)); + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + "scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})", + maxHeightParam)); } } } @@ -1670,8 +1827,8 @@ namespace MediaBrowser.Controller.MediaEncoding private string GetFixedSizeScalingFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight) { - var widthParam = requestedWidth.ToString(_usCulture); - var heightParam = requestedHeight.ToString(_usCulture); + var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture); + var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture); string filter = null; @@ -1713,13 +1870,14 @@ namespace MediaBrowser.Controller.MediaEncoding } } - return string.Format(filter, widthParam, heightParam); + return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam); } /// <summary> /// If we're going to put a fixed size on the command line, this will calculate it /// </summary> - public string GetOutputSizeParam(EncodingJobInfo state, + public string GetOutputSizeParam( + EncodingJobInfo state, EncodingOptions options, string outputVideoCodec, bool allowTimeStampCopy = true) @@ -1728,24 +1886,45 @@ namespace MediaBrowser.Controller.MediaEncoding var request = state.BaseRequest; + var videoStream = state.VideoStream; var filters = new List<string>(); + // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first + var hwType = options.HardwareAccelerationType ?? string.Empty; + if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding ) + { + filters.Add("hwdownload"); + + // If transcoding from 10 bit, transform colour spaces too + if (!string.IsNullOrEmpty(videoStream.PixelFormat) + && videoStream.PixelFormat.IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1 + && string.Equals(outputVideoCodec,"libx264", StringComparison.OrdinalIgnoreCase)) + { + filters.Add("format=p010le"); + filters.Add("format=nv12"); + } + else + { + filters.Add("format=nv12"); + } + } + if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { filters.Add("format=nv12|vaapi"); filters.Add("hwupload"); } - if (state.DeInterlace("h264", true) && string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if (state.DeInterlace("h264", true) + && string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { - filters.Add(string.Format("deinterlace_vaapi")); + filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi")); } - var videoStream = state.VideoStream; - - if (state.DeInterlace("h264", true) && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if ((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)) + && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { - var inputFramerate = videoStream == null ? null : videoStream.RealFrameRate; + var inputFramerate = videoStream?.RealFrameRate; // If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle if (string.Equals(options.DeinterlaceMethod, "bobandweave", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30) @@ -1758,11 +1937,11 @@ namespace MediaBrowser.Controller.MediaEncoding } } - var inputWidth = videoStream == null ? null : videoStream.Width; - var inputHeight = videoStream == null ? null : videoStream.Height; + var inputWidth = videoStream?.Width; + var inputHeight = videoStream?.Height; var threeDFormat = state.MediaSource.Video3DFormat; - var videoDecoder = this.GetHardwareAcceleratedVideoDecoder(state, options); + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options); filters.AddRange(GetScalingFilters(inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight)); @@ -1780,6 +1959,7 @@ namespace MediaBrowser.Controller.MediaEncoding { filters.Add("hwmap"); } + if (allowTimeStampCopy) { output += " -copyts"; @@ -1788,7 +1968,10 @@ namespace MediaBrowser.Controller.MediaEncoding if (filters.Count > 0) { - output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray())); + output += string.Format( + CultureInfo.InvariantCulture, + " -vf \"{0}\"", + string.Join(",", filters)); } return output; @@ -1836,7 +2019,8 @@ namespace MediaBrowser.Controller.MediaEncoding } } - if (state.AudioStream != null && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)) + if (state.AudioStream != null + && CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs)) { state.OutputAudioCodec = "copy"; } @@ -1853,14 +2037,10 @@ namespace MediaBrowser.Controller.MediaEncoding } public static string GetProbeSizeArgument(int numInputFiles) - { - return numInputFiles > 1 ? "-probesize 1G" : ""; - } + => numInputFiles > 1 ? "-probesize 1G" : ""; public static string GetAnalyzeDurationArgument(int numInputFiles) - { - return numInputFiles > 1 ? "-analyzeduration 200M" : ""; - } + => numInputFiles > 1 ? "-analyzeduration 200M" : ""; public string GetInputModifier(EncodingJobInfo state, EncodingOptions encodingOptions) { @@ -1928,18 +2108,22 @@ namespace MediaBrowser.Controller.MediaEncoding { flags.Add("+igndts"); } + if (state.IgnoreInputIndex) { flags.Add("+ignidx"); } + if (state.GenPtsInput || string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { flags.Add("+genpts"); } + if (state.DiscardCorruptFramesInput) { flags.Add("+discardcorrupt"); } + if (state.EnableFastSeekInput) { flags.Add("+fastseek"); @@ -1947,24 +2131,33 @@ namespace MediaBrowser.Controller.MediaEncoding if (flags.Count > 0) { - inputModifier += " -fflags " + string.Join("", flags.ToArray()); + inputModifier += " -fflags " + string.Join(string.Empty, flags); } - var videoDecoder = this.GetHardwareAcceleratedVideoDecoder(state, encodingOptions); + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions); if (!string.IsNullOrEmpty(videoDecoder)) { inputModifier += " " + videoDecoder; - var videoStream = state.VideoStream; - var inputWidth = videoStream == null ? null : videoStream.Width; - var inputHeight = videoStream == null ? null : videoStream.Height; - var request = state.BaseRequest; + if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1) + { + var videoStream = state.VideoStream; + var inputWidth = videoStream?.Width; + var inputHeight = videoStream?.Height; + var request = state.BaseRequest; - var fixedOutputSize = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); + var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight); - if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1 && fixedOutputSize.Item1.HasValue && fixedOutputSize.Item2.HasValue) - { - inputModifier += string.Format(" -resize {0}x{1}", fixedOutputSize.Item1.Value.ToString(_usCulture), fixedOutputSize.Item2.Value.ToString(_usCulture)); + if ((videoDecoder ?? string.Empty).IndexOf("_cuvid", StringComparison.OrdinalIgnoreCase) != -1 + && width.HasValue + && height.HasValue) + { + inputModifier += string.Format( + CultureInfo.InvariantCulture, + " -resize {0}x{1}", + width.Value, + height.Value); + } } } @@ -1973,9 +2166,9 @@ namespace MediaBrowser.Controller.MediaEncoding var outputVideoCodec = GetVideoEncoder(state, encodingOptions); // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking - if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) && - state.TranscodingType != TranscodingJobType.Progressive && - state.EnableBreakOnNonKeyFrames(outputVideoCodec)) + if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase) + && state.TranscodingType != TranscodingJobType.Progressive + && state.EnableBreakOnNonKeyFrames(outputVideoCodec)) { inputModifier += " -noaccurate_seek"; } @@ -1999,14 +2192,16 @@ namespace MediaBrowser.Controller.MediaEncoding } - public void AttachMediaSourceInfo(EncodingJobInfo state, - MediaSourceInfo mediaSource, - string requestedUrl) + public void AttachMediaSourceInfo( + EncodingJobInfo state, + MediaSourceInfo mediaSource, + string requestedUrl) { if (state == null) { throw new ArgumentNullException(nameof(state)); } + if (mediaSource == null) { throw new ArgumentNullException(nameof(mediaSource)); @@ -2064,15 +2259,16 @@ namespace MediaBrowser.Controller.MediaEncoding state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate; - if (state.ReadInputAtNativeFramerate || - mediaSource.Protocol == MediaProtocol.File && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)) + if (state.ReadInputAtNativeFramerate + || mediaSource.Protocol == MediaProtocol.File + && string.Equals(mediaSource.Container, "wtv", StringComparison.OrdinalIgnoreCase)) { state.InputVideoSync = "-1"; state.InputAudioSync = "1"; } - if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase) || - string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(mediaSource.Container, "wma", StringComparison.OrdinalIgnoreCase) + || string.Equals(mediaSource.Container, "asf", StringComparison.OrdinalIgnoreCase)) { // Seeing some stuttering when transcoding wma to audio-only HLS state.InputAudioSync = "1"; @@ -2184,7 +2380,7 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } - return this.GetHardwareAcceleratedVideoDecoder(state.MediaSource.VideoType ?? VideoType.VideoFile, state.VideoStream, encodingOptions); + return GetHardwareAcceleratedVideoDecoder(state.MediaSource.VideoType ?? VideoType.VideoFile, state.VideoStream, encodingOptions); } public string GetHardwareAcceleratedVideoDecoder(VideoType videoType, MediaStream videoStream, EncodingOptions encodingOptions) @@ -2197,9 +2393,9 @@ namespace MediaBrowser.Controller.MediaEncoding return null; } - if (videoStream != null && - !string.IsNullOrEmpty(videoStream.Codec) && - !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) + if (videoStream != null + && !string.IsNullOrEmpty(videoStream.Codec) + && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType)) { if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { @@ -2349,7 +2545,10 @@ namespace MediaBrowser.Controller.MediaEncoding { if (Environment.OSVersion.Platform == PlatformID.Win32NT) { - return "-hwaccel dxva2"; + if(Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1)) + return "-hwaccel d3d11va"; + else + return "-hwaccel dxva2"; } switch (videoStream.Codec.ToLowerInvariant()) @@ -2397,14 +2596,10 @@ namespace MediaBrowser.Controller.MediaEncoding codec = format; } - var args = " -codec:s:0 " + codec; - - args += " -disposition:s:0 default"; - - return args; + return " -codec:s:0 " + codec + " -disposition:s:0 default"; } - public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultH264Preset) + public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultPreset) { // Get the output codec name var videoCodec = GetVideoEncoder(state, encodingOptions); @@ -2412,8 +2607,8 @@ namespace MediaBrowser.Controller.MediaEncoding var format = string.Empty; var keyFrame = string.Empty; - if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase) && - state.BaseRequest.Context == EncodingContext.Streaming) + if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase) + && state.BaseRequest.Context == EncodingContext.Streaming) { // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js format = " -f mp4 -movflags frag_keyframe+empty_moov"; @@ -2423,18 +2618,19 @@ namespace MediaBrowser.Controller.MediaEncoding var inputModifier = GetInputModifier(state, encodingOptions); - return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"", + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1}{2} {3} {4} -map_metadata -1 -map_chapters -1 -threads {5} {6}{7}{8} -y \"{9}\"", inputModifier, GetInputArgument(state, encodingOptions), keyFrame, GetMapArgs(state), - GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultH264Preset), + GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset), threads, GetProgressiveVideoAudioArguments(state, encodingOptions), GetSubtitleEmbedArguments(state), format, - outputPath - ).Trim(); + outputPath).Trim(); } public string GetOutputFFlags(EncodingJobInfo state) @@ -2447,13 +2643,13 @@ namespace MediaBrowser.Controller.MediaEncoding if (flags.Count > 0) { - return " -fflags " + string.Join("", flags.ToArray()); + return " -fflags " + string.Join("", flags); } return string.Empty; } - public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultH264Preset) + public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultPreset) { var args = "-codec:v:0 " + videoCodec; @@ -2464,11 +2660,15 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - if (state.VideoStream != null && IsH264(state.VideoStream) && - string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && - !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) + if (state.VideoStream != null + && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) + && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - args += " -bsf:v h264_mp4toannexb"; + string bitStreamArgs = GetBitStreamArgs(state.VideoStream); + if (!string.IsNullOrEmpty(bitStreamArgs)) + { + args += " " + bitStreamArgs; + } } if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) @@ -2483,8 +2683,10 @@ namespace MediaBrowser.Controller.MediaEncoding } else { - var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", - 5.ToString(_usCulture)); + var keyFrameArg = string.Format( + CultureInfo.InvariantCulture, + " -force_key_frames \"expr:gte(t,n_forced*{0})\"", + 5); args += keyFrameArg; @@ -2505,6 +2707,7 @@ namespace MediaBrowser.Controller.MediaEncoding { args += " -copyts"; } + args += " -avoid_negative_ts disabled -start_at_zero"; } @@ -2514,7 +2717,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec); } - var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset); + var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset); if (!string.IsNullOrEmpty(qualityParam)) { @@ -2605,39 +2808,22 @@ namespace MediaBrowser.Controller.MediaEncoding } } - var albumCoverInput = string.Empty; - var mapArgs = string.Empty; - var metadata = string.Empty; - var vn = string.Empty; - - var hasArt = !string.IsNullOrEmpty(state.AlbumCoverPath); - hasArt = false; - - if (hasArt) - { - albumCoverInput = " -i \"" + state.AlbumCoverPath + "\""; - mapArgs = " -map 0:a -map 1:v -c:1:v copy"; - metadata = " -metadata:s:v title=\"Album cover\" -metadata:s:v comment=\"Cover(Front)\""; - } - else - { - vn = " -vn"; - } - var threads = GetNumberOfThreads(state, encodingOptions, null); var inputModifier = GetInputModifier(state, encodingOptions); - return string.Format("{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"", + return string.Format( + CultureInfo.InvariantCulture, + "{0} {1}{7}{8} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1{6} -y \"{5}\"", inputModifier, GetInputArgument(state, encodingOptions), threads, - vn, - string.Join(" ", audioTranscodeParams.ToArray()), + " -vn", + string.Join(" ", audioTranscodeParams), outputPath, - metadata, - albumCoverInput, - mapArgs).Trim(); + string.Empty, + string.Empty, + string.Empty).Trim(); } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index be97c75a2..d64feb2f7 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -118,14 +118,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// Gets or sets the profile. /// </summary> /// <value>The profile.</value> - [ApiMember(Name = "Profile", Description = "Optional. Specify a specific h264 profile, e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "Profile", Description = "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string Profile { get; set; } /// <summary> /// Gets or sets the level. /// </summary> /// <value>The level.</value> - [ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "Level", Description = "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string Level { get; set; } /// <summary> @@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Gets or sets the video codec. /// </summary> /// <value>The video codec.</value> - [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string VideoCodec { get; set; } public string SubtitleCodec { get; set; } diff --git a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs index 7f842c1d0..5cedc3d57 100644 --- a/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs +++ b/MediaBrowser.Controller/MediaEncoding/MediaEncoderHelpers.cs @@ -1,8 +1,8 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using MediaBrowser.Model.IO; -using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Controller.MediaEncoding { @@ -16,31 +16,26 @@ namespace MediaBrowser.Controller.MediaEncoding /// </summary> /// <param name="fileSystem">The file system.</param> /// <param name="videoPath">The video path.</param> - /// <param name="protocol">The protocol.</param> /// <param name="isoMount">The iso mount.</param> /// <param name="playableStreamFileNames">The playable stream file names.</param> - /// <returns>System.String[][].</returns> - public static string[] GetInputArgument(IFileSystem fileSystem, string videoPath, MediaProtocol protocol, IIsoMount isoMount, string[] playableStreamFileNames) + /// <returns>string[].</returns> + public static string[] GetInputArgument(IFileSystem fileSystem, string videoPath, IIsoMount isoMount, IReadOnlyCollection<string> playableStreamFileNames) { - if (playableStreamFileNames.Length > 0) + if (playableStreamFileNames.Count > 0) { if (isoMount == null) { return GetPlayableStreamFiles(fileSystem, videoPath, playableStreamFileNames); } + return GetPlayableStreamFiles(fileSystem, isoMount.MountedPath, playableStreamFileNames); } return new[] { videoPath }; } - private static string[] GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, string[] filenames) + private static string[] GetPlayableStreamFiles(IFileSystem fileSystem, string rootPath, IEnumerable<string> filenames) { - if (filenames.Length == 0) - { - return new string[] { }; - } - var allFiles = fileSystem .GetFilePaths(rootPath, true) .ToArray(); diff --git a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs index 64c2294e3..29fb81e32 100644 --- a/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs +++ b/MediaBrowser.Controller/Net/AuthenticatedAttribute.cs @@ -1,5 +1,6 @@ using System; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { @@ -33,7 +34,7 @@ namespace MediaBrowser.Controller.Net /// <param name="request">The http request wrapper</param> /// <param name="response">The http response wrapper</param> /// <param name="requestDto">The request DTO</param> - public void RequestFilter(IRequest request, IResponse response, object requestDto) + public void RequestFilter(IRequest request, HttpResponse response, object requestDto) { AuthService.Authenticate(request, this); } diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index e83260725..b45043f92 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Dto; @@ -9,7 +11,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; -using MediaBrowser.Model.Serialization; namespace MediaBrowser.Controller.Playlists { @@ -33,7 +34,7 @@ namespace MediaBrowser.Controller.Playlists Shares = Array.Empty<Share>(); } - [IgnoreDataMember] + [JsonIgnore] public bool IsFile => IsPlaylistFile(Path); public static bool IsPlaylistFile(string path) @@ -41,7 +42,7 @@ namespace MediaBrowser.Controller.Playlists return System.IO.Path.HasExtension(path); } - [IgnoreDataMember] + [JsonIgnore] public override string ContainingFolderPath { get @@ -57,19 +58,19 @@ namespace MediaBrowser.Controller.Playlists } } - [IgnoreDataMember] + [JsonIgnore] protected override bool FilterLinkedChildrenPerUser => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsInheritedParentImages => false; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsPlayedStatus => string.Equals(MediaType, "Video", StringComparison.OrdinalIgnoreCase); - [IgnoreDataMember] + [JsonIgnore] public override bool AlwaysScanInternalMetadataPath => true; - [IgnoreDataMember] + [JsonIgnore] public override bool SupportsCumulativeRunTimeTicks => true; public override double GetDefaultPrimaryImageAspectRatio() @@ -192,12 +193,12 @@ namespace MediaBrowser.Controller.Playlists return new[] { item }; } - [IgnoreDataMember] + [JsonIgnore] public override bool IsPreSorted => true; public string PlaylistMediaType { get; set; } - [IgnoreDataMember] + [JsonIgnore] public override string MediaType => PlaylistMediaType; public void SetMediaType(string value) @@ -205,7 +206,7 @@ namespace MediaBrowser.Controller.Playlists PlaylistMediaType = value; } - [IgnoreDataMember] + [JsonIgnore] private bool IsSharedItem { get @@ -239,7 +240,7 @@ namespace MediaBrowser.Controller.Playlists return base.IsVisible(user); } - var userId = user.Id.ToString("N"); + var userId = user.Id.ToString("N", CultureInfo.InvariantCulture); foreach (var share in shares) { if (string.Equals(share.UserId, userId, StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Controller/Providers/AlbumInfo.cs b/MediaBrowser.Controller/Providers/AlbumInfo.cs index b0b443fc0..ac6b86c1d 100644 --- a/MediaBrowser.Controller/Providers/AlbumInfo.cs +++ b/MediaBrowser.Controller/Providers/AlbumInfo.cs @@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Providers /// Gets or sets the album artist. /// </summary> /// <value>The album artist.</value> - public string[] AlbumArtists { get; set; } + public IReadOnlyList<string> AlbumArtists { get; set; } /// <summary> /// Gets or sets the artist provider ids. diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index f4b915c06..ebff81b7f 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Providers @@ -55,7 +56,7 @@ namespace MediaBrowser.Controller.Providers foreach (var i in UserDataList) { - if (string.Equals(userId, i.UserId.ToString("N"), StringComparison.OrdinalIgnoreCase)) + if (string.Equals(userId, i.UserId.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase)) { userData = i; } diff --git a/MediaBrowser.Controller/Providers/MusicVideoInfo.cs b/MediaBrowser.Controller/Providers/MusicVideoInfo.cs index 194b26484..9835351fc 100644 --- a/MediaBrowser.Controller/Providers/MusicVideoInfo.cs +++ b/MediaBrowser.Controller/Providers/MusicVideoInfo.cs @@ -1,7 +1,9 @@ +using System.Collections.Generic; + namespace MediaBrowser.Controller.Providers { public class MusicVideoInfo : ItemLookupInfo { - public string[] Artists { get; set; } + public IReadOnlyList<string> Artists { get; set; } } } diff --git a/MediaBrowser.Controller/Providers/SongInfo.cs b/MediaBrowser.Controller/Providers/SongInfo.cs index 61e950130..50615b0bd 100644 --- a/MediaBrowser.Controller/Providers/SongInfo.cs +++ b/MediaBrowser.Controller/Providers/SongInfo.cs @@ -1,12 +1,15 @@ using System; +using System.Collections.Generic; namespace MediaBrowser.Controller.Providers { public class SongInfo : ItemLookupInfo { - public string[] AlbumArtists { get; set; } + public IReadOnlyList<string> AlbumArtists { get; set; } + public string Album { get; set; } - public string[] Artists { get; set; } + + public IReadOnlyList<string> Artists { get; set; } public SongInfo() { diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index f0e81e8e7..acda6a416 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -1,9 +1,9 @@ using System; using System.Linq; +using System.Text.Json.Serialization; using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Session; using Microsoft.Extensions.Logging; @@ -123,7 +123,7 @@ namespace MediaBrowser.Controller.Session /// Gets or sets the session controller. /// </summary> /// <value>The session controller.</value> - [IgnoreDataMember] + [JsonIgnore] public ISessionController[] SessionControllers { get; set; } /// <summary> |
