diff options
21 files changed, 426 insertions, 129 deletions
diff --git a/MediaBrowser.Api/ItemLookupService.cs b/MediaBrowser.Api/ItemLookupService.cs index 86fdd6da8..ff11ad47c 100644 --- a/MediaBrowser.Api/ItemLookupService.cs +++ b/MediaBrowser.Api/ItemLookupService.cs @@ -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) diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 0e5a3ab25..802df5cca 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -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> diff --git a/MediaBrowser.Api/Library/SubtitleService.cs b/MediaBrowser.Api/Library/SubtitleService.cs new file mode 100644 index 000000000..78ae627ea --- /dev/null +++ b/MediaBrowser.Api/Library/SubtitleService.cs @@ -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); + } + + }); + } + } +} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index edbae3903..62d5a6ce2 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -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" /> diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index 780aa6a56..35e86fb87 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.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 } } diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs index 8b0ef223c..1d66d1505 100644 --- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs +++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs @@ -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); } } diff --git a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs index 69e92c1f5..e2f6dfc97 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleResponse.cs @@ -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; } } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs index e83387129..e781c048b 100644 --- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs +++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs @@ -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); } } diff --git a/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs b/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs index 4371ddae4..07a4b5f60 100644 --- a/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs +++ b/MediaBrowser.Model/Entities/LibraryUpdateInfo.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace MediaBrowser.Model.Entities { diff --git a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs index 0a4a52cd5..aa697fee3 100644 --- a/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs +++ b/MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs @@ -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; } + } } diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs index 9041d5a92..bbbcaeedf 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs @@ -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) diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 5ce53378c..1cc04ed2a 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -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)) diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs index cf14cfadc..0df2b24f5 100644 --- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs +++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs @@ -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; diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs index 2d5445653..08499a20b 100644 --- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs +++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs @@ -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()); + var savePath = Path.Combine(Path.GetDirectoryName(video.Path), + 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) + }); + } } } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 40eeea651..e6976c54b 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -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) { diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 9af858234..266978d03 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -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." }
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs index fd10c0389..565d83ac3 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs @@ -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); diff --git a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs index 0edd57d2a..040705171 100644 --- a/MediaBrowser.Server.Implementations/Session/WebSocketController.cs +++ b/MediaBrowser.Server.Implementations/Session/WebSocketController.cs @@ -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 IWebSocketConnection GetActiveSocket() + private IEnumerable<IWebSocketConnection> GetActiveSockets() { - var socket = Sockets + return Sockets .OrderByDescending(i => i.LastActivityDate) - .FirstOrDefault(i => i.State == WebSocketState.Open); + .Where(i => i.State == WebSocketState.Open); + } + + private IWebSocketConnection GetActiveSocket() + { + 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); + } } } diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index ed264996b..831067a9e 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -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)); diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 158d1eccd..f679206f0 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -550,6 +550,7 @@ namespace MediaBrowser.WebDashboard.Api "editcollectionitems.js", "edititemmetadata.js", "edititemimages.js", + "edititemsubtitles.js", "encodingsettings.js", "gamesrecommendedpage.js", "gamesystemspage.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index b22a95a37..6aba03efc 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -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>
|
