mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-04 03:27:21 -05:00 
			
		
		
		
	add subtitle management page
This commit is contained in:
		
							parent
							
								
									26aa47eefd
								
							
						
					
					
						commit
						c8e4889ac7
					
				@ -7,7 +7,6 @@ using MediaBrowser.Controller.Entities.Movies;
 | 
			
		||||
using MediaBrowser.Controller.Entities.TV;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Controller.Providers;
 | 
			
		||||
using MediaBrowser.Controller.Subtitles;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Providers;
 | 
			
		||||
using ServiceStack;
 | 
			
		||||
@ -32,16 +31,6 @@ namespace MediaBrowser.Api
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
 | 
			
		||||
    public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
 | 
			
		||||
    {
 | 
			
		||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
 | 
			
		||||
        [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
			
		||||
        public string Language { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Route("/Items/RemoteSearch/Movie", "POST")]
 | 
			
		||||
    [Api(Description = "Gets external id infos for an item")]
 | 
			
		||||
    public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
 | 
			
		||||
@ -121,24 +110,13 @@ namespace MediaBrowser.Api
 | 
			
		||||
        private readonly IServerApplicationPaths _appPaths;
 | 
			
		||||
        private readonly IFileSystem _fileSystem;
 | 
			
		||||
        private readonly ILibraryManager _libraryManager;
 | 
			
		||||
        private readonly ISubtitleManager _subtitleManager;
 | 
			
		||||
 | 
			
		||||
        public ItemLookupService(IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem, ILibraryManager libraryManager, ISubtitleManager subtitleManager)
 | 
			
		||||
        public ItemLookupService(IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem, ILibraryManager libraryManager)
 | 
			
		||||
        {
 | 
			
		||||
            _providerManager = providerManager;
 | 
			
		||||
            _appPaths = appPaths;
 | 
			
		||||
            _fileSystem = fileSystem;
 | 
			
		||||
            _libraryManager = libraryManager;
 | 
			
		||||
            _subtitleManager = subtitleManager;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public object Get(SearchRemoteSubtitles request)
 | 
			
		||||
        {
 | 
			
		||||
            var video = (Video)_libraryManager.GetItemById(request.Id);
 | 
			
		||||
 | 
			
		||||
            var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result;
 | 
			
		||||
 | 
			
		||||
            return ToOptimizedResult(response);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public object Get(GetExternalIdInfos request)
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
using MediaBrowser.Common.Extensions;
 | 
			
		||||
using MediaBrowser.Controller.Channels;
 | 
			
		||||
using MediaBrowser.Controller.Channels;
 | 
			
		||||
using MediaBrowser.Controller.Dto;
 | 
			
		||||
using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Entities.Audio;
 | 
			
		||||
@ -35,21 +34,6 @@ namespace MediaBrowser.Api.Library
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Route("/Videos/{Id}/Subtitles/{Index}", "GET")]
 | 
			
		||||
    [Api(Description = "Gets an external subtitle file")]
 | 
			
		||||
    public class GetSubtitle
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the id.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <value>The id.</value>
 | 
			
		||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
 | 
			
		||||
        [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
 | 
			
		||||
        public int Index { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// Class GetCriticReviews
 | 
			
		||||
    /// </summary>
 | 
			
		||||
@ -305,25 +289,6 @@ namespace MediaBrowser.Api.Library
 | 
			
		||||
            return ToStaticFileResult(item.Path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public object Get(GetSubtitle request)
 | 
			
		||||
        {
 | 
			
		||||
            var subtitleStream = _itemRepo.GetMediaStreams(new MediaStreamQuery
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                Index = request.Index,
 | 
			
		||||
                ItemId = new Guid(request.Id),
 | 
			
		||||
                Type = MediaStreamType.Subtitle
 | 
			
		||||
 | 
			
		||||
            }).FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
            if (subtitleStream == null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ResourceNotFoundException();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return ToStaticFileResult(subtitleStream.Path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the specified request.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										162
									
								
								MediaBrowser.Api/Library/SubtitleService.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								MediaBrowser.Api/Library/SubtitleService.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,162 @@
 | 
			
		||||
using MediaBrowser.Common.Extensions;
 | 
			
		||||
using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Controller.Persistence;
 | 
			
		||||
using MediaBrowser.Controller.Providers;
 | 
			
		||||
using MediaBrowser.Controller.Subtitles;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Providers;
 | 
			
		||||
using ServiceStack;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace MediaBrowser.Api.Library
 | 
			
		||||
{
 | 
			
		||||
    [Route("/Videos/{Id}/Subtitles/{Index}", "GET", Summary = "Gets an external subtitle file")]
 | 
			
		||||
    public class GetSubtitle
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the id.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <value>The id.</value>
 | 
			
		||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
 | 
			
		||||
        [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")]
 | 
			
		||||
        public int Index { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Route("/Videos/{Id}/Subtitles/{Index}", "DELETE", Summary = "Deletes an external subtitle file")]
 | 
			
		||||
    public class DeleteSubtitle
 | 
			
		||||
    {
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets or sets the id.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <value>The id.</value>
 | 
			
		||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
 | 
			
		||||
        [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "DELETE")]
 | 
			
		||||
        public int Index { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
 | 
			
		||||
    public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
 | 
			
		||||
    {
 | 
			
		||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
 | 
			
		||||
        [ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
			
		||||
        public string Language { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Route("/Items/{Id}/RemoteSearch/Subtitles/Providers", "GET")]
 | 
			
		||||
    public class GetSubtitleProviders : IReturn<List<SubtitleProviderInfo>>
 | 
			
		||||
    {
 | 
			
		||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Route("/Items/{Id}/RemoteSearch/Subtitles/{SubtitleId}", "POST")]
 | 
			
		||||
    public class DownloadRemoteSubtitles : IReturnVoid
 | 
			
		||||
    {
 | 
			
		||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
 | 
			
		||||
        [ApiMember(Name = "SubtitleId", Description = "SubtitleId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
 | 
			
		||||
        public string SubtitleId { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Route("/Providers/Subtitles/Subtitles/{Id}", "GET")]
 | 
			
		||||
    public class GetRemoteSubtitles : IReturnVoid
 | 
			
		||||
    {
 | 
			
		||||
        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class SubtitleService : BaseApiService
 | 
			
		||||
    {
 | 
			
		||||
        private readonly ILibraryManager _libraryManager;
 | 
			
		||||
        private readonly ISubtitleManager _subtitleManager;
 | 
			
		||||
        private readonly IItemRepository _itemRepo;
 | 
			
		||||
 | 
			
		||||
        public SubtitleService(ILibraryManager libraryManager, ISubtitleManager subtitleManager, IItemRepository itemRepo)
 | 
			
		||||
        {
 | 
			
		||||
            _libraryManager = libraryManager;
 | 
			
		||||
            _subtitleManager = subtitleManager;
 | 
			
		||||
            _itemRepo = itemRepo;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public object Get(SearchRemoteSubtitles request)
 | 
			
		||||
        {
 | 
			
		||||
            var video = (Video)_libraryManager.GetItemById(request.Id);
 | 
			
		||||
 | 
			
		||||
            var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result;
 | 
			
		||||
 | 
			
		||||
            return ToOptimizedResult(response);
 | 
			
		||||
        }
 | 
			
		||||
        public object Get(GetSubtitle request)
 | 
			
		||||
        {
 | 
			
		||||
            var subtitleStream = _itemRepo.GetMediaStreams(new MediaStreamQuery
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
                Index = request.Index,
 | 
			
		||||
                ItemId = new Guid(request.Id),
 | 
			
		||||
                Type = MediaStreamType.Subtitle
 | 
			
		||||
 | 
			
		||||
            }).FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
            if (subtitleStream == null)
 | 
			
		||||
            {
 | 
			
		||||
                throw new ResourceNotFoundException();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return ToStaticFileResult(subtitleStream.Path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Delete(DeleteSubtitle request)
 | 
			
		||||
        {
 | 
			
		||||
            var task = _subtitleManager.DeleteSubtitles(request.Id, request.Index);
 | 
			
		||||
 | 
			
		||||
            Task.WaitAll(task);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public object Get(GetSubtitleProviders request)
 | 
			
		||||
        {
 | 
			
		||||
            var result = _subtitleManager.GetProviders(request.Id);
 | 
			
		||||
 | 
			
		||||
            return ToOptimizedResult(result);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public object Get(GetRemoteSubtitles request)
 | 
			
		||||
        {
 | 
			
		||||
            var result = _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).Result;
 | 
			
		||||
 | 
			
		||||
            return ResultFactory.GetResult(result.Stream, MimeTypes.GetMimeType("file." + result.Format));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void Post(DownloadRemoteSubtitles request)
 | 
			
		||||
        {
 | 
			
		||||
            var video = (Video)_libraryManager.GetItemById(request.Id);
 | 
			
		||||
 | 
			
		||||
            Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await _subtitleManager.DownloadSubtitles(video, request.SubtitleId, CancellationToken.None)
 | 
			
		||||
                        .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    await video.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    Logger.ErrorException("Error downloading subtitles", ex);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -68,6 +68,7 @@
 | 
			
		||||
    <Compile Include="ChannelService.cs" />
 | 
			
		||||
    <Compile Include="Dlna\DlnaServerService.cs" />
 | 
			
		||||
    <Compile Include="Dlna\DlnaService.cs" />
 | 
			
		||||
    <Compile Include="Library\SubtitleService.cs" />
 | 
			
		||||
    <Compile Include="Movies\CollectionService.cs" />
 | 
			
		||||
    <Compile Include="Music\AlbumsService.cs" />
 | 
			
		||||
    <Compile Include="AppThemeService.cs" />
 | 
			
		||||
 | 
			
		||||
@ -34,17 +34,22 @@ namespace MediaBrowser.Controller.Providers
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Providers will be executed based on default rules
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        EnsureMetadata,
 | 
			
		||||
        EnsureMetadata = 0,
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// No providers will be executed
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        None,
 | 
			
		||||
        None = 1,
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// All providers will be executed to search for new metadata
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        FullRefresh
 | 
			
		||||
        FullRefresh = 2,
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The validation only
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        ValidationOnly = 3
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum ImageRefreshMode
 | 
			
		||||
@ -52,16 +57,16 @@ namespace MediaBrowser.Controller.Providers
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The default
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        Default,
 | 
			
		||||
        Default = 0,
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Existing images will be validated
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        ValidationOnly,
 | 
			
		||||
        ValidationOnly = 1,
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// All providers will be executed to search for new metadata
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        FullRefresh
 | 
			
		||||
        FullRefresh = 2
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -39,12 +39,33 @@ namespace MediaBrowser.Controller.Subtitles
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="video">The video.</param>
 | 
			
		||||
        /// <param name="subtitleId">The subtitle identifier.</param>
 | 
			
		||||
        /// <param name="providerName">Name of the provider.</param>
 | 
			
		||||
        /// <param name="cancellationToken">The cancellation token.</param>
 | 
			
		||||
        /// <returns>Task.</returns>
 | 
			
		||||
        Task DownloadSubtitles(Video video, 
 | 
			
		||||
            string subtitleId, 
 | 
			
		||||
            string providerName, 
 | 
			
		||||
            CancellationToken cancellationToken);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the remote subtitles.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="id">The identifier.</param>
 | 
			
		||||
        /// <param name="cancellationToken">The cancellation token.</param>
 | 
			
		||||
        /// <returns>Task{SubtitleResponse}.</returns>
 | 
			
		||||
        Task<SubtitleResponse> GetRemoteSubtitles(string id, CancellationToken cancellationToken);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Deletes the subtitles.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="itemId">The item identifier.</param>
 | 
			
		||||
        /// <param name="index">The index.</param>
 | 
			
		||||
        /// <returns>Task.</returns>
 | 
			
		||||
        Task DeleteSubtitles(string itemId, int index);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the providers.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="itemId">The item identifier.</param>
 | 
			
		||||
        /// <returns>IEnumerable{SubtitleProviderInfo}.</returns>
 | 
			
		||||
        IEnumerable<SubtitleProviderInfo> GetProviders(string itemId);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ namespace MediaBrowser.Controller.Subtitles
 | 
			
		||||
    {
 | 
			
		||||
        public string Language { get; set; }
 | 
			
		||||
        public string Format { get; set; }
 | 
			
		||||
        public bool IsForced { get; set; }
 | 
			
		||||
        public Stream Stream { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -21,8 +21,11 @@ namespace MediaBrowser.Controller.Subtitles
 | 
			
		||||
        public long? RuntimeTicks { get; set; }
 | 
			
		||||
        public Dictionary<string, string> ProviderIds { get; set; }
 | 
			
		||||
 | 
			
		||||
        public bool SearchAllProviders { get; set; }
 | 
			
		||||
 | 
			
		||||
        public SubtitleSearchRequest()
 | 
			
		||||
        {
 | 
			
		||||
            SearchAllProviders = true;
 | 
			
		||||
            ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,4 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
 | 
			
		||||
namespace MediaBrowser.Model.Entities
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
@ -16,4 +16,10 @@ namespace MediaBrowser.Model.Providers
 | 
			
		||||
        public int? DownloadCount { get; set; }
 | 
			
		||||
        public bool? IsHashMatch { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public class SubtitleProviderInfo
 | 
			
		||||
    {
 | 
			
		||||
        public string Name { get; set; }
 | 
			
		||||
        public string Id { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -142,7 +142,7 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
			
		||||
 | 
			
		||||
            var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager);
 | 
			
		||||
 | 
			
		||||
            return prober.ProbeVideo(item, directoryService, cancellationToken);
 | 
			
		||||
            return prober.ProbeVideo(item, directoryService, true, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<ItemUpdateType> FetchAudioInfo<T>(T item, CancellationToken cancellationToken)
 | 
			
		||||
 | 
			
		||||
@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
			
		||||
            _subtitleManager = subtitleManager;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, CancellationToken cancellationToken)
 | 
			
		||||
        public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, bool enableSubtitleDownloading, CancellationToken cancellationToken)
 | 
			
		||||
            where T : Video
 | 
			
		||||
        {
 | 
			
		||||
            var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
@ -105,7 +105,7 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
			
		||||
 | 
			
		||||
                cancellationToken.ThrowIfCancellationRequested();
 | 
			
		||||
 | 
			
		||||
                await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, directoryService).ConfigureAwait(false);
 | 
			
		||||
                await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, directoryService, enableSubtitleDownloading).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, BlurayDiscInfo blurayInfo, IDirectoryService directoryService)
 | 
			
		||||
        protected async Task Fetch(Video video, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount, BlurayDiscInfo blurayInfo, IDirectoryService directoryService, bool enableSubtitleDownloading)
 | 
			
		||||
        {
 | 
			
		||||
            var mediaInfo = MediaEncoderHelpers.GetMediaInfo(data);
 | 
			
		||||
            var mediaStreams = mediaInfo.MediaStreams;
 | 
			
		||||
@ -208,7 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
			
		||||
                FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await AddExternalSubtitles(video, mediaStreams, directoryService, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            await AddExternalSubtitles(video, mediaStreams, directoryService, enableSubtitleDownloading, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            FetchWtvInfo(video, data);
 | 
			
		||||
 | 
			
		||||
@ -416,13 +416,17 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="video">The video.</param>
 | 
			
		||||
        /// <param name="currentStreams">The current streams.</param>
 | 
			
		||||
        private async Task AddExternalSubtitles(Video video, List<MediaStream> currentStreams, IDirectoryService directoryService, CancellationToken cancellationToken)
 | 
			
		||||
        /// <param name="directoryService">The directory service.</param>
 | 
			
		||||
        /// <param name="enableSubtitleDownloading">if set to <c>true</c> [enable subtitle downloading].</param>
 | 
			
		||||
        /// <param name="cancellationToken">The cancellation token.</param>
 | 
			
		||||
        /// <returns>Task.</returns>
 | 
			
		||||
        private async Task AddExternalSubtitles(Video video, List<MediaStream> currentStreams, IDirectoryService directoryService, bool enableSubtitleDownloading, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var subtitleResolver = new SubtitleResolver(_localization);
 | 
			
		||||
 | 
			
		||||
            var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, directoryService, false).ToList();
 | 
			
		||||
 | 
			
		||||
            if ((_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles &&
 | 
			
		||||
            if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles &&
 | 
			
		||||
                video is Episode) ||
 | 
			
		||||
                (_config.Configuration.SubtitleOptions.DownloadMovieSubtitles &&
 | 
			
		||||
                video is Movie))
 | 
			
		||||
 | 
			
		||||
@ -124,7 +124,10 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
			
		||||
                Name = video.Name,
 | 
			
		||||
                ParentIndexNumber = video.ParentIndexNumber,
 | 
			
		||||
                ProductionYear = video.ProductionYear,
 | 
			
		||||
                ProviderIds = video.ProviderIds
 | 
			
		||||
                ProviderIds = video.ProviderIds,
 | 
			
		||||
 | 
			
		||||
                // Stop as soon as we find something
 | 
			
		||||
                SearchAllProviders = false
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            var episode = video as Episode;
 | 
			
		||||
@ -143,7 +146,7 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
			
		||||
 | 
			
		||||
                if (result != null)
 | 
			
		||||
                {
 | 
			
		||||
                    await _subtitleManager.DownloadSubtitles(video, result.Id, result.ProviderName, cancellationToken)
 | 
			
		||||
                    await _subtitleManager.DownloadSubtitles(video, result.Id, cancellationToken)
 | 
			
		||||
                            .ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    return true;
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,10 @@
 | 
			
		||||
using MediaBrowser.Common.IO;
 | 
			
		||||
using MediaBrowser.Common.Extensions;
 | 
			
		||||
using MediaBrowser.Common.IO;
 | 
			
		||||
using MediaBrowser.Controller.Entities;
 | 
			
		||||
using MediaBrowser.Controller.Entities.Movies;
 | 
			
		||||
using MediaBrowser.Controller.Entities.TV;
 | 
			
		||||
using MediaBrowser.Controller.Library;
 | 
			
		||||
using MediaBrowser.Controller.Persistence;
 | 
			
		||||
using MediaBrowser.Controller.Providers;
 | 
			
		||||
using MediaBrowser.Controller.Subtitles;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
@ -23,12 +25,16 @@ namespace MediaBrowser.Providers.Subtitles
 | 
			
		||||
        private readonly ILogger _logger;
 | 
			
		||||
        private readonly IFileSystem _fileSystem;
 | 
			
		||||
        private readonly ILibraryMonitor _monitor;
 | 
			
		||||
        private readonly ILibraryManager _libraryManager;
 | 
			
		||||
        private readonly IItemRepository _itemRepo;
 | 
			
		||||
 | 
			
		||||
        public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor)
 | 
			
		||||
        public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor, ILibraryManager libraryManager, IItemRepository itemRepo)
 | 
			
		||||
        {
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
            _fileSystem = fileSystem;
 | 
			
		||||
            _monitor = monitor;
 | 
			
		||||
            _libraryManager = libraryManager;
 | 
			
		||||
            _itemRepo = itemRepo;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders)
 | 
			
		||||
@ -38,15 +44,45 @@ namespace MediaBrowser.Providers.Subtitles
 | 
			
		||||
 | 
			
		||||
        public async Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var contentType = request.ContentType;
 | 
			
		||||
            var providers = _subtitleProviders
 | 
			
		||||
                .Where(i => i.SupportedMediaTypes.Contains(request.ContentType))
 | 
			
		||||
                .Where(i => i.SupportedMediaTypes.Contains(contentType))
 | 
			
		||||
                .ToList();
 | 
			
		||||
 | 
			
		||||
            // If not searching all, search one at a time until something is found
 | 
			
		||||
            if (!request.SearchAllProviders)
 | 
			
		||||
            {
 | 
			
		||||
                foreach (var provider in providers)
 | 
			
		||||
                {
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var searchResults = await provider.Search(request, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                        var list = searchResults.ToList();
 | 
			
		||||
 | 
			
		||||
                        if (list.Count > 0)
 | 
			
		||||
                        {
 | 
			
		||||
                            Normalize(list);
 | 
			
		||||
                            return list;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (Exception ex)
 | 
			
		||||
                    {
 | 
			
		||||
                        _logger.ErrorException("Error downloading subtitles from {0}", ex, provider.Name);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                return new List<RemoteSubtitleInfo>();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var tasks = providers.Select(async i =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    return await i.Search(request, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                    var searchResults = await i.Search(request, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
                    var list = searchResults.ToList();
 | 
			
		||||
                    Normalize(list);
 | 
			
		||||
                    return list;
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
@ -62,17 +98,21 @@ namespace MediaBrowser.Providers.Subtitles
 | 
			
		||||
 | 
			
		||||
        public async Task DownloadSubtitles(Video video,
 | 
			
		||||
            string subtitleId,
 | 
			
		||||
            string providerName,
 | 
			
		||||
            CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var provider = _subtitleProviders.First(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
 | 
			
		||||
 | 
			
		||||
            var response = await provider.GetSubtitles(subtitleId, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
            var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
 | 
			
		||||
            using (var stream = response.Stream)
 | 
			
		||||
            {
 | 
			
		||||
                var savePath = Path.Combine(Path.GetDirectoryName(video.Path),
 | 
			
		||||
                    Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower() + "." + response.Format.ToLower());
 | 
			
		||||
                    Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower());
 | 
			
		||||
 | 
			
		||||
                if (response.IsForced)
 | 
			
		||||
                {
 | 
			
		||||
                    savePath += ".forced";
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                savePath += "." + response.Format.ToLower();
 | 
			
		||||
 | 
			
		||||
                _logger.Info("Saving subtitles to {0}", savePath);
 | 
			
		||||
 | 
			
		||||
@ -139,5 +179,93 @@ namespace MediaBrowser.Providers.Subtitles
 | 
			
		||||
 | 
			
		||||
            return SearchSubtitles(request, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void Normalize(IEnumerable<RemoteSubtitleInfo> subtitles)
 | 
			
		||||
        {
 | 
			
		||||
            foreach (var sub in subtitles)
 | 
			
		||||
            {
 | 
			
		||||
                sub.Id = GetProviderId(sub.ProviderName) + "_" + sub.Id;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string GetProviderId(string name)
 | 
			
		||||
        {
 | 
			
		||||
            return name.ToLower().GetMD5().ToString("N");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private ISubtitleProvider GetProvider(string id)
 | 
			
		||||
        {
 | 
			
		||||
            return _subtitleProviders.First(i => string.Equals(id, GetProviderId(i.Name)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task DeleteSubtitles(string itemId, int index)
 | 
			
		||||
        {
 | 
			
		||||
            var stream = _itemRepo.GetMediaStreams(new MediaStreamQuery
 | 
			
		||||
            {
 | 
			
		||||
                Index = index,
 | 
			
		||||
                ItemId = new Guid(itemId),
 | 
			
		||||
                Type = MediaStreamType.Subtitle
 | 
			
		||||
 | 
			
		||||
            }).First();
 | 
			
		||||
 | 
			
		||||
            var path = stream.Path;
 | 
			
		||||
            _monitor.ReportFileSystemChangeBeginning(path);
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                File.Delete(path);
 | 
			
		||||
            }
 | 
			
		||||
            finally
 | 
			
		||||
            {
 | 
			
		||||
                _monitor.ReportFileSystemChangeComplete(path, false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return _libraryManager.GetItemById(itemId).RefreshMetadata(new MetadataRefreshOptions
 | 
			
		||||
            {
 | 
			
		||||
                ImageRefreshMode = ImageRefreshMode.ValidationOnly,
 | 
			
		||||
                MetadataRefreshMode = MetadataRefreshMode.ValidationOnly
 | 
			
		||||
 | 
			
		||||
            }, CancellationToken.None);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<SubtitleResponse> GetRemoteSubtitles(string id, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var parts = id.Split(new[] { '_' }, 2);
 | 
			
		||||
 | 
			
		||||
            var provider = GetProvider(parts.First());
 | 
			
		||||
            id = parts.Last();
 | 
			
		||||
 | 
			
		||||
            return provider.GetSubtitles(id, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public IEnumerable<SubtitleProviderInfo> GetProviders(string itemId)
 | 
			
		||||
        {
 | 
			
		||||
            var video = _libraryManager.GetItemById(itemId) as Video;
 | 
			
		||||
            VideoContentType mediaType;
 | 
			
		||||
 | 
			
		||||
            if (video is Episode)
 | 
			
		||||
            {
 | 
			
		||||
                mediaType = VideoContentType.Episode;
 | 
			
		||||
            }
 | 
			
		||||
            else if (video is Movie)
 | 
			
		||||
            {
 | 
			
		||||
                mediaType = VideoContentType.Movie;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                // These are the only supported types
 | 
			
		||||
                return new List<SubtitleProviderInfo>();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var providers = _subtitleProviders
 | 
			
		||||
                .Where(i => i.SupportedMediaTypes.Contains(mediaType))
 | 
			
		||||
                .ToList();
 | 
			
		||||
 | 
			
		||||
            return providers.Select(i => new SubtitleProviderInfo
 | 
			
		||||
            {
 | 
			
		||||
                Name = i.Name,
 | 
			
		||||
                Id = GetProviderId(i.Name)
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// The library update duration
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        private const int LibraryUpdateDuration = 20000;
 | 
			
		||||
        private const int LibraryUpdateDuration = 5000;
 | 
			
		||||
 | 
			
		||||
        public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -759,5 +759,7 @@
 | 
			
		||||
	"LabelEpisodeNumber": "Episode number",
 | 
			
		||||
	"LabelEndingEpisodeNumber": "Ending episode number",
 | 
			
		||||
	"HeaderTypeText": "Enter Text",
 | 
			
		||||
	"LabelTypeText": "Text"
 | 
			
		||||
	"LabelTypeText": "Text",
 | 
			
		||||
	"HeaderSearchForSubtitles": "Search for Subtitles",
 | 
			
		||||
	"MessageNoSubtitleSearchResultsFound": "No search results founds."
 | 
			
		||||
}
 | 
			
		||||
@ -138,7 +138,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
 | 
			
		||||
                if (controller == null)
 | 
			
		||||
                {
 | 
			
		||||
                    controller = new WebSocketController(session, _appHost);
 | 
			
		||||
                    controller = new WebSocketController(session, _appHost, _logger);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                controller.Sockets.Add(message.Connection);
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@
 | 
			
		||||
using MediaBrowser.Controller;
 | 
			
		||||
using MediaBrowser.Controller.Session;
 | 
			
		||||
using MediaBrowser.Model.Entities;
 | 
			
		||||
using MediaBrowser.Model.Logging;
 | 
			
		||||
using MediaBrowser.Model.Net;
 | 
			
		||||
using MediaBrowser.Model.Session;
 | 
			
		||||
using MediaBrowser.Model.System;
 | 
			
		||||
@ -19,11 +20,13 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
        public List<IWebSocketConnection> Sockets { get; private set; }
 | 
			
		||||
 | 
			
		||||
        private readonly IServerApplicationHost _appHost;
 | 
			
		||||
        private readonly ILogger _logger;
 | 
			
		||||
 | 
			
		||||
        public WebSocketController(SessionInfo session, IServerApplicationHost appHost)
 | 
			
		||||
        public WebSocketController(SessionInfo session, IServerApplicationHost appHost, ILogger logger)
 | 
			
		||||
        {
 | 
			
		||||
            Session = session;
 | 
			
		||||
            _appHost = appHost;
 | 
			
		||||
            _logger = logger;
 | 
			
		||||
            Sockets = new List<IWebSocketConnection>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -35,11 +38,17 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IEnumerable<IWebSocketConnection> GetActiveSockets()
 | 
			
		||||
        {
 | 
			
		||||
            return Sockets
 | 
			
		||||
                .OrderByDescending(i => i.LastActivityDate)
 | 
			
		||||
                .Where(i => i.State == WebSocketState.Open);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private IWebSocketConnection GetActiveSocket()
 | 
			
		||||
        {
 | 
			
		||||
            var socket = Sockets
 | 
			
		||||
                .OrderByDescending(i => i.LastActivityDate)
 | 
			
		||||
                .FirstOrDefault(i => i.State == WebSocketState.Open);
 | 
			
		||||
            var socket = GetActiveSockets()
 | 
			
		||||
                .FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
            if (socket == null)
 | 
			
		||||
            {
 | 
			
		||||
@ -51,9 +60,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
 | 
			
		||||
        public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<PlayRequest>
 | 
			
		||||
            return SendMessage(new WebSocketMessage<PlayRequest>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "Play",
 | 
			
		||||
                Data = command
 | 
			
		||||
@ -63,9 +70,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
 | 
			
		||||
        public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<PlaystateRequest>
 | 
			
		||||
            return SendMessage(new WebSocketMessage<PlaystateRequest>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "Playstate",
 | 
			
		||||
                Data = command
 | 
			
		||||
@ -75,9 +80,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
 | 
			
		||||
        public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
            
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<LibraryUpdateInfo>
 | 
			
		||||
            return SendMessages(new WebSocketMessage<LibraryUpdateInfo>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "LibraryChanged",
 | 
			
		||||
                Data = info
 | 
			
		||||
@ -92,9 +95,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
        /// <returns>Task.</returns>
 | 
			
		||||
        public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<SystemInfo>
 | 
			
		||||
            return SendMessages(new WebSocketMessage<SystemInfo>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "RestartRequired",
 | 
			
		||||
                Data = _appHost.GetSystemInfo()
 | 
			
		||||
@ -111,9 +112,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
        /// <returns>Task.</returns>
 | 
			
		||||
        public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<UserDataChangeInfo>
 | 
			
		||||
            return SendMessages(new WebSocketMessage<UserDataChangeInfo>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "UserDataChanged",
 | 
			
		||||
                Data = info
 | 
			
		||||
@ -128,9 +127,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
        /// <returns>Task.</returns>
 | 
			
		||||
        public Task SendServerShutdownNotification(CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<string>
 | 
			
		||||
            return SendMessages(new WebSocketMessage<string>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "ServerShuttingDown",
 | 
			
		||||
                Data = string.Empty
 | 
			
		||||
@ -145,9 +142,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
        /// <returns>Task.</returns>
 | 
			
		||||
        public Task SendServerRestartNotification(CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<string>
 | 
			
		||||
            return SendMessages(new WebSocketMessage<string>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "ServerRestarting",
 | 
			
		||||
                Data = string.Empty
 | 
			
		||||
@ -157,9 +152,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
 | 
			
		||||
        public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<GeneralCommand>
 | 
			
		||||
            return SendMessage(new WebSocketMessage<GeneralCommand>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "GeneralCommand",
 | 
			
		||||
                Data = command
 | 
			
		||||
@ -169,9 +162,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
 | 
			
		||||
        public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
 | 
			
		||||
            return SendMessages(new WebSocketMessage<SessionInfoDto>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "SessionEnded",
 | 
			
		||||
                Data = sessionInfo
 | 
			
		||||
@ -181,9 +172,7 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
 | 
			
		||||
        public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
 | 
			
		||||
            return SendMessages(new WebSocketMessage<SessionInfoDto>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "PlaybackStart",
 | 
			
		||||
                Data = sessionInfo
 | 
			
		||||
@ -193,14 +182,37 @@ namespace MediaBrowser.Server.Implementations.Session
 | 
			
		||||
 | 
			
		||||
        public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
 | 
			
		||||
            return SendMessages(new WebSocketMessage<SessionInfoDto>
 | 
			
		||||
            {
 | 
			
		||||
                MessageType = "PlaybackStopped",
 | 
			
		||||
                Data = sessionInfo
 | 
			
		||||
 | 
			
		||||
            }, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task SendMessage<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var socket = GetActiveSocket();
 | 
			
		||||
 | 
			
		||||
            return socket.SendAsync(message, cancellationToken);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private Task SendMessages<T>(WebSocketMessage<T> message, CancellationToken cancellationToken)
 | 
			
		||||
        {
 | 
			
		||||
            var tasks = GetActiveSockets().Select(i => Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await i.SendAsync(message, cancellationToken).ConfigureAwait(false);
 | 
			
		||||
                }
 | 
			
		||||
                catch (Exception ex)
 | 
			
		||||
                {
 | 
			
		||||
                    _logger.ErrorException("Error sending web socket message", ex);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }, cancellationToken));
 | 
			
		||||
 | 
			
		||||
            return Task.WhenAll(tasks);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -537,7 +537,7 @@ namespace MediaBrowser.ServerApplication
 | 
			
		||||
 | 
			
		||||
            RegisterSingleInstance<IEncryptionManager>(new EncryptionManager());
 | 
			
		||||
 | 
			
		||||
            SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor);
 | 
			
		||||
            SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, ItemRepository);
 | 
			
		||||
            RegisterSingleInstance(SubtitleManager);
 | 
			
		||||
 | 
			
		||||
            var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));
 | 
			
		||||
 | 
			
		||||
@ -550,6 +550,7 @@ namespace MediaBrowser.WebDashboard.Api
 | 
			
		||||
                                "editcollectionitems.js",
 | 
			
		||||
                                "edititemmetadata.js",
 | 
			
		||||
                                "edititemimages.js",
 | 
			
		||||
                                "edititemsubtitles.js",
 | 
			
		||||
                                "encodingsettings.js",
 | 
			
		||||
                                "gamesrecommendedpage.js",
 | 
			
		||||
                                "gamesystemspage.js",
 | 
			
		||||
 | 
			
		||||
@ -307,6 +307,9 @@
 | 
			
		||||
    <Content Include="dashboard-ui\editcollectionitems.html">
 | 
			
		||||
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
    </Content>
 | 
			
		||||
    <Content Include="dashboard-ui\edititemsubtitles.html">
 | 
			
		||||
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
    </Content>
 | 
			
		||||
    <Content Include="dashboard-ui\encodingsettings.html">
 | 
			
		||||
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
    </Content>
 | 
			
		||||
@ -616,6 +619,9 @@
 | 
			
		||||
    <Content Include="dashboard-ui\scripts\editcollectionitems.js">
 | 
			
		||||
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
    </Content>
 | 
			
		||||
    <Content Include="dashboard-ui\scripts\edititemsubtitles.js">
 | 
			
		||||
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
    </Content>
 | 
			
		||||
    <Content Include="dashboard-ui\scripts\encodingsettings.js">
 | 
			
		||||
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
 | 
			
		||||
    </Content>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user