aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
authorLuke <luke.pulverenti@gmail.com>2017-07-01 12:24:26 -0400
committerGitHub <noreply@github.com>2017-07-01 12:24:26 -0400
commitff3713153ad2317e1c196f33ac2cba61b449a00e (patch)
tree84d2e6ed5bcb556a2395603b6403c8e992535e6b /MediaBrowser.Controller
parentfad71a6c7d12c8b207cdf473c7dd7daafa53c174 (diff)
parent2dcad6b5977f5c5be81b18c42506ed8ad3fb73b6 (diff)
Merge pull request #2739 from MediaBrowser/beta
Beta
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/Channels/Channel.cs12
-rw-r--r--MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs11
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs2
-rw-r--r--MediaBrowser.Controller/Dto/DtoOptions.cs3
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs11
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs38
-rw-r--r--MediaBrowser.Controller/Entities/BasePluginFolder.cs10
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs39
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs232
-rw-r--r--MediaBrowser.Controller/Entities/GameSystem.cs9
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs1
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs15
-rw-r--r--MediaBrowser.Controller/Entities/PhotoAlbum.cs9
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs11
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs22
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs9
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs9
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs4
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs15
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs13
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs18
-rw-r--r--MediaBrowser.Controller/LiveTv/RecordingGroup.cs9
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj1
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs34
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs45
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs4
-rw-r--r--MediaBrowser.Controller/Playlists/Playlist.cs9
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs19
-rw-r--r--MediaBrowser.Controller/Providers/ProviderRefreshStatus.cs22
-rw-r--r--MediaBrowser.Controller/Subtitles/ISubtitleManager.cs5
32 files changed, 467 insertions, 185 deletions
diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs
index 862c8e5ace..c6e750a0c0 100644
--- a/MediaBrowser.Controller/Channels/Channel.cs
+++ b/MediaBrowser.Controller/Channels/Channel.cs
@@ -6,6 +6,7 @@ using System.Linq;
using MediaBrowser.Model.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Progress;
namespace MediaBrowser.Controller.Channels
{
@@ -32,6 +33,15 @@ namespace MediaBrowser.Controller.Channels
}
[IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [IgnoreDataMember]
public override SourceType SourceType
{
get { return SourceType.Channel; }
@@ -51,7 +61,7 @@ namespace MediaBrowser.Controller.Channels
SortBy = query.SortBy,
SortOrder = query.SortOrder
- }, new Progress<double>(), CancellationToken.None).Result;
+ }, new SimpleProgress<double>(), CancellationToken.None).Result;
}
catch
{
diff --git a/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs
index d2d28e5047..160a788f1c 100644
--- a/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs
+++ b/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs
@@ -1,4 +1,5 @@
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Controller.Collections
{
@@ -18,11 +19,21 @@ namespace MediaBrowser.Controller.Collections
}
}
+ [IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
public bool IsHiddenFromUser(User user)
{
return !ConfigurationManager.Configuration.DisplayCollectionsView;
}
+ [IgnoreDataMember]
public override string CollectionType
{
get { return Model.Entities.CollectionType.BoxSets; }
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index d98638d556..2832462a8d 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -33,6 +33,8 @@ namespace MediaBrowser.Controller.Drawing
/// <returns>ImageSize.</returns>
ImageSize GetImageSize(ItemImageInfo info);
+ ImageSize GetImageSize(ItemImageInfo info, bool allowSlowMethods);
+
/// <summary>
/// Gets the size of the image.
/// </summary>
diff --git a/MediaBrowser.Controller/Dto/DtoOptions.cs b/MediaBrowser.Controller/Dto/DtoOptions.cs
index b9d9d7ddad..098ba558f8 100644
--- a/MediaBrowser.Controller/Dto/DtoOptions.cs
+++ b/MediaBrowser.Controller/Dto/DtoOptions.cs
@@ -10,7 +10,8 @@ namespace MediaBrowser.Controller.Dto
{
private static readonly List<ItemFields> DefaultExcludedFields = new List<ItemFields>
{
- ItemFields.SeasonUserData
+ ItemFields.SeasonUserData,
+ ItemFields.RefreshState
};
public List<ItemFields> Fields { get; set; }
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index e9eca19cf7..516ab50530 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -235,8 +235,6 @@ namespace MediaBrowser.Controller.Entities.Audio
{
await RefreshArtists(refreshOptions, cancellationToken).ConfigureAwait(false);
}
-
- progress.Report(100);
}
private async Task RefreshArtists(MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 4b232de499..7a37b2e02e 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -36,6 +36,15 @@ namespace MediaBrowser.Controller.Entities.Audio
}
[IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [IgnoreDataMember]
public override bool SupportsCumulativeRunTimeTicks
{
get
@@ -250,8 +259,6 @@ namespace MediaBrowser.Controller.Entities.Audio
percent /= totalItems;
progress.Report(percent * 100);
}
-
- progress.Report(100);
}
public ArtistInfo GetLookupInfo()
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 4efea94d8e..b4a3d89eac 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1058,6 +1058,16 @@ namespace MediaBrowser.Controller.Entities
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)), cancellationToken);
}
+ protected virtual void TriggerOnRefreshStart()
+ {
+
+ }
+
+ protected virtual void TriggerOnRefreshComplete()
+ {
+
+ }
+
/// <summary>
/// Overrides the base implementation to refresh metadata for local trailers
/// </summary>
@@ -1066,6 +1076,8 @@ namespace MediaBrowser.Controller.Entities
/// <returns>true if a provider reports we changed</returns>
public async Task<ItemUpdateType> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
{
+ TriggerOnRefreshStart();
+
var locationType = LocationType;
var requiresSave = false;
@@ -1091,14 +1103,21 @@ namespace MediaBrowser.Controller.Entities
}
}
- var refreshOptions = requiresSave
- ? new MetadataRefreshOptions(options)
- {
- ForceSave = true
- }
- : options;
+ try
+ {
+ var refreshOptions = requiresSave
+ ? new MetadataRefreshOptions(options)
+ {
+ ForceSave = true
+ }
+ : options;
- return await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
+ return await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ TriggerOnRefreshComplete();
+ }
}
[IgnoreDataMember]
@@ -2421,5 +2440,10 @@ namespace MediaBrowser.Controller.Entities
{
return new List<ExternalUrl>();
}
+
+ public virtual double? GetRefreshProgress()
+ {
+ return null;
+ }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs
index 7dbea317c8..a61862f280 100644
--- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs
+++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs
@@ -9,6 +9,7 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public abstract class BasePluginFolder : Folder, ICollectionFolder
{
+ [IgnoreDataMember]
public virtual string CollectionType
{
get { return null; }
@@ -25,6 +26,15 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [IgnoreDataMember]
public override bool SupportsPeople
{
get
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 8bc23a5819..d88b7da346 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Serialization;
@@ -48,6 +49,15 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
public override bool CanDelete()
{
return false;
@@ -199,6 +209,30 @@ namespace MediaBrowser.Controller.Entities
return changed;
}
+ public override double? GetRefreshProgress()
+ {
+ var folders = GetPhysicalFolders(true).ToList();
+ double totalProgresses = 0;
+ var foldersWithProgress = 0;
+
+ foreach (var folder in folders)
+ {
+ var progress = ProviderManager.GetRefreshProgress(folder.Id);
+ if (progress.HasValue)
+ {
+ totalProgresses += progress.Value;
+ foldersWithProgress++;
+ }
+ }
+
+ if (foldersWithProgress == 0)
+ {
+ return null;
+ }
+
+ return (totalProgresses / foldersWithProgress);
+ }
+
protected override bool RefreshLinkedChildren(IEnumerable<FileSystemMetadata> fileSystemChildren)
{
return RefreshLinkedChildrenInternal(true);
@@ -321,6 +355,11 @@ namespace MediaBrowser.Controller.Entities
return GetPhysicalFolders(true).SelectMany(c => c.Children);
}
+ public IEnumerable<Folder> GetPhysicalFolders()
+ {
+ return GetPhysicalFolders(true);
+ }
+
private IEnumerable<Folder> GetPhysicalFolders(bool enableCache)
{
if (enableCache)
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 48c9b83aa2..5d74cf218b 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -66,6 +66,15 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ [IgnoreDataMember]
public override bool SupportsPlayedStatus
{
get
@@ -212,33 +221,6 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Returns the valid set of index by options for this folder type.
- /// Override or extend to modify.
- /// </summary>
- /// <returns>Dictionary{System.StringFunc{UserIEnumerable{BaseItem}}}.</returns>
- protected virtual IEnumerable<string> GetIndexByOptions()
- {
- return new List<string> {
- {"None"},
- {"Performer"},
- {"Genre"},
- {"Director"},
- {"Year"},
- {"Studio"}
- };
- }
-
- /// <summary>
- /// Get the list of indexy by choices for this folder (localized).
- /// </summary>
- /// <value>The index by option strings.</value>
- [IgnoreDataMember]
- public IEnumerable<string> IndexByOptionStrings
- {
- get { return GetIndexByOptions(); }
- }
-
- /// <summary>
/// Gets the actual children.
/// </summary>
/// <value>The actual children.</value>
@@ -298,6 +280,11 @@ namespace MediaBrowser.Controller.Entities
return GetCachedChildren();
}
+ public override double? GetRefreshProgress()
+ {
+ return ProviderManager.GetRefreshProgress(Id);
+ }
+
public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken)
{
return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(Logger, FileSystem)));
@@ -345,6 +332,14 @@ namespace MediaBrowser.Controller.Entities
return current.IsValidFromResolver(newItem);
}
+ protected override void TriggerOnRefreshStart()
+ {
+ }
+
+ protected override void TriggerOnRefreshComplete()
+ {
+ }
+
/// <summary>
/// Validates the children internal.
/// </summary>
@@ -355,7 +350,27 @@ namespace MediaBrowser.Controller.Entities
/// <param name="refreshOptions">The refresh options.</param>
/// <param name="directoryService">The directory service.</param>
/// <returns>Task.</returns>
- protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
+ protected virtual async Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
+ {
+ if (recursive)
+ {
+ ProviderManager.OnRefreshStart(this);
+ }
+
+ try
+ {
+ await ValidateChildrenInternal2(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService).ConfigureAwait(false);
+ }
+ finally
+ {
+ if (recursive)
+ {
+ ProviderManager.OnRefreshComplete(this);
+ }
+ }
+ }
+
+ private async Task ValidateChildrenInternal2(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
var locationType = LocationType;
@@ -387,6 +402,11 @@ namespace MediaBrowser.Controller.Entities
progress.Report(5);
+ if (recursive)
+ {
+ ProviderManager.OnRefreshProgress(this, 5);
+ }
+
//build a dictionary of the current children we have now by Id so we can compare quickly and easily
var currentChildren = GetActualChildrenDictionary();
@@ -451,76 +471,99 @@ namespace MediaBrowser.Controller.Entities
await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
}
}
+ else
+ {
+ if (recursive || refreshChildMetadata)
+ {
+ // used below
+ validChildren = Children.ToList();
+ }
+ }
progress.Report(10);
- cancellationToken.ThrowIfCancellationRequested();
-
if (recursive)
{
- await ValidateSubFolders(Children.ToList().OfType<Folder>().ToList(), directoryService, progress, cancellationToken).ConfigureAwait(false);
+ ProviderManager.OnRefreshProgress(this, 10);
}
- progress.Report(20);
+ cancellationToken.ThrowIfCancellationRequested();
- if (refreshChildMetadata)
+ if (recursive)
{
- var container = this as IMetadataContainer;
+ using (var innerProgress = new ActionableProgress<double>())
+ {
+ var folder = this;
+ innerProgress.RegisterAction(p =>
+ {
+ double newPct = .80 * p + 10;
+ progress.Report(newPct);
+ ProviderManager.OnRefreshProgress(folder, newPct);
+ });
- var innerProgress = new ActionableProgress<double>();
+ await ValidateSubFolders(validChildren.OfType<Folder>().ToList(), directoryService, innerProgress, cancellationToken).ConfigureAwait(false);
+ }
+ }
- innerProgress.RegisterAction(p => progress.Report(.80 * p + 20));
+ if (refreshChildMetadata)
+ {
+ progress.Report(90);
- if (container != null)
+ if (recursive)
{
- await container.RefreshAllMetadata(refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false);
+ ProviderManager.OnRefreshProgress(this, 90);
}
- else
+
+ var container = this as IMetadataContainer;
+
+ using (var innerProgress = new ActionableProgress<double>())
{
- await RefreshMetadataRecursive(refreshOptions, recursive, innerProgress, cancellationToken);
+ var folder = this;
+ innerProgress.RegisterAction(p =>
+ {
+ double newPct = .10 * p + 90;
+ progress.Report(newPct);
+ if (recursive)
+ {
+ ProviderManager.OnRefreshProgress(folder, newPct);
+ }
+ });
+
+ if (container != null)
+ {
+ await container.RefreshAllMetadata(refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await RefreshMetadataRecursive(validChildren, refreshOptions, recursive, innerProgress, cancellationToken);
+ }
}
}
-
- progress.Report(100);
}
- private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
+ private async Task RefreshMetadataRecursive(List<BaseItem> children, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
{
- var children = Children.ToList();
-
- var percentages = new Dictionary<Guid, double>(children.Count);
var numComplete = 0;
var count = children.Count;
+ double currentPercent = 0;
foreach (var child in children)
{
cancellationToken.ThrowIfCancellationRequested();
- if (child.IsFolder)
+ using (var innerProgress = new ActionableProgress<double>())
{
- var innerProgress = new ActionableProgress<double>();
-
// Avoid implicitly captured closure
- var currentChild = child;
+ var currentInnerPercent = currentPercent;
+
innerProgress.RegisterAction(p =>
{
- lock (percentages)
- {
- percentages[currentChild.Id] = p / 100;
-
- var innerPercent = percentages.Values.Sum();
- innerPercent /= count;
- innerPercent *= 100;
- progress.Report(innerPercent);
- }
+ double innerPercent = currentInnerPercent;
+ innerPercent += p / (count);
+ progress.Report(innerPercent);
});
- await RefreshChildMetadata(child, refreshOptions, recursive, innerProgress, cancellationToken)
- .ConfigureAwait(false);
- }
- else
- {
- await RefreshChildMetadata(child, refreshOptions, false, new Progress<double>(), cancellationToken)
+ await RefreshChildMetadata(child, refreshOptions, recursive && child.IsFolder, innerProgress, cancellationToken)
.ConfigureAwait(false);
}
@@ -528,11 +571,10 @@ namespace MediaBrowser.Controller.Entities
double percent = numComplete;
percent /= count;
percent *= 100;
+ currentPercent = percent;
progress.Report(percent);
}
-
- progress.Report(100);
}
private async Task RefreshChildMetadata(BaseItem child, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
@@ -553,11 +595,10 @@ namespace MediaBrowser.Controller.Entities
if (folder != null)
{
- await folder.RefreshMetadataRecursive(refreshOptions, true, progress, cancellationToken);
+ await folder.RefreshMetadataRecursive(folder.Children.ToList(), refreshOptions, true, progress, cancellationToken);
}
}
}
- progress.Report(100);
}
/// <summary>
@@ -570,47 +611,40 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task.</returns>
private async Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken)
{
- var list = children;
- var childCount = list.Count;
-
- var percentages = new Dictionary<Guid, double>(list.Count);
+ var numComplete = 0;
+ var count = children.Count;
+ double currentPercent = 0;
- foreach (var item in list)
+ foreach (var child in children)
{
cancellationToken.ThrowIfCancellationRequested();
- var child = item;
-
- var innerProgress = new ActionableProgress<double>();
-
- innerProgress.RegisterAction(p =>
+ using (var innerProgress = new ActionableProgress<double>())
{
- lock (percentages)
+ // Avoid implicitly captured closure
+ var currentInnerPercent = currentPercent;
+
+ innerProgress.RegisterAction(p =>
{
- percentages[child.Id] = p / 100;
+ double innerPercent = currentInnerPercent;
+ innerPercent += p / (count);
+ progress.Report(innerPercent);
+ });
- var percent = percentages.Values.Sum();
- percent /= childCount;
+ await child.ValidateChildrenInternal(innerProgress, cancellationToken, true, false, null, directoryService)
+ .ConfigureAwait(false);
+ }
- progress.Report(10 * percent + 10);
- }
- });
+ numComplete++;
+ double percent = numComplete;
+ percent /= count;
+ percent *= 100;
+ currentPercent = percent;
- await child.ValidateChildrenInternal(innerProgress, cancellationToken, true, false, null, directoryService)
- .ConfigureAwait(false);
+ progress.Report(percent);
}
}
- /// <summary>
- /// Determines whether the specified path is offline.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns><c>true</c> if the specified path is offline; otherwise, <c>false</c>.</returns>
- public static bool IsPathOffline(string path)
- {
- return IsPathOffline(path, LibraryManager.GetVirtualFolders().SelectMany(i => i.Locations).ToList());
- }
-
public static bool IsPathOffline(string path, List<string> allLibraryPaths)
{
if (FileSystem.FileExists(path))
@@ -953,7 +987,7 @@ namespace MediaBrowser.Controller.Entities
SortBy = query.SortBy,
SortOrder = query.SortOrder
- }, new Progress<double>(), CancellationToken.None).Result;
+ }, new SimpleProgress<double>(), CancellationToken.None).Result;
}
catch
{
diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs
index d8879155af..bbaec14a15 100644
--- a/MediaBrowser.Controller/Entities/GameSystem.cs
+++ b/MediaBrowser.Controller/Entities/GameSystem.cs
@@ -35,6 +35,15 @@ namespace MediaBrowser.Controller.Entities
}
}
+ [IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
/// <summary>
/// Gets or sets the game system.
/// </summary>
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 4d96c082fb..608e3f56c3 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -56,7 +56,6 @@ namespace MediaBrowser.Controller.Entities
public string Path { get; set; }
public string PathNotStartsWith { get; set; }
public string Name { get; set; }
- public string SlugName { get; set; }
public string Person { get; set; }
public string[] PersonIds { get; set; }
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 406a99f8b7..071ed405f1 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -29,6 +29,7 @@ namespace MediaBrowser.Controller.Entities.Movies
Shares = new List<Share>();
}
+ [IgnoreDataMember]
protected override bool FilterLinkedChildrenPerUser
{
get
@@ -37,6 +38,15 @@ namespace MediaBrowser.Controller.Entities.Movies
}
}
+ [IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
public List<Guid> LocalTrailerIds { get; set; }
public List<Guid> RemoteTrailerIds { get; set; }
@@ -118,6 +128,11 @@ namespace MediaBrowser.Controller.Entities.Movies
{
get
{
+ if (string.IsNullOrWhiteSpace(Path))
+ {
+ return false;
+ }
+
return !FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.DataPath, Path);
}
}
diff --git a/MediaBrowser.Controller/Entities/PhotoAlbum.cs b/MediaBrowser.Controller/Entities/PhotoAlbum.cs
index dd3cd98fb9..af9d8c801d 100644
--- a/MediaBrowser.Controller/Entities/PhotoAlbum.cs
+++ b/MediaBrowser.Controller/Entities/PhotoAlbum.cs
@@ -21,5 +21,14 @@ namespace MediaBrowser.Controller.Entities
return false;
}
}
+
+ [IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 17494eb087..b681fdcb10 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -66,17 +66,6 @@ namespace MediaBrowser.Controller.Entities.TV
return series == null ? SeriesName : series.SortName;
}
- // Genre, Rating and Stuido will all be the same
- protected override IEnumerable<string> GetIndexByOptions()
- {
- return new List<string> {
- {"None"},
- {"Performer"},
- {"Director"},
- {"Year"},
- };
- }
-
public override List<string> GetUserDataKeys()
{
var list = base.GetUserDataKeys();
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index c54f93f11f..8b73b80b02 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -53,6 +53,15 @@ namespace MediaBrowser.Controller.Entities.TV
}
}
+ [IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
public List<Guid> LocalTrailerIds { get; set; }
public List<Guid> RemoteTrailerIds { get; set; }
@@ -227,17 +236,6 @@ namespace MediaBrowser.Controller.Entities.TV
return list;
}
- // Studio, Genre and Rating will all be the same so makes no sense to index by these
- protected override IEnumerable<string> GetIndexByOptions()
- {
- return new List<string> {
- {"None"},
- {"Performer"},
- {"Director"},
- {"Year"},
- };
- }
-
[IgnoreDataMember]
public bool ContainsEpisodesWithoutSeasonFolders
{
@@ -425,8 +423,6 @@ namespace MediaBrowser.Controller.Entities.TV
refreshOptions = new MetadataRefreshOptions(refreshOptions);
refreshOptions.IsPostRecursiveRefresh = true;
await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
-
- progress.Report(100);
}
public IEnumerable<Episode> GetSeasonEpisodes(Season parentSeason, User user, DtoOptions options)
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index 3e4931e774..d351563450 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -34,6 +34,15 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [IgnoreDataMember]
public override bool SupportsPlayedStatus
{
get
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index 27be696ebd..0d2d69c949 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -41,6 +41,15 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [IgnoreDataMember]
public override bool SupportsPlayedStatus
{
get
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index b6887940ed..7978f47613 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -44,6 +44,15 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ [IgnoreDataMember]
public override bool SupportsPositionTicksResume
{
get
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 5a702b4f08..b726c267c6 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -132,7 +132,9 @@ namespace MediaBrowser.Controller.Library
/// Gets the default view.
/// </summary>
/// <returns>IEnumerable{VirtualFolderInfo}.</returns>
- IEnumerable<VirtualFolderInfo> GetVirtualFolders();
+ List<VirtualFolderInfo> GetVirtualFolders();
+
+ List<VirtualFolderInfo> GetVirtualFolders(bool includeRefreshState);
/// <summary>
/// Gets the item by id.
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
index 45786e7959..9f82344029 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
@@ -92,7 +92,20 @@ namespace MediaBrowser.Controller.LiveTv
public override double? GetDefaultPrimaryImageAspectRatio()
{
- return null;
+ if (IsMovie)
+ {
+ double value = 2;
+ value /= 3;
+
+ return value;
+ }
+ else
+ {
+ double value = 2;
+ value /= 3;
+
+ return value;
+ }
}
public override string GetClientTypeName()
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
index 665640dfd1..9f55a9ff23 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -57,8 +57,13 @@ namespace MediaBrowser.Controller.LiveTv
return value;
}
+ else
+ {
+ double value = 2;
+ value /= 3;
- return null;
+ return value;
+ }
}
[IgnoreDataMember]
@@ -216,12 +221,6 @@ namespace MediaBrowser.Controller.LiveTv
public override bool IsInternetMetadataEnabled()
{
- if (IsMovie)
- {
- var options = (LiveTvOptions)ConfigurationManager.GetConfiguration("livetv");
- return options.EnableMovieProviders;
- }
-
return false;
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
index 2b527c4b09..c29d3dc478 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
@@ -108,6 +108,24 @@ namespace MediaBrowser.Controller.LiveTv
return false;
}
+ public override double? GetDefaultPrimaryImageAspectRatio()
+ {
+ if (IsMovie)
+ {
+ double value = 2;
+ value /= 3;
+
+ return value;
+ }
+ else
+ {
+ double value = 2;
+ value /= 3;
+
+ return value;
+ }
+ }
+
[IgnoreDataMember]
public override bool SupportsLocalMetadata
{
diff --git a/MediaBrowser.Controller/LiveTv/RecordingGroup.cs b/MediaBrowser.Controller/LiveTv/RecordingGroup.cs
index b54ca89747..3ee0613605 100644
--- a/MediaBrowser.Controller/LiveTv/RecordingGroup.cs
+++ b/MediaBrowser.Controller/LiveTv/RecordingGroup.cs
@@ -27,6 +27,15 @@ namespace MediaBrowser.Controller.LiveTv
}
[IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [IgnoreDataMember]
public override SourceType SourceType
{
get { return SourceType.LiveTV; }
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 348cfd343f..22f94695d8 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -320,7 +320,6 @@
<Compile Include="Plugins\IPluginConfigurationPage.cs" />
<Compile Include="Plugins\IServerEntryPoint.cs" />
<Compile Include="Providers\IImageEnhancer.cs" />
- <Compile Include="Providers\ProviderRefreshStatus.cs" />
<Compile Include="Resolvers\IResolverIgnoreRule.cs" />
<Compile Include="Resolvers\ResolverPriority.cs" />
<Compile Include="Library\TVUtils.cs" />
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index c348ffda72..44bdafc5b2 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -42,6 +42,11 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var hwType = encodingOptions.HardwareAccelerationType;
+ if (!encodingOptions.EnableHardwareEncoding)
+ {
+ hwType = null;
+ }
+
if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
@@ -196,6 +201,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return null;
}
+ if (string.Equals(container, "tp", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
// Seeing reported failures here, not sure yet if this is related to specfying input format
if (string.Equals(container, "m4v", StringComparison.OrdinalIgnoreCase))
@@ -228,7 +237,12 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
- return codec;
+ if (_mediaEncoder.SupportsDecoder(codec))
+ {
+ return codec;
+ }
+
+ return null;
}
/// <summary>
@@ -1761,14 +1775,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
- if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
+ if (state.VideoStream != null &&
+ !string.IsNullOrWhiteSpace(state.VideoStream.Codec) &&
+ !string.IsNullOrWhiteSpace(encodingOptions.HardwareAccelerationType) &&
+ encodingOptions.EnableHardwareDecoding)
{
- if (!string.IsNullOrWhiteSpace(encodingOptions.HardwareAccelerationType))
- {
- // causing unpredictable results
- //return "-hwaccel auto";
- }
-
if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
switch (state.MediaSource.VideoStream.Codec.ToLower())
@@ -1818,6 +1829,13 @@ namespace MediaBrowser.Controller.MediaEncoding
return "-c:v h264_cuvid ";
}
break;
+ case "hevc":
+ case "h265":
+ if (_mediaEncoder.SupportsDecoder("hevc_cuvid"))
+ {
+ return "-c:v hevc_cuvid ";
+ }
+ break;
}
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 57c81ddf76..b552579a8c 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
@@ -9,6 +10,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Session;
namespace MediaBrowser.Controller.MediaEncoding
{
@@ -40,6 +42,24 @@ namespace MediaBrowser.Controller.MediaEncoding
public bool ReadInputAtNativeFramerate { get; set; }
+ private List<TranscodeReason> _transcodeReasons = null;
+ public List<TranscodeReason> TranscodeReasons
+ {
+ get
+ {
+ if (_transcodeReasons == null)
+ {
+ _transcodeReasons = (BaseRequest.TranscodeReasons ?? string.Empty)
+ .Split(',')
+ .Where(i => !string.IsNullOrWhiteSpace(i))
+ .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true))
+ .ToList();
+ }
+
+ return _transcodeReasons;
+ }
+ }
+
public bool IgnoreInputDts
{
get
@@ -251,7 +271,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return AudioStream.SampleRate;
}
- }
+ }
else if (BaseRequest.AudioSampleRate.HasValue)
{
@@ -264,6 +284,29 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ public int? OutputAudioBitDepth
+ {
+ get
+ {
+ if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ {
+ if (AudioStream != null)
+ {
+ return AudioStream.BitDepth;
+ }
+ }
+
+ //else if (BaseRequest.AudioSampleRate.HasValue)
+ //{
+ // // Don't exceed what the encoder supports
+ // // Seeing issues of attempting to encode to 88200
+ // return Math.Min(44100, BaseRequest.AudioSampleRate.Value);
+ //}
+
+ return null;
+ }
+ }
+
/// <summary>
/// Predicts the audio sample rate that will be in the output stream
/// </summary>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
index 632c350ad0..5fc93bf382 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
@@ -83,6 +83,8 @@ namespace MediaBrowser.Controller.MediaEncoding
[ApiMember(Name = "AudioSampleRate", Description = "Optional. Specify a specific audio sample rate, e.g. 44100", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int? AudioSampleRate { get; set; }
+ public int? MaxAudioBitDepth { get; set; }
+
/// <summary>
/// Gets or sets the audio bit rate.
/// </summary>
@@ -204,6 +206,8 @@ namespace MediaBrowser.Controller.MediaEncoding
public string SubtitleCodec { get; set; }
+ public string TranscodeReasons { get; set; }
+
/// <summary>
/// Gets or sets the index of the audio stream.
/// </summary>
diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs
index 854f8d7a2f..c992ac56a7 100644
--- a/MediaBrowser.Controller/Playlists/Playlist.cs
+++ b/MediaBrowser.Controller/Playlists/Playlist.cs
@@ -33,6 +33,15 @@ namespace MediaBrowser.Controller.Playlists
}
[IgnoreDataMember]
+ public override bool SupportsInheritedParentImages
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ [IgnoreDataMember]
public override bool SupportsPlayedStatus
{
get
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index c0bc902140..0ba573da8b 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Controller.Providers
{
@@ -30,7 +31,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
Task RefreshFullItem(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken);
-
+
/// <summary>
/// Refreshes the metadata.
/// </summary>
@@ -68,7 +69,7 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
/// <returns>Task.</returns>
Task SaveImage(IHasImages item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken);
-
+
/// <summary>
/// Adds the metadata providers.
/// </summary>
@@ -128,7 +129,7 @@ namespace MediaBrowser.Controller.Providers
/// <param name="savers">The savers.</param>
/// <returns>Task.</returns>
Task SaveMetadata(IHasMetadata item, ItemUpdateType updateType, IEnumerable<string> savers);
-
+
/// <summary>
/// Gets the metadata options.
/// </summary>
@@ -158,6 +159,18 @@ namespace MediaBrowser.Controller.Providers
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{HttpResponseInfo}.</returns>
Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken);
+
+ Dictionary<Guid, Guid> GetRefreshQueue();
+
+ void OnRefreshStart(BaseItem item);
+ void OnRefreshProgress(BaseItem item, double progress);
+ void OnRefreshComplete(BaseItem item);
+
+ double? GetRefreshProgress(Guid id);
+
+ event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
+ event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
+ event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
}
public enum RefreshPriority
diff --git a/MediaBrowser.Controller/Providers/ProviderRefreshStatus.cs b/MediaBrowser.Controller/Providers/ProviderRefreshStatus.cs
deleted file mode 100644
index 6523dc4173..0000000000
--- a/MediaBrowser.Controller/Providers/ProviderRefreshStatus.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-
-namespace MediaBrowser.Controller.Providers
-{
- /// <summary>
- /// Enum ProviderRefreshStatus
- /// </summary>
- public enum ProviderRefreshStatus
- {
- /// <summary>
- /// The success
- /// </summary>
- Success = 0,
- /// <summary>
- /// The completed with errors
- /// </summary>
- CompletedWithErrors = 1,
- /// <summary>
- /// The failure
- /// </summary>
- Failure = 2
- }
-}
diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
index 0c814c0d4c..d1d5f27bef 100644
--- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
+++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
@@ -28,12 +28,9 @@ namespace MediaBrowser.Controller.Subtitles
/// <summary>
/// Searches the subtitles.
/// </summary>
- /// <param name="video">The video.</param>
- /// <param name="language">The language.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{IEnumerable{RemoteSubtitleInfo}}.</returns>
Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(Video video,
string language,
+ bool? isPerfectMatch,
CancellationToken cancellationToken);
/// <summary>