beginning remote subtitle downloading
This commit is contained in:
parent
e1dd361c7b
commit
0d025f7fb6
|
@ -67,7 +67,7 @@ namespace MediaBrowser.Api
|
|||
[ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public SortOrder? SortOrder { get; set; }
|
||||
|
||||
[ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsRecentlyAdded, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
||||
[ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
||||
public string Filters { get; set; }
|
||||
|
||||
[ApiMember(Name = "SortBy", Description = "Optional. Specify one or more sort orders, comma delimeted. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using ServiceStack;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -70,6 +72,32 @@ namespace MediaBrowser.Api.Images
|
|||
public string Theme { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Images/MediaInfo", "GET")]
|
||||
[Api(Description = "Gets all media info image by name")]
|
||||
public class GetMediaInfoImages : IReturn<List<ImageByNameInfo>>
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/Images/Ratings", "GET")]
|
||||
[Api(Description = "Gets all rating images by name")]
|
||||
public class GetRatingImages : IReturn<List<ImageByNameInfo>>
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/Images/General", "GET")]
|
||||
[Api(Description = "Gets all general images by name")]
|
||||
public class GetGeneralImages : IReturn<List<ImageByNameInfo>>
|
||||
{
|
||||
}
|
||||
|
||||
public class ImageByNameInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Theme { get; set; }
|
||||
public long FileLength { get; set; }
|
||||
public string Format { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class ImageByNameService
|
||||
/// </summary>
|
||||
|
@ -89,6 +117,60 @@ namespace MediaBrowser.Api.Images
|
|||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
public object Get(GetMediaInfoImages request)
|
||||
{
|
||||
return ToOptimizedResult(GetImageList(_appPaths.MediaInfoImagesPath));
|
||||
}
|
||||
|
||||
public object Get(GetRatingImages request)
|
||||
{
|
||||
return ToOptimizedResult(GetImageList(_appPaths.RatingsPath));
|
||||
}
|
||||
|
||||
public object Get(GetGeneralImages request)
|
||||
{
|
||||
return ToOptimizedResult(GetImageList(_appPaths.GeneralPath));
|
||||
}
|
||||
|
||||
private List<ImageByNameInfo> GetImageList(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new DirectoryInfo(path)
|
||||
.GetFiles("*", SearchOption.AllDirectories)
|
||||
.Where(i => BaseItem.SupportedImageExtensions.Contains(i.Extension, StringComparer.Ordinal))
|
||||
.Select(i => new ImageByNameInfo
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(i.FullName),
|
||||
FileLength = i.Length,
|
||||
Theme = GetThemeName(i.FullName, path),
|
||||
Format = i.Extension.ToLower().TrimStart('.')
|
||||
})
|
||||
.OrderBy(i => i.Name)
|
||||
.ToList();
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
return new List<ImageByNameInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
private string GetThemeName(string path, string rootImagePath)
|
||||
{
|
||||
var parentName = Path.GetDirectoryName(path);
|
||||
|
||||
if (string.Equals(parentName, rootImagePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
parentName = Path.GetFileName(parentName);
|
||||
|
||||
return string.Equals(parentName, "all", StringComparison.OrdinalIgnoreCase) ?
|
||||
null :
|
||||
parentName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified request.
|
||||
/// </summary>
|
||||
|
@ -118,7 +200,8 @@ namespace MediaBrowser.Api.Images
|
|||
|
||||
if (Directory.Exists(themeFolder))
|
||||
{
|
||||
var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(themeFolder, request.Name + i))
|
||||
var path = BaseItem.SupportedImageExtensions
|
||||
.Select(i => Path.Combine(themeFolder, request.Name + i))
|
||||
.FirstOrDefault(File.Exists);
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
|
@ -134,7 +217,8 @@ namespace MediaBrowser.Api.Images
|
|||
// Avoid implicitly captured closure
|
||||
var currentRequest = request;
|
||||
|
||||
var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, currentRequest.Name + i))
|
||||
var path = BaseItem.SupportedImageExtensions
|
||||
.Select(i => Path.Combine(allFolder, currentRequest.Name + i))
|
||||
.FirstOrDefault(File.Exists);
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
|
@ -175,7 +259,7 @@ namespace MediaBrowser.Api.Images
|
|||
|
||||
var path = BaseItem.SupportedImageExtensions.Select(i => Path.Combine(allFolder, currentRequest.Name + i))
|
||||
.FirstOrDefault(File.Exists);
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
return ToStaticFileResult(path);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Globalization;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
|
@ -14,6 +13,7 @@ using ServiceStack.Text.Controller;
|
|||
using ServiceStack.Web;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
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,6 +32,16 @@ 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>>
|
||||
|
@ -107,19 +117,28 @@ namespace MediaBrowser.Api
|
|||
|
||||
public class ItemLookupService : BaseApiService
|
||||
{
|
||||
private readonly IDtoService _dtoService;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly IServerApplicationPaths _appPaths;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ISubtitleManager _subtitleManager;
|
||||
|
||||
public ItemLookupService(IDtoService dtoService, IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem, ILibraryManager libraryManager)
|
||||
public ItemLookupService(IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem, ILibraryManager libraryManager, ISubtitleManager subtitleManager)
|
||||
{
|
||||
_dtoService = dtoService;
|
||||
_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)
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
/// Filters to apply to the results
|
||||
/// </summary>
|
||||
/// <value>The filters.</value>
|
||||
[ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsRecentlyAdded, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
||||
[ApiMember(Name = "Filters", Description = "Optional. Specify additional filters to apply. This allows multiple, comma delimeted. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
|
||||
public string Filters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -521,6 +521,9 @@ namespace MediaBrowser.Api.UserLibrary
|
|||
|
||||
case ItemFilter.IsNotFolder:
|
||||
return items.Where(item => !item.IsFolder);
|
||||
|
||||
case ItemFilter.IsRecentlyAdded:
|
||||
return items.Where(item => (DateTime.UtcNow - item.DateCreated).TotalDays <= 10);
|
||||
}
|
||||
|
||||
return items;
|
||||
|
|
|
@ -114,9 +114,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||
|
||||
request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
|
||||
|
||||
request.CachePolicy = options.CachePolicy == Net.HttpRequestCachePolicy.None ?
|
||||
new RequestCachePolicy(RequestCacheLevel.BypassCache) :
|
||||
new RequestCachePolicy(RequestCacheLevel.Revalidate);
|
||||
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
|
||||
|
||||
request.ConnectionGroupName = GetHostFromUrl(options.Url);
|
||||
request.KeepAlive = true;
|
||||
|
@ -124,6 +122,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||
request.Pipelined = true;
|
||||
request.Timeout = 20000;
|
||||
|
||||
if (!string.IsNullOrEmpty(options.Host))
|
||||
{
|
||||
request.Host = options.Host;
|
||||
}
|
||||
|
||||
#if !__MonoCS__
|
||||
// This is a hack to prevent KeepAlive from getting disabled internally by the HttpWebRequest
|
||||
// May need to remove this for mono
|
||||
|
@ -234,9 +237,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
|||
!string.IsNullOrEmpty(options.RequestContent) ||
|
||||
string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var bytes = options.RequestContentBytes ?? Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty);
|
||||
var bytes = options.RequestContentBytes ??
|
||||
Encoding.UTF8.GetBytes(options.RequestContent ?? string.Empty);
|
||||
|
||||
httpWebRequest.ContentType = options.RequestContentType ?? "application/x-www-form-urlencoded";
|
||||
|
||||
httpWebRequest.ContentLength = bytes.Length;
|
||||
httpWebRequest.GetRequestStream().Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
|
|||
private readonly ILogger _logger;
|
||||
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
|
||||
/// </summary>
|
||||
|
@ -74,9 +74,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
|
|||
|
||||
progress.Report(90);
|
||||
|
||||
minDateModified = DateTime.UtcNow.AddDays(-3);
|
||||
|
||||
try
|
||||
{
|
||||
DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, DateTime.MaxValue, progress);
|
||||
DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, minDateModified, progress);
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
|
|
|
@ -52,6 +52,12 @@ namespace MediaBrowser.Common.Net
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the host.
|
||||
/// </summary>
|
||||
/// <value>The host.</value>
|
||||
public string Host { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the progress.
|
||||
/// </summary>
|
||||
|
@ -76,8 +82,6 @@ namespace MediaBrowser.Common.Net
|
|||
public bool LogRequest { get; set; }
|
||||
|
||||
public bool LogErrorResponseBody { get; set; }
|
||||
|
||||
public HttpRequestCachePolicy CachePolicy { get; set; }
|
||||
|
||||
private string GetHeaderValue(string name)
|
||||
{
|
||||
|
@ -96,17 +100,9 @@ namespace MediaBrowser.Common.Net
|
|||
EnableHttpCompression = true;
|
||||
BufferContent = true;
|
||||
|
||||
CachePolicy = HttpRequestCachePolicy.None;
|
||||
|
||||
RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
LogRequest = true;
|
||||
}
|
||||
}
|
||||
|
||||
public enum HttpRequestCachePolicy
|
||||
{
|
||||
None = 1,
|
||||
Validate = 2
|
||||
}
|
||||
}
|
||||
|
|
|
@ -191,7 +191,8 @@
|
|||
<Compile Include="Providers\IMetadataProvider.cs" />
|
||||
<Compile Include="Providers\IMetadataService.cs" />
|
||||
<Compile Include="Providers\IRemoteMetadataProvider.cs" />
|
||||
<Compile Include="Providers\ISubtitleProvider.cs" />
|
||||
<Compile Include="Subtitles\ISubtitleManager.cs" />
|
||||
<Compile Include="Subtitles\ISubtitleProvider.cs" />
|
||||
<Compile Include="Providers\ItemLookupInfo.cs" />
|
||||
<Compile Include="Providers\MetadataRefreshOptions.cs" />
|
||||
<Compile Include="Providers\NameParser.cs" />
|
||||
|
|
50
MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
Normal file
50
MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
Normal file
|
@ -0,0 +1,50 @@
|
|||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.Subtitles
|
||||
{
|
||||
public interface ISubtitleManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the parts.
|
||||
/// </summary>
|
||||
/// <param name="subtitleProviders">The subtitle providers.</param>
|
||||
void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders);
|
||||
|
||||
/// <summary>
|
||||
/// Searches the subtitles.
|
||||
/// </summary>
|
||||
/// <param name="video">The video.</param>
|
||||
/// <param name="language">The language.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{IEnumerable{RemoteSubtitleInfo}}.</returns>
|
||||
Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(Video video,
|
||||
string language,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Searches the subtitles.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{IEnumerable{RemoteSubtitleInfo}}.</returns>
|
||||
Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Downloads the 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);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Controller.Providers
|
||||
namespace MediaBrowser.Controller.Subtitles
|
||||
{
|
||||
public interface ISubtitleProvider
|
||||
{
|
||||
|
@ -22,12 +23,20 @@ namespace MediaBrowser.Controller.Providers
|
|||
IEnumerable<SubtitleMediaType> SupportedMediaTypes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subtitles.
|
||||
/// Searches the subtitles.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{IEnumerable{RemoteSubtitleInfo}}.</returns>
|
||||
Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the subtitles.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{SubtitleResponse}.</returns>
|
||||
Task<SubtitleResponse> GetSubtitles(SubtitleRequest request, CancellationToken cancellationToken);
|
||||
Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public enum SubtitleMediaType
|
||||
|
@ -38,12 +47,12 @@ namespace MediaBrowser.Controller.Providers
|
|||
|
||||
public class SubtitleResponse
|
||||
{
|
||||
public string Language { get; set; }
|
||||
public string Format { get; set; }
|
||||
public bool HasContent { get; set; }
|
||||
public Stream Stream { get; set; }
|
||||
}
|
||||
|
||||
public class SubtitleRequest : IHasProviderIds
|
||||
public class SubtitleSearchRequest : IHasProviderIds
|
||||
{
|
||||
public string Language { get; set; }
|
||||
|
||||
|
@ -58,7 +67,7 @@ namespace MediaBrowser.Controller.Providers
|
|||
public int? ProductionYear { get; set; }
|
||||
public Dictionary<string, string> ProviderIds { get; set; }
|
||||
|
||||
public SubtitleRequest()
|
||||
public SubtitleSearchRequest()
|
||||
{
|
||||
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
|
@ -77,6 +77,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
public DateTime DateLastActivity { get; private set; }
|
||||
|
||||
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
|
||||
{
|
||||
Properties = deviceProperties;
|
||||
|
@ -386,6 +388,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
{
|
||||
var transportState = await GetTransportInfo().ConfigureAwait(false);
|
||||
|
||||
DateLastActivity = DateTime.UtcNow;
|
||||
|
||||
if (transportState.HasValue)
|
||||
{
|
||||
// If we're not playing anything no need to get additional data
|
||||
|
|
|
@ -51,6 +51,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
}
|
||||
}
|
||||
|
||||
private Timer _updateTimer;
|
||||
|
||||
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IDtoService dtoService, IImageProcessor imageProcessor, SsdpHandler ssdpHandler, string serverAddress)
|
||||
{
|
||||
_session = session;
|
||||
|
@ -75,6 +77,24 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
_device.Start();
|
||||
|
||||
_ssdpHandler.MessageReceived += _SsdpHandler_MessageReceived;
|
||||
|
||||
_updateTimer = new Timer(updateTimer_Elapsed, null, 60000, 60000);
|
||||
}
|
||||
|
||||
private async void updateTimer_Elapsed(object state)
|
||||
{
|
||||
if (DateTime.UtcNow >= _device.DateLastActivity.AddSeconds(60))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Session is inactive, mark it for Disposal and don't start the elapsed timer.
|
||||
await _sessionManager.ReportSessionEnded(_session.Id).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error in ReportSessionEnded", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetServerAddress()
|
||||
|
@ -571,10 +591,21 @@ namespace MediaBrowser.Dlna.PlayTo
|
|||
_device.PlaybackStopped -= _device_PlaybackStopped;
|
||||
_ssdpHandler.MessageReceived -= _SsdpHandler_MessageReceived;
|
||||
|
||||
DisposeUpdateTimer();
|
||||
|
||||
_device.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeUpdateTimer()
|
||||
{
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
_updateTimer.Dispose();
|
||||
_updateTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
<Compile Include="Subtitles\ISubtitleParser.cs" />
|
||||
<Compile Include="Subtitles\SrtParser.cs" />
|
||||
<Compile Include="Subtitles\SsaParser.cs" />
|
||||
<Compile Include="Subtitles\SubtitleInfo.cs" />
|
||||
<Compile Include="Subtitles\SubtitleTrackInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||
|
|
|
@ -4,6 +4,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
{
|
||||
public interface ISubtitleParser
|
||||
{
|
||||
SubtitleInfo Parse(Stream stream);
|
||||
SubtitleTrackInfo Parse(Stream stream);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
public class SrtParser
|
||||
public class SrtParser : ISubtitleParser
|
||||
{
|
||||
public SubtitleInfo Parse(Stream stream)
|
||||
public SubtitleTrackInfo Parse(Stream stream)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
public class SsaParser
|
||||
public class SsaParser : ISubtitleParser
|
||||
{
|
||||
public SubtitleInfo Parse(Stream stream)
|
||||
public SubtitleTrackInfo Parse(Stream stream)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
public class SubtitleInfo
|
||||
public class SubtitleTrackInfo
|
||||
{
|
||||
public List<SubtitleTrackEvent> TrackEvents { get; set; }
|
||||
|
||||
public SubtitleInfo()
|
||||
public SubtitleTrackInfo()
|
||||
{
|
||||
TrackEvents = new List<SubtitleTrackEvent>();
|
||||
}
|
|
@ -416,6 +416,9 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Providers\RemoteSearchResult.cs">
|
||||
<Link>Providers\RemoteSearchResult.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Providers\RemoteSubtitleInfo.cs">
|
||||
<Link>Providers\RemoteSubtitleInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs">
|
||||
<Link>Querying\ArtistsQuery.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -403,6 +403,9 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Providers\RemoteSearchResult.cs">
|
||||
<Link>Providers\RemoteSearchResult.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Providers\RemoteSubtitleInfo.cs">
|
||||
<Link>Providers\RemoteSubtitleInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs">
|
||||
<Link>Querying\ArtistsQuery.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -760,7 +760,7 @@ namespace MediaBrowser.Model.ApiClient
|
|||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
string GetSubtitleUrl(SubtitleOptions options);
|
||||
string GetSubtitleUrl(SubtitleDownloadOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an image url that can be used to download an image from the api
|
||||
|
|
|
@ -221,6 +221,8 @@ namespace MediaBrowser.Model.Configuration
|
|||
|
||||
public NotificationOptions NotificationOptions { get; set; }
|
||||
|
||||
public SubtitleOptions SubtitleOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
|
||||
/// </summary>
|
||||
|
@ -284,6 +286,8 @@ namespace MediaBrowser.Model.Configuration
|
|||
UICulture = "en-us";
|
||||
|
||||
NotificationOptions = new NotificationOptions();
|
||||
|
||||
SubtitleOptions = new SubtitleOptions();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -311,4 +315,17 @@ namespace MediaBrowser.Model.Configuration
|
|||
public string From { get; set; }
|
||||
public string To { get; set; }
|
||||
}
|
||||
|
||||
public class SubtitleOptions
|
||||
{
|
||||
public bool RequireExternalSubtitles { get; set; }
|
||||
public string[] SubtitleDownloadLanguages { get; set; }
|
||||
public bool DownloadMovieSubtitles { get; set; }
|
||||
public bool DownloadEpisodeSubtitles { get; set; }
|
||||
|
||||
public SubtitleOptions()
|
||||
{
|
||||
SubtitleDownloadLanguages = new string[] { };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@
|
|||
public string DeviceId { get; set; }
|
||||
}
|
||||
|
||||
public class SubtitleOptions
|
||||
public class SubtitleDownloadOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the item identifier.
|
||||
|
|
|
@ -139,6 +139,7 @@
|
|||
<Compile Include="Notifications\NotificationsSummary.cs" />
|
||||
<Compile Include="Providers\RemoteImageResult.cs" />
|
||||
<Compile Include="Providers\RemoteSearchResult.cs" />
|
||||
<Compile Include="Providers\RemoteSubtitleInfo.cs" />
|
||||
<Compile Include="Querying\ArtistsQuery.cs" />
|
||||
<Compile Include="Querying\EpisodeQuery.cs" />
|
||||
<Compile Include="Querying\ItemCountsQuery.cs" />
|
||||
|
|
19
MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
Normal file
19
MediaBrowser.Model/Providers/RemoteSubtitleInfo.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace MediaBrowser.Model.Providers
|
||||
{
|
||||
public class RemoteSubtitleInfo
|
||||
{
|
||||
public string Language { get; set; }
|
||||
public string Id { get; set; }
|
||||
public string ProviderName { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Format { get; set; }
|
||||
public string Author { get; set; }
|
||||
public string Comment { get; set; }
|
||||
public DateTime? DateCreated { get; set; }
|
||||
public float? CommunityRating { get; set; }
|
||||
public int? DownloadCount { get; set; }
|
||||
public bool? IsHashMatch { get; set; }
|
||||
}
|
||||
}
|
|
@ -27,6 +27,10 @@ namespace MediaBrowser.Model.Querying
|
|||
/// </summary>
|
||||
IsFavorite = 5,
|
||||
/// <summary>
|
||||
/// The is recently added
|
||||
/// </summary>
|
||||
IsRecentlyAdded = 6,
|
||||
/// <summary>
|
||||
/// The item is resumable
|
||||
/// </summary>
|
||||
IsResumable = 7,
|
||||
|
|
|
@ -108,6 +108,7 @@
|
|||
<Compile Include="MediaInfo\FFProbeHelpers.cs" />
|
||||
<Compile Include="MediaInfo\FFProbeProvider.cs" />
|
||||
<Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
|
||||
<Compile Include="MediaInfo\SubtitleDownloader.cs" />
|
||||
<Compile Include="Movies\MovieDbTrailerProvider.cs" />
|
||||
<Compile Include="Movies\MovieExternalIds.cs" />
|
||||
<Compile Include="Movies\TrailerMetadataService.cs" />
|
||||
|
@ -187,6 +188,7 @@
|
|||
<Compile Include="Studios\StudiosImageProvider.cs" />
|
||||
<Compile Include="Studios\StudioMetadataService.cs" />
|
||||
<Compile Include="Subtitles\OpenSubtitleDownloader.cs" />
|
||||
<Compile Include="Subtitles\SubtitleManager.cs" />
|
||||
<Compile Include="TV\EpisodeLocalImageProvider.cs" />
|
||||
<Compile Include="TV\EpisodeMetadataService.cs" />
|
||||
<Compile Include="TV\EpisodeXmlProvider.cs" />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
|
@ -10,15 +11,16 @@ using MediaBrowser.Controller.Localization;
|
|||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
|
@ -45,6 +47,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
private readonly IJsonSerializer _json;
|
||||
private readonly IEncodingManager _encodingManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ISubtitleManager _subtitleManager;
|
||||
|
||||
public string Name
|
||||
{
|
||||
|
@ -96,7 +100,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
return FetchAudioInfo(item, cancellationToken);
|
||||
}
|
||||
|
||||
public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem)
|
||||
public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_isoManager = isoManager;
|
||||
|
@ -108,6 +112,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
_json = json;
|
||||
_encodingManager = encodingManager;
|
||||
_fileSystem = fileSystem;
|
||||
_config = config;
|
||||
_subtitleManager = subtitleManager;
|
||||
}
|
||||
|
||||
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
|
||||
|
@ -134,7 +140,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
return _cachedTask;
|
||||
}
|
||||
|
||||
var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem);
|
||||
var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager);
|
||||
|
||||
return prober.ProbeVideo(item, directoryService, cancellationToken);
|
||||
}
|
||||
|
@ -165,7 +171,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
|
||||
if (video != null && !video.IsPlaceHolder)
|
||||
{
|
||||
var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem);
|
||||
var prober = new FFProbeVideoInfo(_logger, _isoManager, _mediaEncoder, _itemRepo, _blurayExaminer, _localization, _appPaths, _json, _encodingManager, _fileSystem, _config, _subtitleManager);
|
||||
|
||||
return !video.SubtitleFiles.SequenceEqual(prober.GetSubtitleFiles(video, directoryService).Select(i => i.FullName).OrderBy(i => i), StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,16 @@
|
|||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Localization;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
|
@ -35,10 +39,12 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
private readonly IJsonSerializer _json;
|
||||
private readonly IEncodingManager _encodingManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ISubtitleManager _subtitleManager;
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public FFProbeVideoInfo(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem)
|
||||
public FFProbeVideoInfo(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization, IApplicationPaths appPaths, IJsonSerializer json, IEncodingManager encodingManager, IFileSystem fileSystem, IServerConfigurationManager config, ISubtitleManager subtitleManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_isoManager = isoManager;
|
||||
|
@ -50,6 +56,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
_json = json;
|
||||
_encodingManager = encodingManager;
|
||||
_fileSystem = fileSystem;
|
||||
_config = config;
|
||||
_subtitleManager = subtitleManager;
|
||||
}
|
||||
|
||||
public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, CancellationToken cancellationToken)
|
||||
|
@ -118,7 +126,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var idString = item.Id.ToString("N");
|
||||
var cachePath = Path.Combine(_appPaths.CachePath,
|
||||
var cachePath = Path.Combine(_appPaths.CachePath,
|
||||
"ffprobe-video",
|
||||
idString.Substring(0, 2), idString, "v" + SchemaVersion + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json");
|
||||
|
||||
|
@ -200,7 +208,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
|
||||
}
|
||||
|
||||
AddExternalSubtitles(video, mediaStreams, directoryService);
|
||||
await AddExternalSubtitles(video, mediaStreams, directoryService, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
FetchWtvInfo(video, data);
|
||||
|
||||
|
@ -247,7 +255,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
}
|
||||
}
|
||||
|
||||
info.StartPositionTicks = chapter.start/100;
|
||||
info.StartPositionTicks = chapter.start / 100;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
@ -450,11 +458,42 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
/// </summary>
|
||||
/// <param name="video">The video.</param>
|
||||
/// <param name="currentStreams">The current streams.</param>
|
||||
private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams, IDirectoryService directoryService)
|
||||
private async Task AddExternalSubtitles(Video video, List<MediaStream> currentStreams, IDirectoryService directoryService, CancellationToken cancellationToken)
|
||||
{
|
||||
var externalSubtitleStreams = GetExternalSubtitleStreams(video, currentStreams.Count, directoryService).ToList();
|
||||
|
||||
if ((_config.Configuration.SubtitleOptions.DownloadEpisodeSubtitles &&
|
||||
video is Episode) ||
|
||||
(_config.Configuration.SubtitleOptions.DownloadMovieSubtitles &&
|
||||
video is Movie))
|
||||
{
|
||||
var downloadedLanguages = await new SubtitleDownloader(_logger,
|
||||
_subtitleManager)
|
||||
.DownloadSubtitles(video,
|
||||
currentStreams,
|
||||
externalSubtitleStreams,
|
||||
_config.Configuration.SubtitleOptions.RequireExternalSubtitles,
|
||||
_config.Configuration.SubtitleOptions.SubtitleDownloadLanguages,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Rescan
|
||||
if (downloadedLanguages.Count > 0)
|
||||
{
|
||||
externalSubtitleStreams = GetExternalSubtitleStreams(video, currentStreams.Count, directoryService).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
video.SubtitleFiles = externalSubtitleStreams.Select(i => i.Path).OrderBy(i => i).ToList();
|
||||
|
||||
currentStreams.AddRange(externalSubtitleStreams);
|
||||
}
|
||||
|
||||
private IEnumerable<MediaStream> GetExternalSubtitleStreams(Video video,
|
||||
int startIndex,
|
||||
IDirectoryService directoryService)
|
||||
{
|
||||
var files = GetSubtitleFiles(video, directoryService);
|
||||
|
||||
var startIndex = currentStreams.Count;
|
||||
var streams = new List<MediaStream>();
|
||||
|
||||
var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
|
||||
|
@ -504,9 +543,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
}
|
||||
}
|
||||
|
||||
video.SubtitleFiles = streams.Select(i => i.Path).OrderBy(i => i).ToList();
|
||||
|
||||
currentStreams.AddRange(streams);
|
||||
return streams;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -627,7 +664,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
{
|
||||
var path = mount == null ? item.Path : mount.MountedPath;
|
||||
var dvd = new Dvd(path);
|
||||
|
||||
|
||||
var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
|
||||
|
||||
byte? titleNumber = null;
|
||||
|
|
140
MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
Normal file
140
MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
Normal file
|
@ -0,0 +1,140 @@
|
|||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
public class SubtitleDownloader
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly ISubtitleManager _subtitleManager;
|
||||
|
||||
public SubtitleDownloader(ILogger logger, ISubtitleManager subtitleManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_subtitleManager = subtitleManager;
|
||||
}
|
||||
|
||||
public async Task<List<string>> DownloadSubtitles(Video video,
|
||||
List<MediaStream> internalSubtitleStreams,
|
||||
List<MediaStream> externalSubtitleStreams,
|
||||
bool forceExternal,
|
||||
IEnumerable<string> languages,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (video.LocationType != LocationType.FileSystem ||
|
||||
video.VideoType != VideoType.VideoFile)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
SubtitleMediaType mediaType;
|
||||
|
||||
if (video is Episode)
|
||||
{
|
||||
mediaType = SubtitleMediaType.Episode;
|
||||
}
|
||||
else if (video is Movie)
|
||||
{
|
||||
mediaType = SubtitleMediaType.Movie;
|
||||
}
|
||||
else
|
||||
{
|
||||
// These are the only supported types
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
var downloadedLanguages = new List<string>();
|
||||
|
||||
foreach (var lang in languages)
|
||||
{
|
||||
try
|
||||
{
|
||||
var downloaded = await DownloadSubtitles(video, internalSubtitleStreams, externalSubtitleStreams, forceExternal, lang, mediaType, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (downloaded)
|
||||
{
|
||||
downloadedLanguages.Add(lang);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error downloading subtitles", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return downloadedLanguages;
|
||||
}
|
||||
|
||||
private async Task<bool> DownloadSubtitles(Video video,
|
||||
IEnumerable<MediaStream> internalSubtitleStreams,
|
||||
IEnumerable<MediaStream> externalSubtitleStreams,
|
||||
bool forceExternal,
|
||||
string language,
|
||||
SubtitleMediaType mediaType,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// There's already subtitles for this language
|
||||
if (externalSubtitleStreams.Any(i => string.Equals(i.Language, language, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// There's an internal subtitle stream for this language
|
||||
if (!forceExternal && internalSubtitleStreams.Any(i => string.Equals(i.Language, language, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var request = new SubtitleSearchRequest
|
||||
{
|
||||
ContentType = mediaType,
|
||||
IndexNumber = video.IndexNumber,
|
||||
Language = language,
|
||||
MediaPath = video.Path,
|
||||
Name = video.Name,
|
||||
ParentIndexNumber = video.ParentIndexNumber,
|
||||
ProductionYear = video.ProductionYear,
|
||||
ProviderIds = video.ProviderIds
|
||||
};
|
||||
|
||||
var episode = video as Episode;
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
request.IndexNumberEnd = episode.IndexNumberEnd;
|
||||
request.SeriesName = episode.SeriesName;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var searchResults = await _subtitleManager.SearchSubtitles(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var result = searchResults.FirstOrDefault();
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
await _subtitleManager.DownloadSubtitles(video, result.Id, result.ProviderName, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error downloading subtitles", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using OpenSubtitlesHandler;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -20,9 +22,9 @@ namespace MediaBrowser.Providers.Subtitles
|
|||
private readonly IHttpClient _httpClient;
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public OpenSubtitleDownloader(ILogger logger, IHttpClient httpClient)
|
||||
public OpenSubtitleDownloader(ILogManager logManager, IHttpClient httpClient)
|
||||
{
|
||||
_logger = logger;
|
||||
_logger = logManager.GetLogger(GetType().Name);
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
|
@ -36,39 +38,71 @@ namespace MediaBrowser.Providers.Subtitles
|
|||
get { return new[] { SubtitleMediaType.Episode, SubtitleMediaType.Movie }; }
|
||||
}
|
||||
|
||||
public Task<SubtitleResponse> GetSubtitles(SubtitleRequest request, CancellationToken cancellationToken)
|
||||
public Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken)
|
||||
{
|
||||
return GetSubtitlesInternal(request, cancellationToken);
|
||||
return GetSubtitlesInternal(id, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task<SubtitleResponse> GetSubtitlesInternal(SubtitleRequest request,
|
||||
private async Task<SubtitleResponse> GetSubtitlesInternal(string id,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var response = new SubtitleResponse();
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
var idParts = id.Split(new[] { '-' }, 3);
|
||||
|
||||
var format = idParts[0];
|
||||
var language = idParts[1];
|
||||
var ossId = idParts[2];
|
||||
|
||||
var downloadsList = new[] { int.Parse(ossId, _usCulture) };
|
||||
|
||||
var resultDownLoad = OpenSubtitles.DownloadSubtitles(downloadsList);
|
||||
if (!(resultDownLoad is MethodResponseSubtitleDownload))
|
||||
{
|
||||
throw new ApplicationException("Invalid response type");
|
||||
}
|
||||
|
||||
var res = ((MethodResponseSubtitleDownload)resultDownLoad).Results.First();
|
||||
var data = Convert.FromBase64String(res.Data);
|
||||
|
||||
return new SubtitleResponse
|
||||
{
|
||||
Format = format,
|
||||
Language = language,
|
||||
|
||||
Stream = new MemoryStream(Utilities.Decompress(new MemoryStream(data)))
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var imdbIdText = request.GetProviderId(MetadataProviders.Imdb);
|
||||
long imdbId;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(imdbIdText) ||
|
||||
long.TryParse(imdbIdText.TrimStart('t'), NumberStyles.Any, _usCulture, out imdbId))
|
||||
!long.TryParse(imdbIdText.TrimStart('t'), NumberStyles.Any, _usCulture, out imdbId))
|
||||
{
|
||||
return response;
|
||||
_logger.Debug("Imdb id missing");
|
||||
return new List<RemoteSubtitleInfo>();
|
||||
}
|
||||
|
||||
|
||||
switch (request.ContentType)
|
||||
{
|
||||
case SubtitleMediaType.Episode:
|
||||
if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName))
|
||||
{
|
||||
_logger.Debug("Information Missing");
|
||||
return response;
|
||||
_logger.Debug("Episode information missing");
|
||||
return new List<RemoteSubtitleInfo>();
|
||||
}
|
||||
break;
|
||||
case SubtitleMediaType.Movie:
|
||||
if (string.IsNullOrEmpty(request.Name))
|
||||
{
|
||||
_logger.Debug("Information Missing");
|
||||
return response;
|
||||
_logger.Debug("Movie name missing");
|
||||
return new List<RemoteSubtitleInfo>();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -76,16 +110,18 @@ namespace MediaBrowser.Providers.Subtitles
|
|||
if (string.IsNullOrEmpty(request.MediaPath))
|
||||
{
|
||||
_logger.Debug("Path Missing");
|
||||
return response;
|
||||
return new List<RemoteSubtitleInfo>();
|
||||
}
|
||||
|
||||
Utilities.HttpClient = _httpClient;
|
||||
OpenSubtitles.SetUserAgent("OS Test User Agent");
|
||||
var loginResponse = OpenSubtitles.LogIn("", "", "en");
|
||||
|
||||
var loginResponse = await OpenSubtitles.LogInAsync("", "", "en", cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!(loginResponse is MethodResponseLogIn))
|
||||
{
|
||||
_logger.Debug("Login error");
|
||||
return response;
|
||||
return new List<RemoteSubtitleInfo>();
|
||||
}
|
||||
|
||||
var subLanguageId = request.Language;
|
||||
|
@ -105,54 +141,42 @@ namespace MediaBrowser.Providers.Subtitles
|
|||
var result = OpenSubtitles.SearchSubtitles(parms.ToArray());
|
||||
if (!(result is MethodResponseSubtitleSearch))
|
||||
{
|
||||
_logger.Debug("invalid response type");
|
||||
return null;
|
||||
_logger.Debug("Invalid response type");
|
||||
return new List<RemoteSubtitleInfo>();
|
||||
}
|
||||
|
||||
Predicate<SubtitleSearchResult> mediaFilter =
|
||||
x =>
|
||||
request.ContentType == SubtitleMediaType.Episode
|
||||
? int.Parse(x.SeriesSeason) == request.ParentIndexNumber && int.Parse(x.SeriesEpisode) == request.IndexNumber
|
||||
: long.Parse(x.IDMovieImdb) == imdbId;
|
||||
? int.Parse(x.SeriesSeason, _usCulture) == request.ParentIndexNumber && int.Parse(x.SeriesEpisode, _usCulture) == request.IndexNumber
|
||||
: long.Parse(x.IDMovieImdb, _usCulture) == imdbId;
|
||||
|
||||
var results = ((MethodResponseSubtitleSearch)result).Results;
|
||||
var bestResult = results.Where(x => x.SubBad == "0" && mediaFilter(x))
|
||||
|
||||
// Avoid implicitly captured closure
|
||||
var hasCopy = hash;
|
||||
|
||||
return results.Where(x => x.SubBad == "0" && mediaFilter(x))
|
||||
.OrderBy(x => x.MovieHash == hash)
|
||||
.ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize) - movieByteSize))
|
||||
.ThenByDescending(x => int.Parse(x.SubDownloadsCnt))
|
||||
.ThenByDescending(x => double.Parse(x.SubRating))
|
||||
.ToList();
|
||||
.ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize, _usCulture) - movieByteSize))
|
||||
.ThenByDescending(x => int.Parse(x.SubDownloadsCnt, _usCulture))
|
||||
.ThenByDescending(x => double.Parse(x.SubRating, _usCulture))
|
||||
.Select(i => new RemoteSubtitleInfo
|
||||
{
|
||||
Author = i.UserNickName,
|
||||
Comment = i.SubAuthorComment,
|
||||
CommunityRating = float.Parse(i.SubRating, _usCulture),
|
||||
DownloadCount = int.Parse(i.SubDownloadsCnt, _usCulture),
|
||||
Format = i.SubFormat,
|
||||
ProviderName = Name,
|
||||
Language = i.SubLanguageID,
|
||||
|
||||
if (!bestResult.Any())
|
||||
{
|
||||
_logger.Debug("No Subtitles");
|
||||
return response;
|
||||
}
|
||||
Id = i.SubFormat + "-" + i.SubLanguageID + "-" + i.IDSubtitle,
|
||||
|
||||
_logger.Debug("Found " + bestResult.Count + " subtitles.");
|
||||
|
||||
var subtitle = bestResult.First();
|
||||
var downloadsList = new[] { int.Parse(subtitle.IDSubtitleFile) };
|
||||
|
||||
var resultDownLoad = OpenSubtitles.DownloadSubtitles(downloadsList);
|
||||
if (!(resultDownLoad is MethodResponseSubtitleDownload))
|
||||
{
|
||||
_logger.Debug("invalid response type");
|
||||
return response;
|
||||
}
|
||||
if (!((MethodResponseSubtitleDownload)resultDownLoad).Results.Any())
|
||||
{
|
||||
_logger.Debug("No Subtitle Downloads");
|
||||
return response;
|
||||
}
|
||||
|
||||
var res = ((MethodResponseSubtitleDownload)resultDownLoad).Results.First();
|
||||
var data = Convert.FromBase64String(res.Data);
|
||||
|
||||
response.HasContent = true;
|
||||
response.Format = subtitle.SubFormat.ToUpper();
|
||||
response.Stream = new MemoryStream(Utilities.Decompress(new MemoryStream(data)));
|
||||
return response;
|
||||
Name = i.SubFileName,
|
||||
DateCreated = DateTime.Parse(i.SubAddDate, _usCulture),
|
||||
IsHashMatch = i.MovieHash == hasCopy
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
141
MediaBrowser.Providers/Subtitles/SubtitleManager.cs
Normal file
141
MediaBrowser.Providers/Subtitles/SubtitleManager.cs
Normal file
|
@ -0,0 +1,141 @@
|
|||
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.Subtitles;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Providers.Subtitles
|
||||
{
|
||||
public class SubtitleManager : ISubtitleManager
|
||||
{
|
||||
private ISubtitleProvider[] _subtitleProviders;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILibraryMonitor _monitor;
|
||||
|
||||
public SubtitleManager(ILogger logger, IFileSystem fileSystem, ILibraryMonitor monitor)
|
||||
{
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_monitor = monitor;
|
||||
}
|
||||
|
||||
public void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders)
|
||||
{
|
||||
_subtitleProviders = subtitleProviders.ToArray();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
var providers = _subtitleProviders
|
||||
.Where(i => i.SupportedMediaTypes.Contains(request.ContentType))
|
||||
.ToList();
|
||||
|
||||
var tasks = providers.Select(async i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return await i.SearchSubtitles(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error downloading subtitles from {0}", ex, i.Name);
|
||||
return new List<RemoteSubtitleInfo>();
|
||||
}
|
||||
});
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
return results.SelectMany(i => i);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
using (var stream = response.Stream)
|
||||
{
|
||||
var savePath = Path.Combine(Path.GetDirectoryName(video.Path),
|
||||
Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLower() + "." + response.Format.ToLower());
|
||||
|
||||
_logger.Info("Saving subtitles to {0}", savePath);
|
||||
|
||||
_monitor.ReportFileSystemChangeBeginning(savePath);
|
||||
|
||||
try
|
||||
{
|
||||
using (var fs = _fileSystem.GetFileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
|
||||
{
|
||||
await stream.CopyToAsync(fs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_monitor.ReportFileSystemChangeComplete(savePath, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(Video video, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
if (video.LocationType != LocationType.FileSystem ||
|
||||
video.VideoType != VideoType.VideoFile)
|
||||
{
|
||||
return Task.FromResult<IEnumerable<RemoteSubtitleInfo>>(new List<RemoteSubtitleInfo>());
|
||||
}
|
||||
|
||||
SubtitleMediaType mediaType;
|
||||
|
||||
if (video is Episode)
|
||||
{
|
||||
mediaType = SubtitleMediaType.Episode;
|
||||
}
|
||||
else if (video is Movie)
|
||||
{
|
||||
mediaType = SubtitleMediaType.Movie;
|
||||
}
|
||||
else
|
||||
{
|
||||
// These are the only supported types
|
||||
return Task.FromResult<IEnumerable<RemoteSubtitleInfo>>(new List<RemoteSubtitleInfo>());
|
||||
}
|
||||
|
||||
var request = new SubtitleSearchRequest
|
||||
{
|
||||
ContentType = mediaType,
|
||||
IndexNumber = video.IndexNumber,
|
||||
Language = language,
|
||||
MediaPath = video.Path,
|
||||
Name = video.Name,
|
||||
ParentIndexNumber = video.ParentIndexNumber,
|
||||
ProductionYear = video.ProductionYear,
|
||||
ProviderIds = video.ProviderIds
|
||||
};
|
||||
|
||||
var episode = video as Episode;
|
||||
|
||||
if (episode != null)
|
||||
{
|
||||
request.IndexNumberEnd = episode.IndexNumberEnd;
|
||||
request.SeriesName = episode.SeriesName;
|
||||
}
|
||||
|
||||
return SearchSubtitles(request, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -327,7 +327,7 @@ namespace MediaBrowser.Server.Implementations.Channels
|
|||
|
||||
var categoryKey = string.IsNullOrWhiteSpace(categoryId) ? "root" : categoryId.GetMD5().ToString("N");
|
||||
|
||||
return Path.Combine(_config.ApplicationPaths.CachePath, channelId, categoryKey, user.Id.ToString("N") + ".json");
|
||||
return Path.Combine(_config.ApplicationPaths.CachePath, "channels", channelId, categoryKey, user.Id.ToString("N") + ".json");
|
||||
}
|
||||
|
||||
private async Task<QueryResult<BaseItemDto>> GetReturnItems(IEnumerable<BaseItem> items, User user, ChannelItemQuery query, CancellationToken cancellationToken)
|
||||
|
|
|
@ -93,7 +93,13 @@ namespace MediaBrowser.Server.Implementations.Collections
|
|||
// Find an actual physical folder
|
||||
if (folder is CollectionFolder)
|
||||
{
|
||||
return _libraryManager.RootFolder.Children.OfType<Folder>().First(i => folder.PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase));
|
||||
var child = _libraryManager.RootFolder.Children.OfType<Folder>()
|
||||
.FirstOrDefault(i => folder.PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase));
|
||||
|
||||
if (child != null)
|
||||
{
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -206,7 +206,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
/// <param name="message">The message.</param>
|
||||
public void Warn(object message)
|
||||
{
|
||||
_logger.Warn(GetMesssage(message));
|
||||
// Hide StringMapTypeDeserializer messages
|
||||
// _logger.Warn(GetMesssage(message));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -216,7 +217,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
/// <param name="args">The args.</param>
|
||||
public void WarnFormat(string format, params object[] args)
|
||||
{
|
||||
_logger.Warn(format, args);
|
||||
// Hide StringMapTypeDeserializer messages
|
||||
// _logger.Warn(format, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Library.Validators
|
||||
{
|
||||
class PeoplePostScanTask : ILibraryPostScanTask
|
||||
{
|
||||
/// <summary>
|
||||
/// The _library manager
|
||||
/// </summary>
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public PeoplePostScanTask(ILibraryManager libraryManager, ILogger logger)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the specified progress.
|
||||
/// </summary>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
return new PeopleValidator(_libraryManager, _logger).ValidatePeople(cancellationToken, new MetadataRefreshOptions
|
||||
{
|
||||
ImageRefreshMode = ImageRefreshMode.ValidationOnly,
|
||||
MetadataRefreshMode = MetadataRefreshMode.None
|
||||
|
||||
}, progress);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ using MediaBrowser.Controller.Localization;
|
|||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MoreLinq;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
@ -106,16 +105,13 @@ namespace MediaBrowser.Server.Implementations.Localization
|
|||
/// <returns>IEnumerable{CultureDto}.</returns>
|
||||
public IEnumerable<CultureDto> GetCultures()
|
||||
{
|
||||
return CultureInfo.GetCultures(CultureTypes.AllCultures)
|
||||
.OrderBy(c => c.DisplayName)
|
||||
.DistinctBy(c => c.TwoLetterISOLanguageName + c.ThreeLetterISOLanguageName)
|
||||
.Select(c => new CultureDto
|
||||
{
|
||||
Name = c.Name,
|
||||
DisplayName = c.DisplayName,
|
||||
ThreeLetterISOLanguageName = c.ThreeLetterISOLanguageName,
|
||||
TwoLetterISOLanguageName = c.TwoLetterISOLanguageName
|
||||
});
|
||||
var type = GetType();
|
||||
var path = type.Namespace + ".cultures.json";
|
||||
|
||||
using (var stream = type.Assembly.GetManifestResourceStream(path))
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromStream<List<CultureDto>>(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -124,28 +120,13 @@ namespace MediaBrowser.Server.Implementations.Localization
|
|||
/// <returns>IEnumerable{CountryInfo}.</returns>
|
||||
public IEnumerable<CountryInfo> GetCountries()
|
||||
{
|
||||
return CultureInfo.GetCultures(CultureTypes.SpecificCultures)
|
||||
.Select(c =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return new RegionInfo(c.LCID);
|
||||
}
|
||||
catch (CultureNotFoundException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.OrderBy(c => c.DisplayName)
|
||||
.DistinctBy(c => c.TwoLetterISORegionName)
|
||||
.Select(c => new CountryInfo
|
||||
{
|
||||
Name = c.Name,
|
||||
DisplayName = c.DisplayName,
|
||||
TwoLetterISORegionName = c.TwoLetterISORegionName,
|
||||
ThreeLetterISORegionName = c.ThreeLetterISORegionName
|
||||
});
|
||||
var type = GetType();
|
||||
var path = type.Namespace + ".countries.json";
|
||||
|
||||
using (var stream = type.Assembly.GetManifestResourceStream(path))
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromStream<List<CountryInfo>>(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -627,5 +627,84 @@
|
|||
"OptionSpecialFeatures": "Special Features",
|
||||
"HeaderCollections": "Collections",
|
||||
"HeaderChannels": "Channels",
|
||||
"HeaderMyLibrary": "My Library"
|
||||
"HeaderMyLibrary": "My Library",
|
||||
"LabelProfileCodecsHelp": "Separated by comma. This can be left empty to apply to all codecs.",
|
||||
"LabelProfileContainersHelp": "Separated by comma. This can be left empty to apply to all containers.",
|
||||
"HeaderResponseProfile": "Response Profile",
|
||||
"LabelType": "Type:",
|
||||
"LabelProfileContainer": "Container:",
|
||||
"LabelProfileVideoCodecs": "Video codecs:",
|
||||
"LabelProfileAudioCodecs": "Audio codecs:",
|
||||
"LabelProfileCodecs": "Codecs:",
|
||||
"HeaderDirectPlayProfile": "Direct Play Profile",
|
||||
"HeaderTranscodingProfile": "Transcoding Profile",
|
||||
"HeaderCodecProfile": "Codec Profile",
|
||||
"HeaderCodecProfileHelp": "Define additional conditions that must be met in order for a codec to be direct played.",
|
||||
"HeaderContainerProfile": "Container Profile",
|
||||
"HeaderContainerProfileHelp": "Define additional conditions that must be met in order for a file to be direct played.",
|
||||
"OptionProfileVideo": "Video",
|
||||
"OptionProfileAudio": "Audio",
|
||||
"OptionProfileVideoAudio": "Video Audio",
|
||||
"OptionProfilePhoto": "Photo",
|
||||
"LabelUserLibrary": "User library:",
|
||||
"LabelUserLibraryHelp": "Select which user library to display to the device. Leave empty to inherit the default setting.",
|
||||
"OptionPlainStorageFolders": "Display all folders as plain storage folders",
|
||||
"OptionPlainStorageFoldersHelp": "If enabled, all folders are represented in DIDL as \"object.container.storageFolder\" instead of a more specific type, such as \"object.container.person.musicArtist\".",
|
||||
"OptionPlainVideoItems": "Display all videos as plain video items",
|
||||
"OptionPlainVideoItemsHelp": "If enabled, all videos are represented in DIDL as \"object.item.videoItem\" instead of a more specific type, such as \"object.item.videoItem.movie\".",
|
||||
"LabelSupportedMediaTypes": "Supported Media Types:",
|
||||
"TabIdentification": "Identification",
|
||||
"TabDirectPlay": "Direct Play",
|
||||
"TabContainers": "Containers",
|
||||
"TabCodecs": "Codecs",
|
||||
"TabResponses": "Responses",
|
||||
"HeaderProfileInformation": "Profile Information",
|
||||
"LabelEmbedAlbumArtDidl": "Embed album art in Didl",
|
||||
"LabelEmbedAlbumArtDidlHelp": "Some devices prefer this method for obtaining album art. Others may fail to play with this option enabled.",
|
||||
"LabelAlbumArtPN": "Album art PN:",
|
||||
"LabelAlbumArtHelp": "PN used for album art, within the dlna:profileID attribute on upnp:albumArtURI. Some clients require a specific value, regardless of the size of the image.",
|
||||
"LabelAlbumArtMaxWidth": "Album art max width:",
|
||||
"LabelAlbumArtMaxWidthHelp": "Max resolution of album art exposed via upnp:albumArtURI.",
|
||||
"LabelAlbumArtMaxHeight": "Album art max height:",
|
||||
"LabelAlbumArtMaxHeightHelp": "Max resolution of album art exposed via upnp:albumArtURI.",
|
||||
"LabelIconMaxWidth": "Icon max width:",
|
||||
"LabelIconMaxWidthHelp": "Max resolution of icons exposed via upnp:icon.",
|
||||
"LabelIconMaxHeight": "Icon max height:",
|
||||
"LabelIconMaxHeightHelp": "Max resolution of icons exposed via upnp:icon.",
|
||||
"LabelIdentificationFieldHelp": "A case-insensitive substring or regex expression.",
|
||||
"HeaderProfileServerSettingsHelp": "These values control how Media Browser will present itself to the device.",
|
||||
"LabelMaxBitrate": "Max bitrate:",
|
||||
"LabelMaxBitrateHelp": "Specify a max bitrate in bandwidth constrained environments, or if the device imposes it's own limit.",
|
||||
"OptionIgnoreTranscodeByteRangeRequests": "Ignore transcode byte range requests",
|
||||
"OptionIgnoreTranscodeByteRangeRequestsHelp": "If enabled, these requests will be honored but will ignore the byte range header.",
|
||||
"LabelFriendlyName": "Friendly name",
|
||||
"LabelManufacturer": "Manufacturer",
|
||||
"LabelManufacturerUrl": "Manufacturer url",
|
||||
"LabelModelName": "Model name",
|
||||
"LabelModelNumber": "Model number",
|
||||
"LabelModelDescription": "Model description",
|
||||
"LabelModelUrl": "Model url",
|
||||
"LabelSerialNumber": "Serial number",
|
||||
"LabelDeviceDescription": "Device description",
|
||||
"HeaderIdentificationCriteriaHelp": "Enter at least one identification criteria.",
|
||||
"HeaderDirectPlayProfileHelp": "Add direct play profiles to indicate which formats the device can handle natively.",
|
||||
"HeaderTranscodingProfileHelp": "Add transcoding profiles to indicate which formats should be used when transcoding is required.",
|
||||
"HeaderContainerProfileHelp": "Container profiles indicate the limitations of a device when playing specific formats. If a limitation applies then the media will be transcoded, even if the format is configured for direct play.",
|
||||
"HeaderCodecProfileHelp": "Codec profiles indicate the limitations of a device when playing specific codecs. If a limitation applies then the media will be transcoded, even if the codec is configured for direct play.",
|
||||
"HeaderResponseProfileHelp": "Response profiles provide a way to customize information sent to the device when playing certain kinds of media.",
|
||||
"LabelXDlnaCap": "X-Dlna cap:",
|
||||
"LabelXDlnaCapHelp": "Determines the content of the X_DLNACAP element in the urn:schemas-dlna-org:device-1-0 namespace.",
|
||||
"LabelXDlnaDoc": "X-Dlna doc:",
|
||||
"LabelXDlnaDocHelp": "Determines the content of the X_DLNADOC element in the urn:schemas-dlna-org:device-1-0 namespace.",
|
||||
"LabelSonyAggregationFlags": "Sony aggregation flags:",
|
||||
"LabelSonyAggregationFlagsHelp": "Determines the content of the aggregationFlags element in the urn:schemas-sonycom:av namespace.",
|
||||
"LabelTranscodingContainer": "Container:",
|
||||
"LabelTranscodingVideoCodec": "Video codec:",
|
||||
"LabelTranscodingVideoProfile": "Video profile:",
|
||||
"LabelTranscodingAudioCodec": "Audio codec:",
|
||||
"OptionEnableM2tsMode": "Enable M2ts mode",
|
||||
"OptionEnableM2tsModeHelp": "Enable m2ts mode when encoding to mpegts.",
|
||||
"OptionEstimateContentLength": "Estimate content length when transcoding",
|
||||
"OptionReportByteRangeSeekingWhenTranscoding": "Report that the server supports byte seeking when transcoding",
|
||||
"OptionReportByteRangeSeekingWhenTranscodingHelp": "This is required for some devices that don't time seek very well."
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -169,7 +169,6 @@
|
|||
<Compile Include="Library\Validators\GenresValidator.cs" />
|
||||
<Compile Include="Library\Validators\MusicGenresPostScanTask.cs" />
|
||||
<Compile Include="Library\Validators\MusicGenresValidator.cs" />
|
||||
<Compile Include="Library\Validators\PeoplePostScanTask.cs" />
|
||||
<Compile Include="Library\Validators\PeopleValidator.cs" />
|
||||
<Compile Include="Library\Validators\StudiosPostScanTask.cs" />
|
||||
<Compile Include="Library\Validators\StudiosValidator.cs" />
|
||||
|
@ -328,6 +327,8 @@
|
|||
<EmbeddedResource Include="Localization\Server\ms.json" />
|
||||
<EmbeddedResource Include="Localization\JavaScript\kk.json" />
|
||||
<EmbeddedResource Include="Localization\Server\kk.json" />
|
||||
<EmbeddedResource Include="Localization\countries.json" />
|
||||
<EmbeddedResource Include="Localization\cultures.json" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -31,11 +31,11 @@ using MediaBrowser.Controller.Providers;
|
|||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Controller.Subtitles;
|
||||
using MediaBrowser.Controller.Themes;
|
||||
using MediaBrowser.Dlna;
|
||||
using MediaBrowser.Dlna.Eventing;
|
||||
using MediaBrowser.Dlna.Main;
|
||||
using MediaBrowser.Dlna.PlayTo;
|
||||
using MediaBrowser.Dlna.Server;
|
||||
using MediaBrowser.MediaEncoding.BdInfo;
|
||||
using MediaBrowser.MediaEncoding.Encoder;
|
||||
|
@ -44,6 +44,7 @@ using MediaBrowser.Model.MediaInfo;
|
|||
using MediaBrowser.Model.System;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using MediaBrowser.Providers.Manager;
|
||||
using MediaBrowser.Providers.Subtitles;
|
||||
using MediaBrowser.Server.Implementations;
|
||||
using MediaBrowser.Server.Implementations.Channels;
|
||||
using MediaBrowser.Server.Implementations.Collections;
|
||||
|
@ -193,6 +194,7 @@ namespace MediaBrowser.ServerApplication
|
|||
private IProviderRepository ProviderRepository { get; set; }
|
||||
|
||||
private INotificationManager NotificationManager { get; set; }
|
||||
private ISubtitleManager SubtitleManager { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ApplicationHost"/> class.
|
||||
|
@ -531,6 +533,9 @@ namespace MediaBrowser.ServerApplication
|
|||
NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
|
||||
RegisterSingleInstance(NotificationManager);
|
||||
|
||||
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor);
|
||||
RegisterSingleInstance(SubtitleManager);
|
||||
|
||||
var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));
|
||||
var itemsTask = Task.Run(async () => await ConfigureItemRepositories().ConfigureAwait(false));
|
||||
var userdataTask = Task.Run(async () => await ConfigureUserDataRepositories().ConfigureAwait(false));
|
||||
|
@ -566,7 +571,7 @@ namespace MediaBrowser.ServerApplication
|
|||
{
|
||||
var info = await new FFMpegDownloader(Logger, ApplicationPaths, HttpClient, ZipClient, FileSystemManager).GetFFMpegInfo(progress).ConfigureAwait(false);
|
||||
|
||||
MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ApplicationPaths, JsonSerializer, info.Path, info.ProbePath, info.Version, FileSystemManager);
|
||||
MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ApplicationPaths, JsonSerializer, info.EncoderPath, info.ProbePath, info.Version, FileSystemManager);
|
||||
RegisterSingleInstance(MediaEncoder);
|
||||
}
|
||||
|
||||
|
@ -710,6 +715,8 @@ namespace MediaBrowser.ServerApplication
|
|||
|
||||
LiveTvManager.AddParts(GetExports<ILiveTvService>());
|
||||
|
||||
SubtitleManager.AddParts(GetExports<ISubtitleProvider>());
|
||||
|
||||
SessionManager.AddParts(GetExports<ISessionControllerFactory>());
|
||||
|
||||
ChannelManager.AddParts(GetExports<IChannel>(), GetExports<IChannelFactory>());
|
||||
|
|
|
@ -4,6 +4,8 @@ using Mono.Unix.Native;
|
|||
using System.Text.RegularExpressions;
|
||||
using System.IO;
|
||||
#endif
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace MediaBrowser.ServerApplication.FFMpeg
|
||||
{
|
||||
|
@ -32,7 +34,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
switch (arg)
|
||||
{
|
||||
case "Version":
|
||||
return "20140304";
|
||||
return "20140506";
|
||||
case "FFMpegFilename":
|
||||
return "ffmpeg.exe";
|
||||
case "FFProbeFilename":
|
||||
|
@ -42,7 +44,6 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
}
|
||||
break;
|
||||
|
||||
#if __MonoCS__
|
||||
case PlatformID.Unix:
|
||||
if (PlatformDetection.IsMac)
|
||||
{
|
||||
|
@ -69,7 +70,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
switch (arg)
|
||||
{
|
||||
case "Version":
|
||||
return "20140304";
|
||||
return "20140506";
|
||||
case "FFMpegFilename":
|
||||
return "ffmpeg";
|
||||
case "FFProbeFilename":
|
||||
|
@ -85,7 +86,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
switch (arg)
|
||||
{
|
||||
case "Version":
|
||||
return "20140304";
|
||||
return "20140505";
|
||||
case "FFMpegFilename":
|
||||
return "ffmpeg";
|
||||
case "FFProbeFilename":
|
||||
|
@ -98,7 +99,6 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
}
|
||||
// Unsupported Unix platform
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
@ -106,18 +106,17 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
private static string[] GetDownloadUrls()
|
||||
{
|
||||
var pid = Environment.OSVersion.Platform;
|
||||
|
||||
|
||||
switch (pid)
|
||||
{
|
||||
case PlatformID.Win32NT:
|
||||
return new[]
|
||||
{
|
||||
"http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20140304-git-f34cceb-win32-static.7z",
|
||||
"https://www.dropbox.com/s/6brdetuzbld93jk/ffmpeg-20140304-git-f34cceb-win32-static.7z?dl=1"
|
||||
"http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20140506-git-2baf1c8-win32-static.7z",
|
||||
"https://www.dropbox.com/s/lxlzxs0r83iatsv/ffmpeg-20140506-git-2baf1c8-win32-static.7z?dl=1"
|
||||
};
|
||||
|
||||
#if __MonoCS__
|
||||
case PlatformID.Unix:
|
||||
|
||||
case PlatformID.Unix:
|
||||
if (PlatformDetection.IsMac && PlatformDetection.IsX86_64)
|
||||
{
|
||||
return new[]
|
||||
|
@ -132,8 +131,8 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
{
|
||||
return new[]
|
||||
{
|
||||
"http://ffmpeg.gusari.org/static/32bit/ffmpeg.static.32bit.2014-03-04.tar.gz",
|
||||
"https://www.dropbox.com/s/0l76mcauqqkta31/ffmpeg.static.32bit.2014-03-04.tar.gz?dl=1"
|
||||
"http://ffmpeg.gusari.org/static/32bit/ffmpeg.static.32bit.latest.tar.gz",
|
||||
"https://www.dropbox.com/s/k9s02pv5to6slfb/ffmpeg.static.32bit.2014-05-06.tar.gz?dl=1"
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -141,22 +140,20 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
{
|
||||
return new[]
|
||||
{
|
||||
"http://ffmpeg.gusari.org/static/64bit/ffmpeg.static.64bit.2014-03-04.tar.gz",
|
||||
"https://www.dropbox.com/s/9wlxz440mdejuqe/ffmpeg.static.64bit.2014-03-04.tar.gz?dl=1"
|
||||
"http://ffmpeg.gusari.org/static/64bit/ffmpeg.static.64bit.latest.tar.gz",
|
||||
"https://www.dropbox.com/s/onuregwghywnzjo/ffmpeg.static.64bit.2014-05-05.tar.gz?dl=1"
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//No Unix version available
|
||||
return new string[] {};
|
||||
#endif
|
||||
return new string[] { };
|
||||
}
|
||||
return new string[] {};
|
||||
return new string[] { };
|
||||
}
|
||||
}
|
||||
|
||||
#if __MonoCS__
|
||||
public static class PlatformDetection
|
||||
{
|
||||
public readonly static bool IsWindows;
|
||||
|
@ -166,34 +163,52 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
public readonly static bool IsX86_64;
|
||||
public readonly static bool IsArm;
|
||||
|
||||
static PlatformDetection ()
|
||||
static PlatformDetection()
|
||||
{
|
||||
IsWindows = Path.DirectorySeparatorChar == '\\';
|
||||
|
||||
//Don't call uname on windows
|
||||
if (!IsWindows)
|
||||
{
|
||||
Utsname uname;
|
||||
var callResult = Syscall.uname(out uname);
|
||||
if (callResult == 0)
|
||||
{
|
||||
IsMac = uname.sysname == "Darwin";
|
||||
IsLinux = !IsMac && uname.sysname == "Linux";
|
||||
var uname = GetUnixName();
|
||||
|
||||
Regex archX86 = new Regex("(i|I)[3-6]86");
|
||||
IsX86 = archX86.IsMatch(uname.machine);
|
||||
IsX86_64 = !IsX86 && uname.machine == "x86_64";
|
||||
IsArm = !IsX86 && !IsX86 && uname.machine.StartsWith("arm");
|
||||
}
|
||||
IsMac = uname.sysname == "Darwin";
|
||||
IsLinux = uname.sysname == "Linux";
|
||||
|
||||
var archX86 = new Regex("(i|I)[3-6]86");
|
||||
IsX86 = archX86.IsMatch(uname.machine);
|
||||
IsX86_64 = !IsX86 && uname.machine == "x86_64";
|
||||
IsArm = !IsX86 && !IsX86_64 && uname.machine.StartsWith("arm");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (System.Environment.Is64BitOperatingSystem)
|
||||
if (Environment.Is64BitOperatingSystem)
|
||||
IsX86_64 = true;
|
||||
else
|
||||
IsX86 = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static Uname GetUnixName()
|
||||
{
|
||||
var uname = new Uname();
|
||||
|
||||
#if __MonoCS__
|
||||
Utsname uname;
|
||||
var callResult = Syscall.uname(out uname);
|
||||
if (callResult == 0)
|
||||
{
|
||||
uname.sysname= uname.sysname;
|
||||
uname.machine= uname.machine;
|
||||
}
|
||||
#endif
|
||||
return uname;
|
||||
}
|
||||
}
|
||||
|
||||
public class Uname
|
||||
{
|
||||
public string sysname = string.Empty;
|
||||
public string machine = string.Empty;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -42,63 +42,86 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
|
||||
public async Task<FFMpegInfo> GetFFMpegInfo(IProgress<double> progress)
|
||||
{
|
||||
var versionedDirectoryPath = Path.Combine(GetMediaToolsPath(true), FFMpegDownloadInfo.Version);
|
||||
var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
|
||||
var versionedDirectoryPath = Path.Combine(rootEncoderPath, FFMpegDownloadInfo.Version);
|
||||
|
||||
var info = new FFMpegInfo
|
||||
{
|
||||
ProbePath = Path.Combine(versionedDirectoryPath, FFMpegDownloadInfo.FFProbeFilename),
|
||||
Path = Path.Combine(versionedDirectoryPath, FFMpegDownloadInfo.FFMpegFilename),
|
||||
EncoderPath = Path.Combine(versionedDirectoryPath, FFMpegDownloadInfo.FFMpegFilename),
|
||||
Version = FFMpegDownloadInfo.Version
|
||||
};
|
||||
|
||||
Directory.CreateDirectory(versionedDirectoryPath);
|
||||
|
||||
var tasks = new List<Task>();
|
||||
|
||||
double ffmpegPercent = 0;
|
||||
double fontPercent = 0;
|
||||
var syncLock = new object();
|
||||
|
||||
if (!File.Exists(info.ProbePath) || !File.Exists(info.Path))
|
||||
if (!File.Exists(info.ProbePath) || !File.Exists(info.EncoderPath))
|
||||
{
|
||||
var ffmpegProgress = new ActionableProgress<double>();
|
||||
ffmpegProgress.RegisterAction(p =>
|
||||
// ffmpeg not present. See if there's an older version we can start with
|
||||
var existingVersion = GetExistingVersion(info, rootEncoderPath);
|
||||
|
||||
// No older version. Need to download and block until complete
|
||||
if (existingVersion == null)
|
||||
{
|
||||
ffmpegPercent = p;
|
||||
|
||||
lock (syncLock)
|
||||
{
|
||||
progress.Report((ffmpegPercent / 2) + (fontPercent / 2));
|
||||
}
|
||||
});
|
||||
|
||||
tasks.Add(DownloadFFMpeg(info, ffmpegProgress));
|
||||
}
|
||||
else
|
||||
{
|
||||
ffmpegPercent = 100;
|
||||
progress.Report(50);
|
||||
}
|
||||
|
||||
var fontProgress = new ActionableProgress<double>();
|
||||
fontProgress.RegisterAction(p =>
|
||||
{
|
||||
fontPercent = p;
|
||||
|
||||
lock (syncLock)
|
||||
{
|
||||
progress.Report((ffmpegPercent / 2) + (fontPercent / 2));
|
||||
await DownloadFFMpeg(versionedDirectoryPath, progress).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
else
|
||||
{
|
||||
// Older version found.
|
||||
// Start with that. Download new version in the background.
|
||||
var newPath = versionedDirectoryPath;
|
||||
Task.Run(() => DownloadFFMpegInBackground(newPath));
|
||||
|
||||
tasks.Add(DownloadFonts(versionedDirectoryPath, fontProgress));
|
||||
info = existingVersion;
|
||||
versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await DownloadFonts(versionedDirectoryPath).ConfigureAwait(false);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
private async Task DownloadFFMpeg(FFMpegInfo info, IProgress<double> progress)
|
||||
private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath)
|
||||
{
|
||||
var encoderFilename = Path.GetFileName(info.EncoderPath);
|
||||
var probeFilename = Path.GetFileName(info.ProbePath);
|
||||
|
||||
foreach (var directory in Directory.EnumerateDirectories(rootEncoderPath, "*", SearchOption.TopDirectoryOnly)
|
||||
.ToList())
|
||||
{
|
||||
var allFiles = Directory.EnumerateFiles(directory, "*", SearchOption.AllDirectories).ToList();
|
||||
|
||||
var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase));
|
||||
var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(encoder) &&
|
||||
!string.IsNullOrWhiteSpace(probe))
|
||||
{
|
||||
return new FFMpegInfo
|
||||
{
|
||||
EncoderPath = encoder,
|
||||
ProbePath = probe,
|
||||
Version = Path.GetFileNameWithoutExtension(Path.GetDirectoryName(probe))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async void DownloadFFMpegInBackground(string directory)
|
||||
{
|
||||
try
|
||||
{
|
||||
await DownloadFFMpeg(directory, new Progress<double>()).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error downloading ffmpeg", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DownloadFFMpeg(string directory, IProgress<double> progress)
|
||||
{
|
||||
foreach (var url in FFMpegDownloadInfo.FfMpegUrls)
|
||||
{
|
||||
|
@ -114,7 +137,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
ExtractFFMpeg(tempFile, Path.GetDirectoryName(info.Path));
|
||||
ExtractFFMpeg(tempFile, directory);
|
||||
return;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
|
@ -132,7 +155,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
|
||||
private void ExtractFFMpeg(string tempFile, string targetFolder)
|
||||
{
|
||||
_logger.Debug("Extracting ffmpeg from {0}", tempFile);
|
||||
_logger.Info("Extracting ffmpeg from {0}", tempFile);
|
||||
|
||||
var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString());
|
||||
|
||||
|
@ -171,6 +194,8 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
|
||||
private void ExtractArchive(string archivePath, string targetPath)
|
||||
{
|
||||
_logger.Info("Extracting {0} to {1}", archivePath, targetPath);
|
||||
|
||||
if (string.Equals(FFMpegDownloadInfo.ArchiveType, "7z", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
|
||||
|
@ -182,6 +207,8 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
}
|
||||
private void Extract7zArchive(string archivePath, string targetPath)
|
||||
{
|
||||
_logger.Info("Extracting {0} to {1}", archivePath, targetPath);
|
||||
|
||||
_zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
|
||||
}
|
||||
|
||||
|
@ -201,7 +228,8 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
/// Extracts the fonts.
|
||||
/// </summary>
|
||||
/// <param name="targetPath">The target path.</param>
|
||||
private async Task DownloadFonts(string targetPath, IProgress<double> progress)
|
||||
/// <returns>Task.</returns>
|
||||
private async Task DownloadFonts(string targetPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -213,12 +241,19 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
|
||||
var fontFile = Path.Combine(fontsDirectory, fontFilename);
|
||||
|
||||
if (!File.Exists(fontFile))
|
||||
if (File.Exists(fontFile))
|
||||
{
|
||||
await DownloadFontFile(fontsDirectory, fontFilename, progress).ConfigureAwait(false);
|
||||
await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Kick this off, but no need to wait on it
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await DownloadFontFile(fontsDirectory, fontFilename, new Progress<double>()).ConfigureAwait(false);
|
||||
await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
|
@ -230,8 +265,6 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
// Don't let the server crash because of this
|
||||
_logger.ErrorException("Error writing ffmpeg font files", ex);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -325,19 +358,5 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the media tools path.
|
||||
/// </summary>
|
||||
/// <param name="create">if set to <c>true</c> [create].</param>
|
||||
/// <returns>System.String.</returns>
|
||||
private string GetMediaToolsPath(bool create)
|
||||
{
|
||||
var path = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
|
||||
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
/// Gets or sets the path.
|
||||
/// </summary>
|
||||
/// <value>The path.</value>
|
||||
public string Path { get; set; }
|
||||
public string EncoderPath { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the probe path.
|
||||
/// </summary>
|
||||
|
|
|
@ -217,6 +217,9 @@
|
|||
<Content Include="dashboard-ui\css\images\items\folders\channels.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\css\images\items\folders\folder.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\css\images\items\folders\games.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
|
|
@ -20,6 +20,8 @@ using System;
|
|||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenSubtitlesHandler.Console;
|
||||
using XmlRpcHandler;
|
||||
|
||||
|
@ -96,6 +98,56 @@ namespace OpenSubtitlesHandler
|
|||
}
|
||||
return new MethodResponseError("Fail", "Log in failed !");
|
||||
}
|
||||
|
||||
public static async Task<IMethodResponse> LogInAsync(string userName, string password, string language, CancellationToken cancellationToken)
|
||||
{
|
||||
// Method call ..
|
||||
List<IXmlRpcValue> parms = new List<IXmlRpcValue>();
|
||||
parms.Add(new XmlRpcValueBasic(userName));
|
||||
parms.Add(new XmlRpcValueBasic(password));
|
||||
parms.Add(new XmlRpcValueBasic(language));
|
||||
parms.Add(new XmlRpcValueBasic(XML_PRC_USERAGENT));
|
||||
XmlRpcMethodCall call = new XmlRpcMethodCall("LogIn", parms);
|
||||
OSHConsole.WriteLine("Sending LogIn request to the server ...", DebugCode.Good);
|
||||
|
||||
//File.WriteAllText(".\\request.txt", Encoding.UTF8.GetString(XmlRpcGenerator.Generate(call)));
|
||||
// Send the request to the server
|
||||
var stream = await Utilities.SendRequestAsync(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
string response = Utilities.GetStreamString(stream);
|
||||
|
||||
if (!response.Contains("ERROR:"))
|
||||
{
|
||||
// No error occur, get and decode the response. We expect Struct here.
|
||||
XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response);
|
||||
if (calls.Length > 0)
|
||||
{
|
||||
if (calls[0].Parameters.Count > 0)
|
||||
{
|
||||
XmlRpcValueStruct mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0];
|
||||
MethodResponseLogIn re = new MethodResponseLogIn("Success", "Log in successful.");
|
||||
foreach (XmlRpcStructMember MEMBER in mainStruct.Members)
|
||||
{
|
||||
switch (MEMBER.Name)
|
||||
{
|
||||
case "token": re.Token = TOKEN = MEMBER.Data.Data.ToString(); OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break;
|
||||
case "seconds": re.Seconds = (double)MEMBER.Data.Data; OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break;
|
||||
case "status": re.Status = MEMBER.Data.Data.ToString(); OSHConsole.WriteLine(MEMBER.Name + "= " + MEMBER.Data.Data.ToString()); break;
|
||||
}
|
||||
}
|
||||
return re;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
OSHConsole.WriteLine(response, DebugCode.Error);
|
||||
return new MethodResponseError("Fail", response);
|
||||
}
|
||||
return new MethodResponseError("Fail", "Log in failed !");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log out from the server. Call this to terminate the session.
|
||||
/// </summary>
|
||||
|
|
|
@ -24,6 +24,7 @@ using System.IO;
|
|||
using System.IO.Compression;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
||||
|
@ -161,7 +162,7 @@ namespace OpenSubtitlesHandler
|
|||
/// <returns>Response of the server or stream of error message as string started with 'ERROR:' keyword.</returns>
|
||||
public static Stream SendRequest(byte[] request, string userAgent)
|
||||
{
|
||||
return SendRequestAsync(request, userAgent).Result;
|
||||
return SendRequestAsync(request, userAgent, CancellationToken.None).Result;
|
||||
|
||||
//HttpWebRequest req = (HttpWebRequest)WebRequest.Create(XML_RPC_SERVER);
|
||||
//req.ContentType = "text/xml";
|
||||
|
@ -190,16 +191,27 @@ namespace OpenSubtitlesHandler
|
|||
//}
|
||||
}
|
||||
|
||||
public static async Task<Stream> SendRequestAsync(byte[] request, string userAgent)
|
||||
public static async Task<Stream> SendRequestAsync(byte[] request, string userAgent, CancellationToken cancellationToken)
|
||||
{
|
||||
var options = new HttpRequestOptions
|
||||
{
|
||||
RequestContentBytes = request,
|
||||
RequestContentType = "text/xml",
|
||||
UserAgent = "xmlrpc-epi-php/0.2 (PHP)",
|
||||
Url = XML_RPC_SERVER
|
||||
UserAgent = userAgent,
|
||||
Host = "api.opensubtitles.org:80",
|
||||
Url = XML_RPC_SERVER,
|
||||
|
||||
// Response parsing will fail with this enabled
|
||||
EnableHttpCompression = false,
|
||||
|
||||
CancellationToken = cancellationToken
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(options.UserAgent))
|
||||
{
|
||||
options.UserAgent = "xmlrpc-epi-php/0.2 (PHP)";
|
||||
}
|
||||
|
||||
var result = await HttpClient.Post(options).ConfigureAwait(false);
|
||||
|
||||
return result.Content;
|
||||
|
|
Loading…
Reference in New Issue
Block a user