add subtitle management page

This commit is contained in:
Luke Pulverenti 2014-05-17 00:24:10 -04:00
parent 26aa47eefd
commit c8e4889ac7
21 changed files with 426 additions and 129 deletions

View File

@ -7,7 +7,6 @@ using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers; using MediaBrowser.Model.Providers;
using ServiceStack; using ServiceStack;
@ -32,16 +31,6 @@ namespace MediaBrowser.Api
public string Id { get; set; } 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")] [Route("/Items/RemoteSearch/Movie", "POST")]
[Api(Description = "Gets external id infos for an item")] [Api(Description = "Gets external id infos for an item")]
public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>> public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
@ -121,24 +110,13 @@ namespace MediaBrowser.Api
private readonly IServerApplicationPaths _appPaths; private readonly IServerApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager; 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; _providerManager = providerManager;
_appPaths = appPaths; _appPaths = appPaths;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_libraryManager = libraryManager; _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) public object Get(GetExternalIdInfos request)

View File

@ -1,5 +1,4 @@
using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -35,21 +34,6 @@ namespace MediaBrowser.Api.Library
public string Id { get; set; } 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> /// <summary>
/// Class GetCriticReviews /// Class GetCriticReviews
/// </summary> /// </summary>
@ -305,25 +289,6 @@ namespace MediaBrowser.Api.Library
return ToStaticFileResult(item.Path); 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> /// <summary>
/// Gets the specified request. /// Gets the specified request.
/// </summary> /// </summary>

View 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);
}
});
}
}
}

View File

@ -68,6 +68,7 @@
<Compile Include="ChannelService.cs" /> <Compile Include="ChannelService.cs" />
<Compile Include="Dlna\DlnaServerService.cs" /> <Compile Include="Dlna\DlnaServerService.cs" />
<Compile Include="Dlna\DlnaService.cs" /> <Compile Include="Dlna\DlnaService.cs" />
<Compile Include="Library\SubtitleService.cs" />
<Compile Include="Movies\CollectionService.cs" /> <Compile Include="Movies\CollectionService.cs" />
<Compile Include="Music\AlbumsService.cs" /> <Compile Include="Music\AlbumsService.cs" />
<Compile Include="AppThemeService.cs" /> <Compile Include="AppThemeService.cs" />

View File

@ -34,17 +34,22 @@ namespace MediaBrowser.Controller.Providers
/// <summary> /// <summary>
/// Providers will be executed based on default rules /// Providers will be executed based on default rules
/// </summary> /// </summary>
EnsureMetadata, EnsureMetadata = 0,
/// <summary> /// <summary>
/// No providers will be executed /// No providers will be executed
/// </summary> /// </summary>
None, None = 1,
/// <summary> /// <summary>
/// All providers will be executed to search for new metadata /// All providers will be executed to search for new metadata
/// </summary> /// </summary>
FullRefresh FullRefresh = 2,
/// <summary>
/// The validation only
/// </summary>
ValidationOnly = 3
} }
public enum ImageRefreshMode public enum ImageRefreshMode
@ -52,16 +57,16 @@ namespace MediaBrowser.Controller.Providers
/// <summary> /// <summary>
/// The default /// The default
/// </summary> /// </summary>
Default, Default = 0,
/// <summary> /// <summary>
/// Existing images will be validated /// Existing images will be validated
/// </summary> /// </summary>
ValidationOnly, ValidationOnly = 1,
/// <summary> /// <summary>
/// All providers will be executed to search for new metadata /// All providers will be executed to search for new metadata
/// </summary> /// </summary>
FullRefresh FullRefresh = 2
} }
} }

View File

@ -39,12 +39,33 @@ namespace MediaBrowser.Controller.Subtitles
/// </summary> /// </summary>
/// <param name="video">The video.</param> /// <param name="video">The video.</param>
/// <param name="subtitleId">The subtitle identifier.</param> /// <param name="subtitleId">The subtitle identifier.</param>
/// <param name="providerName">Name of the provider.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task DownloadSubtitles(Video video, Task DownloadSubtitles(Video video,
string subtitleId, string subtitleId,
string providerName,
CancellationToken cancellationToken); 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);
} }
} }

View File

@ -6,6 +6,7 @@ namespace MediaBrowser.Controller.Subtitles
{ {
public string Language { get; set; } public string Language { get; set; }
public string Format { get; set; } public string Format { get; set; }
public bool IsForced { get; set; }
public Stream Stream { get; set; } public Stream Stream { get; set; }
} }
} }

View File

@ -21,8 +21,11 @@ namespace MediaBrowser.Controller.Subtitles
public long? RuntimeTicks { get; set; } public long? RuntimeTicks { get; set; }
public Dictionary<string, string> ProviderIds { get; set; } public Dictionary<string, string> ProviderIds { get; set; }
public bool SearchAllProviders { get; set; }
public SubtitleSearchRequest() public SubtitleSearchRequest()
{ {
SearchAllProviders = true;
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
} }
} }

View File

@ -1,5 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
namespace MediaBrowser.Model.Entities namespace MediaBrowser.Model.Entities
{ {

View File

@ -16,4 +16,10 @@ namespace MediaBrowser.Model.Providers
public int? DownloadCount { get; set; } public int? DownloadCount { get; set; }
public bool? IsHashMatch { get; set; } public bool? IsHashMatch { get; set; }
} }
public class SubtitleProviderInfo
{
public string Name { get; set; }
public string Id { get; set; }
}
} }

View File

@ -142,7 +142,7 @@ namespace MediaBrowser.Providers.MediaInfo
var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager); 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) public Task<ItemUpdateType> FetchAudioInfo<T>(T item, CancellationToken cancellationToken)

View File

@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.MediaInfo
_subtitleManager = subtitleManager; _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 where T : Video
{ {
var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false); var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false);
@ -105,7 +105,7 @@ namespace MediaBrowser.Providers.MediaInfo
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, directoryService).ConfigureAwait(false); await Fetch(item, cancellationToken, result, isoMount, blurayDiscInfo, directoryService, enableSubtitleDownloading).ConfigureAwait(false);
} }
finally finally
@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.MediaInfo
return result; 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 mediaInfo = MediaEncoderHelpers.GetMediaInfo(data);
var mediaStreams = mediaInfo.MediaStreams; var mediaStreams = mediaInfo.MediaStreams;
@ -208,7 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo
FetchBdInfo(video, chapters, mediaStreams, blurayInfo); 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); FetchWtvInfo(video, data);
@ -416,13 +416,17 @@ namespace MediaBrowser.Providers.MediaInfo
/// </summary> /// </summary>
/// <param name="video">The video.</param> /// <param name="video">The video.</param>
/// <param name="currentStreams">The current streams.</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 subtitleResolver = new SubtitleResolver(_localization);
var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, directoryService, false).ToList(); var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, currentStreams.Count, directoryService, false).ToList();
if ((_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles && if (enableSubtitleDownloading && (_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles &&
video is Episode) || video is Episode) ||
(_config.Configuration.SubtitleOptions.DownloadMovieSubtitles && (_config.Configuration.SubtitleOptions.DownloadMovieSubtitles &&
video is Movie)) video is Movie))

View File

@ -124,7 +124,10 @@ namespace MediaBrowser.Providers.MediaInfo
Name = video.Name, Name = video.Name,
ParentIndexNumber = video.ParentIndexNumber, ParentIndexNumber = video.ParentIndexNumber,
ProductionYear = video.ProductionYear, ProductionYear = video.ProductionYear,
ProviderIds = video.ProviderIds ProviderIds = video.ProviderIds,
// Stop as soon as we find something
SearchAllProviders = false
}; };
var episode = video as Episode; var episode = video as Episode;
@ -143,7 +146,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (result != null) if (result != null)
{ {
await _subtitleManager.DownloadSubtitles(video, result.Id, result.ProviderName, cancellationToken) await _subtitleManager.DownloadSubtitles(video, result.Id, cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
return true; return true;

View File

@ -1,8 +1,10 @@
using MediaBrowser.Common.IO; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.Subtitles;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -23,12 +25,16 @@ namespace MediaBrowser.Providers.Subtitles
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ILibraryMonitor _monitor; 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; _logger = logger;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_monitor = monitor; _monitor = monitor;
_libraryManager = libraryManager;
_itemRepo = itemRepo;
} }
public void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders) public void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders)
@ -38,15 +44,45 @@ namespace MediaBrowser.Providers.Subtitles
public async Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken) public async Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken)
{ {
var contentType = request.ContentType;
var providers = _subtitleProviders var providers = _subtitleProviders
.Where(i => i.SupportedMediaTypes.Contains(request.ContentType)) .Where(i => i.SupportedMediaTypes.Contains(contentType))
.ToList(); .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 => var tasks = providers.Select(async i =>
{ {
try 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) catch (Exception ex)
{ {
@ -62,17 +98,21 @@ namespace MediaBrowser.Providers.Subtitles
public async Task DownloadSubtitles(Video video, public async Task DownloadSubtitles(Video video,
string subtitleId, string subtitleId,
string providerName,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var provider = _subtitleProviders.First(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase)); var response = await GetRemoteSubtitles(subtitleId, cancellationToken).ConfigureAwait(false);
var response = await provider.GetSubtitles(subtitleId, cancellationToken).ConfigureAwait(false);
using (var stream = response.Stream) using (var stream = response.Stream)
{ {
var savePath = Path.Combine(Path.GetDirectoryName(video.Path), 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); _logger.Info("Saving subtitles to {0}", savePath);
@ -139,5 +179,93 @@ namespace MediaBrowser.Providers.Subtitles
return SearchSubtitles(request, cancellationToken); 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)
});
}
} }
} }

View File

@ -44,7 +44,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
/// <summary> /// <summary>
/// The library update duration /// The library update duration
/// </summary> /// </summary>
private const int LibraryUpdateDuration = 20000; private const int LibraryUpdateDuration = 5000;
public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger) public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger)
{ {

View File

@ -759,5 +759,7 @@
"LabelEpisodeNumber": "Episode number", "LabelEpisodeNumber": "Episode number",
"LabelEndingEpisodeNumber": "Ending episode number", "LabelEndingEpisodeNumber": "Ending episode number",
"HeaderTypeText": "Enter Text", "HeaderTypeText": "Enter Text",
"LabelTypeText": "Text" "LabelTypeText": "Text",
"HeaderSearchForSubtitles": "Search for Subtitles",
"MessageNoSubtitleSearchResultsFound": "No search results founds."
} }

View File

@ -138,7 +138,7 @@ namespace MediaBrowser.Server.Implementations.Session
if (controller == null) if (controller == null)
{ {
controller = new WebSocketController(session, _appHost); controller = new WebSocketController(session, _appHost, _logger);
} }
controller.Sockets.Add(message.Connection); controller.Sockets.Add(message.Connection);

View File

@ -2,6 +2,7 @@
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
using MediaBrowser.Model.System; using MediaBrowser.Model.System;
@ -19,11 +20,13 @@ namespace MediaBrowser.Server.Implementations.Session
public List<IWebSocketConnection> Sockets { get; private set; } public List<IWebSocketConnection> Sockets { get; private set; }
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
public WebSocketController(SessionInfo session, IServerApplicationHost appHost) public WebSocketController(SessionInfo session, IServerApplicationHost appHost, ILogger logger)
{ {
Session = session; Session = session;
_appHost = appHost; _appHost = appHost;
_logger = logger;
Sockets = new List<IWebSocketConnection>(); 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() private IWebSocketConnection GetActiveSocket()
{ {
var socket = Sockets var socket = GetActiveSockets()
.OrderByDescending(i => i.LastActivityDate) .FirstOrDefault();
.FirstOrDefault(i => i.State == WebSocketState.Open);
if (socket == null) if (socket == null)
{ {
@ -51,9 +60,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken) public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessage(new WebSocketMessage<PlayRequest>
return socket.SendAsync(new WebSocketMessage<PlayRequest>
{ {
MessageType = "Play", MessageType = "Play",
Data = command Data = command
@ -63,9 +70,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken) public Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessage(new WebSocketMessage<PlaystateRequest>
return socket.SendAsync(new WebSocketMessage<PlaystateRequest>
{ {
MessageType = "Playstate", MessageType = "Playstate",
Data = command Data = command
@ -75,9 +80,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken) public Task SendLibraryUpdateInfo(LibraryUpdateInfo info, CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessages(new WebSocketMessage<LibraryUpdateInfo>
return socket.SendAsync(new WebSocketMessage<LibraryUpdateInfo>
{ {
MessageType = "LibraryChanged", MessageType = "LibraryChanged",
Data = info Data = info
@ -92,9 +95,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns> /// <returns>Task.</returns>
public Task SendRestartRequiredNotification(CancellationToken cancellationToken) public Task SendRestartRequiredNotification(CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessages(new WebSocketMessage<SystemInfo>
return socket.SendAsync(new WebSocketMessage<SystemInfo>
{ {
MessageType = "RestartRequired", MessageType = "RestartRequired",
Data = _appHost.GetSystemInfo() Data = _appHost.GetSystemInfo()
@ -111,9 +112,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns> /// <returns>Task.</returns>
public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken) public Task SendUserDataChangeInfo(UserDataChangeInfo info, CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessages(new WebSocketMessage<UserDataChangeInfo>
return socket.SendAsync(new WebSocketMessage<UserDataChangeInfo>
{ {
MessageType = "UserDataChanged", MessageType = "UserDataChanged",
Data = info Data = info
@ -128,9 +127,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns> /// <returns>Task.</returns>
public Task SendServerShutdownNotification(CancellationToken cancellationToken) public Task SendServerShutdownNotification(CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessages(new WebSocketMessage<string>
return socket.SendAsync(new WebSocketMessage<string>
{ {
MessageType = "ServerShuttingDown", MessageType = "ServerShuttingDown",
Data = string.Empty Data = string.Empty
@ -145,9 +142,7 @@ namespace MediaBrowser.Server.Implementations.Session
/// <returns>Task.</returns> /// <returns>Task.</returns>
public Task SendServerRestartNotification(CancellationToken cancellationToken) public Task SendServerRestartNotification(CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessages(new WebSocketMessage<string>
return socket.SendAsync(new WebSocketMessage<string>
{ {
MessageType = "ServerRestarting", MessageType = "ServerRestarting",
Data = string.Empty Data = string.Empty
@ -157,9 +152,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken) public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessage(new WebSocketMessage<GeneralCommand>
return socket.SendAsync(new WebSocketMessage<GeneralCommand>
{ {
MessageType = "GeneralCommand", MessageType = "GeneralCommand",
Data = command Data = command
@ -169,9 +162,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) public Task SendSessionEndedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessages(new WebSocketMessage<SessionInfoDto>
return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
{ {
MessageType = "SessionEnded", MessageType = "SessionEnded",
Data = sessionInfo Data = sessionInfo
@ -181,9 +172,7 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) public Task SendPlaybackStartNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessages(new WebSocketMessage<SessionInfoDto>
return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
{ {
MessageType = "PlaybackStart", MessageType = "PlaybackStart",
Data = sessionInfo Data = sessionInfo
@ -193,14 +182,37 @@ namespace MediaBrowser.Server.Implementations.Session
public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken) public Task SendPlaybackStoppedNotification(SessionInfoDto sessionInfo, CancellationToken cancellationToken)
{ {
var socket = GetActiveSocket(); return SendMessages(new WebSocketMessage<SessionInfoDto>
return socket.SendAsync(new WebSocketMessage<SessionInfoDto>
{ {
MessageType = "PlaybackStopped", MessageType = "PlaybackStopped",
Data = sessionInfo Data = sessionInfo
}, cancellationToken); }, 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);
}
} }
} }

View File

@ -537,7 +537,7 @@ namespace MediaBrowser.ServerApplication
RegisterSingleInstance<IEncryptionManager>(new EncryptionManager()); RegisterSingleInstance<IEncryptionManager>(new EncryptionManager());
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor); SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, ItemRepository);
RegisterSingleInstance(SubtitleManager); RegisterSingleInstance(SubtitleManager);
var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false)); var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));

View File

@ -550,6 +550,7 @@ namespace MediaBrowser.WebDashboard.Api
"editcollectionitems.js", "editcollectionitems.js",
"edititemmetadata.js", "edititemmetadata.js",
"edititemimages.js", "edititemimages.js",
"edititemsubtitles.js",
"encodingsettings.js", "encodingsettings.js",
"gamesrecommendedpage.js", "gamesrecommendedpage.js",
"gamesystemspage.js", "gamesystemspage.js",

View File

@ -307,6 +307,9 @@
<Content Include="dashboard-ui\editcollectionitems.html"> <Content Include="dashboard-ui\editcollectionitems.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\edititemsubtitles.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\encodingsettings.html"> <Content Include="dashboard-ui\encodingsettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@ -616,6 +619,9 @@
<Content Include="dashboard-ui\scripts\editcollectionitems.js"> <Content Include="dashboard-ui\scripts\editcollectionitems.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="dashboard-ui\scripts\edititemsubtitles.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\encodingsettings.js"> <Content Include="dashboard-ui\scripts\encodingsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>