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