aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Api/ItemLookupService.cs24
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs37
-rw-r--r--MediaBrowser.Api/Library/SubtitleService.cs162
-rw-r--r--MediaBrowser.Api/MediaBrowser.Api.csproj1
-rw-r--r--MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs17
-rw-r--r--MediaBrowser.Controller/Subtitles/ISubtitleManager.cs25
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleResponse.cs1
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs3
-rw-r--r--MediaBrowser.Model/Entities/LibraryUpdateInfo.cs3
-rw-r--r--MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs6
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs2
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs16
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs7
-rw-r--r--MediaBrowser.Providers/Subtitles/SubtitleManager.cs148
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Server/server.json4
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionWebSocketListener.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Session/WebSocketController.cs86
-rw-r--r--MediaBrowser.ServerApplication/ApplicationHost.cs2
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs1
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj6
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>