mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-20 00:55:13 +01:00
Merge branch 'master' into syncplay-enhanced
This commit is contained in:
86
MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
Normal file
86
MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
||||
namespace MediaBrowser.Controller.BaseItemManager
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class BaseItemManager : IBaseItemManager
|
||||
{
|
||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseItemManager"/> class.
|
||||
/// </summary>
|
||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||
public BaseItemManager(IServerConfigurationManager serverConfigurationManager)
|
||||
{
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name)
|
||||
{
|
||||
if (baseItem is Channel)
|
||||
{
|
||||
// Hack alert.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (baseItem.SourceType == SourceType.Channel)
|
||||
{
|
||||
// Hack alert.
|
||||
return !baseItem.EnableMediaSourceDisplay;
|
||||
}
|
||||
|
||||
var typeOptions = libraryOptions.GetTypeOptions(GetType().Name);
|
||||
if (typeOptions != null)
|
||||
{
|
||||
return typeOptions.ImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (!libraryOptions.EnableInternetProviders)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name)
|
||||
{
|
||||
if (baseItem is Channel)
|
||||
{
|
||||
// Hack alert.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (baseItem.SourceType == SourceType.Channel)
|
||||
{
|
||||
// Hack alert.
|
||||
return !baseItem.EnableMediaSourceDisplay;
|
||||
}
|
||||
|
||||
var typeOptions = libraryOptions.GetTypeOptions(GetType().Name);
|
||||
if (typeOptions != null)
|
||||
{
|
||||
return typeOptions.ImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (!libraryOptions.EnableInternetProviders)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
Normal file
29
MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
|
||||
namespace MediaBrowser.Controller.BaseItemManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The <c>BaseItem</c> manager.
|
||||
/// </summary>
|
||||
public interface IBaseItemManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Is metadata fetcher enabled.
|
||||
/// </summary>
|
||||
/// <param name="baseItem">The base item.</param>
|
||||
/// <param name="libraryOptions">The library options.</param>
|
||||
/// <param name="name">The metadata fetcher name.</param>
|
||||
/// <returns><c>true</c> if metadata fetcher is enabled, else false.</returns>
|
||||
bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
|
||||
|
||||
/// <summary>
|
||||
/// Is image fetcher enabled.
|
||||
/// </summary>
|
||||
/// <param name="baseItem">The base item.</param>
|
||||
/// <param name="libraryOptions">The library options.</param>
|
||||
/// <param name="name">The image fetcher name.</param>
|
||||
/// <returns><c>true</c> if image fetcher is enabled, else false.</returns>
|
||||
bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Querying;
|
||||
@@ -15,9 +16,9 @@ namespace MediaBrowser.Controller.Dto
|
||||
ItemFields.RefreshState
|
||||
};
|
||||
|
||||
public ItemFields[] Fields { get; set; }
|
||||
public IReadOnlyList<ItemFields> Fields { get; set; }
|
||||
|
||||
public ImageType[] ImageTypes { get; set; }
|
||||
public IReadOnlyList<ImageType> ImageTypes { get; set; }
|
||||
|
||||
public int ImageTypeLimit { get; set; }
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
{
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Audio), nameof(MusicVideo), nameof(MusicAlbum) };
|
||||
query.ArtistIds = new[] { Id };
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
|
||||
{
|
||||
query.GenreIds = new[] { Id };
|
||||
query.IncludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(MusicVideo), nameof(Audio), nameof(MusicAlbum), nameof(MusicArtist) };
|
||||
|
||||
return LibraryManager.GetItemList(query);
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
public const string InterviewFolderName = "interviews";
|
||||
public const string SceneFolderName = "scenes";
|
||||
public const string SampleFolderName = "samples";
|
||||
public const string ShortsFolderName = "shorts";
|
||||
public const string FeaturettesFolderName = "featurettes";
|
||||
|
||||
public static readonly string[] AllExtrasTypesFolderNames = {
|
||||
ExtrasFolderName,
|
||||
@@ -94,7 +96,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
DeletedScenesFolderName,
|
||||
InterviewFolderName,
|
||||
SceneFolderName,
|
||||
SampleFolderName
|
||||
SampleFolderName,
|
||||
ShortsFolderName,
|
||||
FeaturettesFolderName
|
||||
};
|
||||
|
||||
[JsonIgnore]
|
||||
@@ -459,60 +463,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
[JsonIgnore]
|
||||
public string PrimaryImagePath => this.GetImagePath(ImageType.Primary);
|
||||
|
||||
public bool IsMetadataFetcherEnabled(LibraryOptions libraryOptions, string name)
|
||||
{
|
||||
if (SourceType == SourceType.Channel)
|
||||
{
|
||||
// hack alert
|
||||
return !EnableMediaSourceDisplay;
|
||||
}
|
||||
|
||||
var typeOptions = libraryOptions.GetTypeOptions(GetType().Name);
|
||||
if (typeOptions != null)
|
||||
{
|
||||
return typeOptions.MetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (!libraryOptions.EnableInternetProviders)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var itemConfig = ConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return itemConfig == null || !itemConfig.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public bool IsImageFetcherEnabled(LibraryOptions libraryOptions, string name)
|
||||
{
|
||||
if (this is Channel)
|
||||
{
|
||||
// hack alert
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SourceType == SourceType.Channel)
|
||||
{
|
||||
// hack alert
|
||||
return !EnableMediaSourceDisplay;
|
||||
}
|
||||
|
||||
var typeOptions = libraryOptions.GetTypeOptions(GetType().Name);
|
||||
if (typeOptions != null)
|
||||
{
|
||||
return typeOptions.ImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
if (!libraryOptions.EnableInternetProviders)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var itemConfig = ConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public virtual bool CanDelete()
|
||||
{
|
||||
if (SourceType == SourceType.Channel)
|
||||
@@ -1435,7 +1385,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
new List<FileSystemMetadata>();
|
||||
|
||||
var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false);
|
||||
await LibraryManager.UpdateImagesAsync(this).ConfigureAwait(false); // ensure all image properties in DB are fresh
|
||||
|
||||
if (ownedItemsChanged)
|
||||
{
|
||||
@@ -2607,7 +2556,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
if (!AllowsMultipleImages(type))
|
||||
{
|
||||
throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots");
|
||||
throw new ArgumentException("The change index operation is only applicable to backdrops and screen shots");
|
||||
}
|
||||
|
||||
var info1 = GetImageInfo(type, index1);
|
||||
|
||||
@@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
/// <summary>
|
||||
/// Loads our children. Validation will occur externally.
|
||||
/// We want this sychronous.
|
||||
/// We want this synchronous.
|
||||
/// </summary>
|
||||
protected virtual List<BaseItem> LoadChildren()
|
||||
{
|
||||
@@ -353,11 +353,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
await currentChild.UpdateToRepositoryAsync(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// metadata is up-to-date; make sure DB has correct images dimensions and hash
|
||||
await LibraryManager.UpdateImagesAsync(currentChild).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -723,7 +718,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
private bool RequiresPostFiltering2(InternalItemsQuery query)
|
||||
{
|
||||
if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], typeof(BoxSet).Name, StringComparison.OrdinalIgnoreCase))
|
||||
if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Logger.LogDebug("Query requires post-filtering due to BoxSet query");
|
||||
return true;
|
||||
@@ -813,7 +808,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (query.IsPlayed.HasValue)
|
||||
{
|
||||
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(typeof(Series).Name))
|
||||
if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(nameof(Series)))
|
||||
{
|
||||
Logger.LogDebug("Query requires post-filtering due to IsPlayed");
|
||||
return true;
|
||||
@@ -1067,12 +1062,12 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.Genres.Length > 0)
|
||||
if (request.Genres.Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.GenreIds.Length > 0)
|
||||
if (request.GenreIds.Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1177,7 +1172,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.GenreIds.Length > 0)
|
||||
if (request.GenreIds.Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,13 @@ namespace MediaBrowser.Controller.Entities
|
||||
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
|
||||
{
|
||||
query.GenreIds = new[] { Id };
|
||||
query.ExcludeItemTypes = new[] { typeof(MusicVideo).Name, typeof(Audio.Audio).Name, typeof(MusicAlbum).Name, typeof(MusicArtist).Name };
|
||||
query.ExcludeItemTypes = new[]
|
||||
{
|
||||
nameof(MusicVideo),
|
||||
nameof(Entities.Audio.Audio),
|
||||
nameof(MusicAlbum),
|
||||
nameof(MusicArtist)
|
||||
};
|
||||
|
||||
return LibraryManager.GetItemList(query);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public string[] ExcludeInheritedTags { get; set; }
|
||||
|
||||
public string[] Genres { get; set; }
|
||||
public IReadOnlyList<string> Genres { get; set; }
|
||||
|
||||
public bool? IsSpecialSeason { get; set; }
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public Guid[] StudioIds { get; set; }
|
||||
|
||||
public Guid[] GenreIds { get; set; }
|
||||
public IReadOnlyList<Guid> GenreIds { get; set; }
|
||||
|
||||
public ImageType[] ImageTypes { get; set; }
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public double? MinCommunityRating { get; set; }
|
||||
|
||||
public Guid[] ChannelIds { get; set; }
|
||||
public IReadOnlyList<Guid> ChannelIds { get; set; }
|
||||
|
||||
public int? ParentIndexNumber { get; set; }
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using Jellyfin.Data.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
@@ -23,6 +24,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public string NameContains { get; set; }
|
||||
|
||||
public User User { get; set; }
|
||||
|
||||
public bool? IsFavorite { get; set; }
|
||||
|
||||
public InternalPeopleQuery()
|
||||
{
|
||||
PersonTypes = Array.Empty<string>();
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
}
|
||||
|
||||
query.IsVirtualItem = false;
|
||||
@@ -207,7 +207,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
query.AncestorWithPresentationUniqueKey = null;
|
||||
query.SeriesPresentationUniqueKey = seriesKey;
|
||||
query.IncludeItemTypes = new[] { typeof(Season).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Season) };
|
||||
query.OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray();
|
||||
|
||||
if (user != null && !user.DisplayMissingEpisodes)
|
||||
@@ -233,7 +233,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode), nameof(Season) };
|
||||
}
|
||||
|
||||
query.IsVirtualItem = false;
|
||||
@@ -253,7 +253,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
{
|
||||
AncestorWithPresentationUniqueKey = null,
|
||||
SeriesPresentationUniqueKey = seriesKey,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode), nameof(Season) },
|
||||
OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
|
||||
DtoOptions = options
|
||||
};
|
||||
@@ -364,7 +364,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
{
|
||||
AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey,
|
||||
SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null,
|
||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||
IncludeItemTypes = new[] { nameof(Episode) },
|
||||
OrderBy = new[] { ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
|
||||
DtoOptions = options
|
||||
};
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (query.IncludeItemTypes.Length == 0)
|
||||
{
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
}
|
||||
|
||||
return parent.QueryRecursive(query);
|
||||
@@ -167,7 +167,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -178,7 +178,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -189,7 +189,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.IsFavorite = true;
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -200,7 +200,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -208,7 +208,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
private QueryResult<BaseItem> GetMovieCollections(Folder parent, User user, InternalItemsQuery query)
|
||||
{
|
||||
query.Parent = null;
|
||||
query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(BoxSet) };
|
||||
query.SetUser(user);
|
||||
query.Recursive = true;
|
||||
|
||||
@@ -223,7 +223,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
}
|
||||
@@ -236,7 +236,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
}
|
||||
@@ -255,7 +255,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
var genres = parent.QueryRecursive(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Movie).Name },
|
||||
IncludeItemTypes = new[] { nameof(Movie) },
|
||||
Recursive = true,
|
||||
EnableTotalRecordCount = false
|
||||
}).Items
|
||||
@@ -286,7 +286,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.GenreIds = new[] { displayParent.Id };
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Movie).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Movie) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -333,7 +333,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
query.IsVirtualItem = false;
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
@@ -362,7 +362,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
query.Limit = GetSpecialItemsLimit();
|
||||
query.IncludeItemTypes = new[] { typeof(Episode).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Episode) };
|
||||
|
||||
return ConvertToResult(_libraryManager.GetItemList(query));
|
||||
}
|
||||
@@ -373,7 +373,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.Parent = parent;
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -382,7 +382,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
var genres = parent.QueryRecursive(new InternalItemsQuery(user)
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||
IncludeItemTypes = new[] { nameof(Series) },
|
||||
Recursive = true,
|
||||
EnableTotalRecordCount = false
|
||||
}).Items
|
||||
@@ -413,7 +413,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
query.GenreIds = new[] { displayParent.Id };
|
||||
query.SetUser(user);
|
||||
|
||||
query.IncludeItemTypes = new[] { typeof(Series).Name };
|
||||
query.IncludeItemTypes = new[] { nameof(Series) };
|
||||
|
||||
return _libraryManager.GetItemsResult(query);
|
||||
}
|
||||
@@ -791,7 +791,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
// Apply genre filter
|
||||
if (query.Genres.Length > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))
|
||||
if (query.Genres.Count > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -822,7 +822,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
// Apply genre filter
|
||||
if (query.GenreIds.Length > 0 && !query.GenreIds.Any(id =>
|
||||
if (query.GenreIds.Count > 0 && !query.GenreIds.Any(id =>
|
||||
{
|
||||
var genreItem = libraryManager.GetItemById(id);
|
||||
return genreItem != null && item.Genres.Contains(genreItem.Name, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace MediaBrowser.Controller
|
||||
/// <summary>
|
||||
/// Gets the display preferences for the user and client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will create the display preferences if it does not exist, but it will not save automatically.
|
||||
/// </remarks>
|
||||
/// <param name="userId">The user's id.</param>
|
||||
/// <param name="client">The client string.</param>
|
||||
/// <returns>The associated display preferences.</returns>
|
||||
@@ -20,6 +23,9 @@ namespace MediaBrowser.Controller
|
||||
/// <summary>
|
||||
/// Gets the default item display preferences for the user and client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This will create the item display preferences if it does not exist, but it will not save automatically.
|
||||
/// </remarks>
|
||||
/// <param name="userId">The user id.</param>
|
||||
/// <param name="itemId">The item id.</param>
|
||||
/// <param name="client">The client string.</param>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace MediaBrowser.Controller
|
||||
{
|
||||
public interface IResourceFileManager
|
||||
{
|
||||
string GetResourcePath(string basePath, string virtualPath);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
@@ -56,41 +57,43 @@ namespace MediaBrowser.Controller
|
||||
/// <summary>
|
||||
/// Gets the system info.
|
||||
/// </summary>
|
||||
/// <param name="source">The originator of the request.</param>
|
||||
/// <returns>SystemInfo.</returns>
|
||||
Task<SystemInfo> GetSystemInfo(CancellationToken cancellationToken);
|
||||
SystemInfo GetSystemInfo(IPAddress source);
|
||||
|
||||
Task<PublicSystemInfo> GetPublicSystemInfo(CancellationToken cancellationToken);
|
||||
PublicSystemInfo GetPublicSystemInfo(IPAddress address);
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the local IP addresses of this API instance. Each address is validated by sending a 'ping' request
|
||||
/// to the API that should exist at the address.
|
||||
/// Gets a URL specific for the request.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param>
|
||||
/// <returns>A list containing all the local IP addresses of the server.</returns>
|
||||
Task<List<IPAddress>> GetLocalIpAddresses(CancellationToken cancellationToken);
|
||||
/// <param name="request">The <see cref="HttpRequest"/> instance.</param>
|
||||
/// <param name="port">Optional port number.</param>
|
||||
/// <returns>An accessible URL.</returns>
|
||||
string GetSmartApiUrl(HttpRequest request, int? port = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a local (LAN) URL that can be used to access the API. The hostname used is the first valid configured
|
||||
/// IP address that can be found via <see cref="GetLocalIpAddresses"/>. HTTPS will be preferred when available.
|
||||
/// Gets a URL specific for the request.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A cancellation token that can be used to cancel the task.</param>
|
||||
/// <returns>The server URL.</returns>
|
||||
Task<string> GetLocalApiUrl(CancellationToken cancellationToken);
|
||||
/// <param name="remoteAddr">The remote <see cref="IPAddress"/> of the connection.</param>
|
||||
/// <param name="port">Optional port number.</param>
|
||||
/// <returns>An accessible URL.</returns>
|
||||
string GetSmartApiUrl(IPAddress remoteAddr, int? port = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localhost URL that can be used to access the API using the loop-back IP address (127.0.0.1)
|
||||
/// Gets a URL specific for the request.
|
||||
/// </summary>
|
||||
/// <param name="hostname">The hostname used in the connection.</param>
|
||||
/// <param name="port">Optional port number.</param>
|
||||
/// <returns>An accessible URL.</returns>
|
||||
string GetSmartApiUrl(string hostname, int? port = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a localhost URL that can be used to access the API using the loop-back IP address.
|
||||
/// over HTTP (not HTTPS).
|
||||
/// </summary>
|
||||
/// <returns>The API URL.</returns>
|
||||
string GetLoopbackHttpApiUrl();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a local (LAN) URL that can be used to access the API. HTTPS will be preferred when available.
|
||||
/// </summary>
|
||||
/// <param name="address">The IP address to use as the hostname in the URL.</param>
|
||||
/// <returns>The API URL.</returns>
|
||||
string GetLocalApiUrl(IPAddress address);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a local (LAN) URL that can be used to access the API.
|
||||
/// Note: if passing non-null scheme or port it is up to the caller to ensure they form the correct pair.
|
||||
@@ -105,7 +108,7 @@ namespace MediaBrowser.Controller
|
||||
/// preferring the HTTPS port, if available.
|
||||
/// </param>
|
||||
/// <returns>The API URL.</returns>
|
||||
string GetLocalApiUrl(ReadOnlySpan<char> hostname, string scheme = null, int? port = null);
|
||||
string GetLocalApiUrl(string hostname, string scheme = null, int? port = null);
|
||||
|
||||
/// <summary>
|
||||
/// Open a URL in an external browser window.
|
||||
@@ -119,5 +122,13 @@ namespace MediaBrowser.Controller
|
||||
string ExpandVirtualPath(string path);
|
||||
|
||||
string ReverseVirtualPath(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of local plugins.
|
||||
/// </summary>
|
||||
/// <param name="path">Plugin base directory.</param>
|
||||
/// <param name="cleanup">Cleanup old plugins.</param>
|
||||
/// <returns>Enumerable of local plugins.</returns>
|
||||
IEnumerable<LocalPlugin> GetLocalPlugins(string path, bool cleanup = true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -570,5 +570,9 @@ namespace MediaBrowser.Controller.Library
|
||||
List<MediaStream> streams,
|
||||
string videoPath,
|
||||
string[] files);
|
||||
|
||||
void RunMetadataSavers(IReadOnlyList<BaseItem> items, ItemUpdateType updateReason);
|
||||
|
||||
BaseItem GetParentItem(string parentId, Guid? userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,5 +115,7 @@ namespace MediaBrowser.Controller.Library
|
||||
public interface IDirectStreamProvider
|
||||
{
|
||||
Task CopyToAsync(Stream stream, CancellationToken cancellationToken);
|
||||
|
||||
string GetFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,8 @@ namespace MediaBrowser.Controller.Library
|
||||
/// </summary>
|
||||
/// <param name="userId">The user's Id.</param>
|
||||
/// <param name="config">The request containing the new user configuration.</param>
|
||||
void UpdateConfiguration(Guid userId, UserConfiguration config);
|
||||
/// <returns>A task representing the update.</returns>
|
||||
Task UpdateConfigurationAsync(Guid userId, UserConfiguration config);
|
||||
|
||||
/// <summary>
|
||||
/// This method updates the user's policy.
|
||||
@@ -167,12 +168,14 @@ namespace MediaBrowser.Controller.Library
|
||||
/// </summary>
|
||||
/// <param name="userId">The user's Id.</param>
|
||||
/// <param name="policy">The request containing the new user policy.</param>
|
||||
void UpdatePolicy(Guid userId, UserPolicy policy);
|
||||
/// <returns>A task representing the update.</returns>
|
||||
Task UpdatePolicyAsync(Guid userId, UserPolicy policy);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the user's profile image.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
void ClearProfileImage(User user);
|
||||
/// <returns>A task representing the clearing of the profile image.</returns>
|
||||
Task ClearProfileImageAsync(User user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <param name="fields">The fields.</param>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, ItemFields[] fields, User user = null);
|
||||
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the tuner host.
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -287,6 +287,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return BaseRequest.AudioChannels;
|
||||
}
|
||||
|
||||
if (BaseRequest.TranscodingMaxAudioChannels.HasValue)
|
||||
{
|
||||
return BaseRequest.TranscodingMaxAudioChannels;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(codec))
|
||||
{
|
||||
var value = BaseRequest.GetOption(codec, "audiochannels");
|
||||
@@ -404,7 +409,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
// Don't exceed what the encoder supports
|
||||
// Seeing issues of attempting to encode to 88200
|
||||
return Math.Min(44100, BaseRequest.AudioSampleRate.Value);
|
||||
return BaseRequest.AudioSampleRate.Value;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -588,6 +593,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (VideoStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (EncodingHelper.IsCopyCodec(OutputVideoCodec))
|
||||
{
|
||||
return VideoStream?.Codec;
|
||||
@@ -601,6 +611,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (AudioStream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (EncodingHelper.IsCopyCodec(OutputAudioCodec))
|
||||
{
|
||||
return AudioStream?.Codec;
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
else if (part.StartsWith("fps=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var rate = part.Split(new[] { '=' }, 2)[^1];
|
||||
var rate = part.Split('=', 2)[^1];
|
||||
|
||||
if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
|
||||
{
|
||||
@@ -103,7 +103,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
else if (state.RunTimeTicks.HasValue &&
|
||||
part.StartsWith("time=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var time = part.Split(new[] { '=' }, 2).Last();
|
||||
var time = part.Split('=', 2)[^1];
|
||||
|
||||
if (TimeSpan.TryParse(time, _usCulture, out var val))
|
||||
{
|
||||
@@ -116,7 +116,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var size = part.Split(new[] { '=' }, 2).Last();
|
||||
var size = part.Split('=', 2)[^1];
|
||||
|
||||
int? scale = null;
|
||||
if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
@@ -135,7 +135,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var rate = part.Split(new[] { '=' }, 2).Last();
|
||||
var rate = part.Split('=', 2)[^1];
|
||||
|
||||
int? scale = null;
|
||||
if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using Jellyfin.Data.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Net
|
||||
{
|
||||
/// <summary>
|
||||
/// The request authorization info.
|
||||
/// </summary>
|
||||
public class AuthorizationInfo
|
||||
{
|
||||
/// <summary>
|
||||
@@ -43,6 +44,19 @@ namespace MediaBrowser.Controller.Net
|
||||
/// <value>The token.</value>
|
||||
public string Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the authorization is from an api key.
|
||||
/// </summary>
|
||||
public bool IsApiKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the user making the request.
|
||||
/// </summary>
|
||||
public User User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the token is authenticated.
|
||||
/// </summary>
|
||||
public bool IsAuthenticated { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,6 @@ namespace MediaBrowser.Controller.Net
|
||||
/// </summary>
|
||||
event EventHandler<GenericEventArgs<IWebSocketConnection>> WebSocketConnected;
|
||||
|
||||
/// <summary>
|
||||
/// Inits this instance.
|
||||
/// </summary>
|
||||
/// <param name="listeners">The websocket listeners.</param>
|
||||
void Init(IEnumerable<IWebSocketListener> listeners);
|
||||
|
||||
/// <summary>
|
||||
/// The HTTP request handler.
|
||||
/// </summary>
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace MediaBrowser.Controller.Playlists
|
||||
/// <param name="itemIds">The item ids.</param>
|
||||
/// <param name="userId">The user identifier.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task AddToPlaylistAsync(Guid playlistId, ICollection<Guid> itemIds, Guid userId);
|
||||
Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId);
|
||||
|
||||
/// <summary>
|
||||
/// Removes from playlist.
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace MediaBrowser.Controller.Playlists
|
||||
return LibraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
Recursive = true,
|
||||
IncludeItemTypes = new[] { typeof(Audio).Name },
|
||||
IncludeItemTypes = new[] { nameof(Audio) },
|
||||
GenreIds = new[] { musicGenre.Id },
|
||||
OrderBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
|
||||
DtoOptions = options
|
||||
@@ -172,7 +172,7 @@ namespace MediaBrowser.Controller.Playlists
|
||||
return LibraryManager.GetItemList(new InternalItemsQuery(user)
|
||||
{
|
||||
Recursive = true,
|
||||
IncludeItemTypes = new[] { typeof(Audio).Name },
|
||||
IncludeItemTypes = new[] { nameof(Audio) },
|
||||
ArtistIds = new[] { musicArtist.Id },
|
||||
OrderBy = new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }.Select(i => new ValueTuple<string, SortOrder>(i, SortOrder.Ascending)).ToArray(),
|
||||
DtoOptions = options
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
@@ -54,7 +55,7 @@ namespace MediaBrowser.Controller.Session
|
||||
/// Gets or sets the playable media types.
|
||||
/// </summary>
|
||||
/// <value>The playable media types.</value>
|
||||
public string[] PlayableMediaTypes
|
||||
public IReadOnlyList<string> PlayableMediaTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -230,7 +231,7 @@ namespace MediaBrowser.Controller.Session
|
||||
/// Gets or sets the supported commands.
|
||||
/// </summary>
|
||||
/// <value>The supported commands.</value>
|
||||
public GeneralCommandType[] SupportedCommands
|
||||
public IReadOnlyList<GeneralCommandType> SupportedCommands
|
||||
=> Capabilities == null ? Array.Empty<GeneralCommandType>() : Capabilities.SupportedCommands;
|
||||
|
||||
public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@@ -52,6 +53,14 @@ namespace MediaBrowser.Controller.Subtitles
|
||||
/// </summary>
|
||||
Task DownloadSubtitles(Video video, LibraryOptions libraryOptions, string subtitleId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Upload new subtitle.
|
||||
/// </summary>
|
||||
/// <param name="video">The video the subtitle belongs to.</param>
|
||||
/// <param name="response">The subtitle response.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||
Task UploadSubtitle(Video video, SubtitleResponse response);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remote subtitles.
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user