mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-07-05 21:33:02 +02:00
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")]
|
[ApiMember(Name = "SortOrder", Description = "Sort Order - Ascending,Descending", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
public SortOrder? SortOrder { get; set; }
|
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; }
|
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)]
|
[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.Extensions;
|
||||||
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
@ -70,6 +72,32 @@ namespace MediaBrowser.Api.Images
|
||||||
public string Theme { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Class ImageByNameService
|
/// Class ImageByNameService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -89,6 +117,60 @@ namespace MediaBrowser.Api.Images
|
||||||
_appPaths = appPaths;
|
_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>
|
/// <summary>
|
||||||
/// Gets the specified request.
|
/// Gets the specified request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -118,7 +200,8 @@ namespace MediaBrowser.Api.Images
|
||||||
|
|
||||||
if (Directory.Exists(themeFolder))
|
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);
|
.FirstOrDefault(File.Exists);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path))
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
@ -134,7 +217,8 @@ namespace MediaBrowser.Api.Images
|
||||||
// Avoid implicitly captured closure
|
// Avoid implicitly captured closure
|
||||||
var currentRequest = request;
|
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);
|
.FirstOrDefault(File.Exists);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path))
|
if (!string.IsNullOrEmpty(path))
|
||||||
|
@ -175,7 +259,7 @@ namespace MediaBrowser.Api.Images
|
||||||
|
|
||||||
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);
|
.FirstOrDefault(File.Exists);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(path))
|
if (!string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
return ToStaticFileResult(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.Common.IO;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -14,6 +13,7 @@ using ServiceStack.Text.Controller;
|
||||||
using ServiceStack.Web;
|
using ServiceStack.Web;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Dto;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Controller.Subtitles;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
|
@ -32,6 +32,16 @@ namespace MediaBrowser.Api
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("/Items/{Id}/RemoteSearch/Subtitles/{Language}", "GET")]
|
||||||
|
public class SearchRemoteSubtitles : IReturn<List<RemoteSubtitleInfo>>
|
||||||
|
{
|
||||||
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "Language", Description = "Language", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string Language { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[Route("/Items/RemoteSearch/Movie", "POST")]
|
[Route("/Items/RemoteSearch/Movie", "POST")]
|
||||||
[Api(Description = "Gets external id infos for an item")]
|
[Api(Description = "Gets external id infos for an item")]
|
||||||
public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
|
public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
|
||||||
|
@ -107,19 +117,28 @@ namespace MediaBrowser.Api
|
||||||
|
|
||||||
public class ItemLookupService : BaseApiService
|
public class ItemLookupService : BaseApiService
|
||||||
{
|
{
|
||||||
private readonly IDtoService _dtoService;
|
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly ISubtitleManager _subtitleManager;
|
||||||
|
|
||||||
public ItemLookupService(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;
|
_providerManager = providerManager;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
_subtitleManager = subtitleManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(SearchRemoteSubtitles request)
|
||||||
|
{
|
||||||
|
var video = (Video)_libraryManager.GetItemById(request.Id);
|
||||||
|
|
||||||
|
var response = _subtitleManager.SearchSubtitles(video, request.Language, CancellationToken.None).Result;
|
||||||
|
|
||||||
|
return ToOptimizedResult(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetExternalIdInfos request)
|
public object Get(GetExternalIdInfos request)
|
||||||
|
|
|
@ -69,7 +69,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
/// Filters to apply to the results
|
/// Filters to apply to the results
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The filters.</value>
|
/// <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; }
|
public string Filters { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -521,6 +521,9 @@ namespace MediaBrowser.Api.UserLibrary
|
||||||
|
|
||||||
case ItemFilter.IsNotFolder:
|
case ItemFilter.IsNotFolder:
|
||||||
return items.Where(item => !item.IsFolder);
|
return items.Where(item => !item.IsFolder);
|
||||||
|
|
||||||
|
case ItemFilter.IsRecentlyAdded:
|
||||||
|
return items.Where(item => (DateTime.UtcNow - item.DateCreated).TotalDays <= 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
|
|
|
@ -114,9 +114,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
|
|
||||||
request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
|
request.AutomaticDecompression = enableHttpCompression ? DecompressionMethods.Deflate : DecompressionMethods.None;
|
||||||
|
|
||||||
request.CachePolicy = options.CachePolicy == Net.HttpRequestCachePolicy.None ?
|
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
|
||||||
new RequestCachePolicy(RequestCacheLevel.BypassCache) :
|
|
||||||
new RequestCachePolicy(RequestCacheLevel.Revalidate);
|
|
||||||
|
|
||||||
request.ConnectionGroupName = GetHostFromUrl(options.Url);
|
request.ConnectionGroupName = GetHostFromUrl(options.Url);
|
||||||
request.KeepAlive = true;
|
request.KeepAlive = true;
|
||||||
|
@ -124,6 +122,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
request.Pipelined = true;
|
request.Pipelined = true;
|
||||||
request.Timeout = 20000;
|
request.Timeout = 20000;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(options.Host))
|
||||||
|
{
|
||||||
|
request.Host = options.Host;
|
||||||
|
}
|
||||||
|
|
||||||
#if !__MonoCS__
|
#if !__MonoCS__
|
||||||
// This is a hack to prevent KeepAlive from getting disabled internally by the HttpWebRequest
|
// This is a hack to prevent KeepAlive from getting disabled internally by the HttpWebRequest
|
||||||
// May need to remove this for mono
|
// May need to remove this for mono
|
||||||
|
@ -234,9 +237,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
|
||||||
!string.IsNullOrEmpty(options.RequestContent) ||
|
!string.IsNullOrEmpty(options.RequestContent) ||
|
||||||
string.Equals(httpMethod, "post", StringComparison.OrdinalIgnoreCase))
|
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.ContentType = options.RequestContentType ?? "application/x-www-form-urlencoded";
|
||||||
|
|
||||||
httpWebRequest.ContentLength = bytes.Length;
|
httpWebRequest.ContentLength = bytes.Length;
|
||||||
httpWebRequest.GetRequestStream().Write(bytes, 0, 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 ILogger _logger;
|
||||||
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
|
/// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -74,9 +74,11 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
|
||||||
|
|
||||||
progress.Report(90);
|
progress.Report(90);
|
||||||
|
|
||||||
|
minDateModified = DateTime.UtcNow.AddDays(-3);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, DateTime.MaxValue, progress);
|
DeleteCacheFilesFromDirectory(cancellationToken, ApplicationPaths.TempDirectory, minDateModified, progress);
|
||||||
}
|
}
|
||||||
catch (DirectoryNotFoundException)
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the progress.
|
/// Gets or sets the progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -76,8 +82,6 @@ namespace MediaBrowser.Common.Net
|
||||||
public bool LogRequest { get; set; }
|
public bool LogRequest { get; set; }
|
||||||
|
|
||||||
public bool LogErrorResponseBody { get; set; }
|
public bool LogErrorResponseBody { get; set; }
|
||||||
|
|
||||||
public HttpRequestCachePolicy CachePolicy { get; set; }
|
|
||||||
|
|
||||||
private string GetHeaderValue(string name)
|
private string GetHeaderValue(string name)
|
||||||
{
|
{
|
||||||
|
@ -96,17 +100,9 @@ namespace MediaBrowser.Common.Net
|
||||||
EnableHttpCompression = true;
|
EnableHttpCompression = true;
|
||||||
BufferContent = true;
|
BufferContent = true;
|
||||||
|
|
||||||
CachePolicy = HttpRequestCachePolicy.None;
|
|
||||||
|
|
||||||
RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
RequestHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
LogRequest = true;
|
LogRequest = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum HttpRequestCachePolicy
|
|
||||||
{
|
|
||||||
None = 1,
|
|
||||||
Validate = 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -191,7 +191,8 @@
|
||||||
<Compile Include="Providers\IMetadataProvider.cs" />
|
<Compile Include="Providers\IMetadataProvider.cs" />
|
||||||
<Compile Include="Providers\IMetadataService.cs" />
|
<Compile Include="Providers\IMetadataService.cs" />
|
||||||
<Compile Include="Providers\IRemoteMetadataProvider.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\ItemLookupInfo.cs" />
|
||||||
<Compile Include="Providers\MetadataRefreshOptions.cs" />
|
<Compile Include="Providers\MetadataRefreshOptions.cs" />
|
||||||
<Compile Include="Providers\NameParser.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.Entities;
|
||||||
|
using MediaBrowser.Model.Providers;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Providers
|
namespace MediaBrowser.Controller.Subtitles
|
||||||
{
|
{
|
||||||
public interface ISubtitleProvider
|
public interface ISubtitleProvider
|
||||||
{
|
{
|
||||||
|
@ -22,12 +23,20 @@ namespace MediaBrowser.Controller.Providers
|
||||||
IEnumerable<SubtitleMediaType> SupportedMediaTypes { get; }
|
IEnumerable<SubtitleMediaType> SupportedMediaTypes { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the subtitles.
|
/// Searches the subtitles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</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>
|
/// <returns>Task{SubtitleResponse}.</returns>
|
||||||
Task<SubtitleResponse> GetSubtitles(SubtitleRequest request, CancellationToken cancellationToken);
|
Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SubtitleMediaType
|
public enum SubtitleMediaType
|
||||||
|
@ -38,12 +47,12 @@ namespace MediaBrowser.Controller.Providers
|
||||||
|
|
||||||
public class SubtitleResponse
|
public class SubtitleResponse
|
||||||
{
|
{
|
||||||
|
public string Language { get; set; }
|
||||||
public string Format { get; set; }
|
public string Format { get; set; }
|
||||||
public bool HasContent { get; set; }
|
|
||||||
public Stream Stream { get; set; }
|
public Stream Stream { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SubtitleRequest : IHasProviderIds
|
public class SubtitleSearchRequest : IHasProviderIds
|
||||||
{
|
{
|
||||||
public string Language { get; set; }
|
public string Language { get; set; }
|
||||||
|
|
||||||
|
@ -58,7 +67,7 @@ namespace MediaBrowser.Controller.Providers
|
||||||
public int? ProductionYear { get; set; }
|
public int? ProductionYear { get; set; }
|
||||||
public Dictionary<string, string> ProviderIds { get; set; }
|
public Dictionary<string, string> ProviderIds { get; set; }
|
||||||
|
|
||||||
public SubtitleRequest()
|
public SubtitleSearchRequest()
|
||||||
{
|
{
|
||||||
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
|
@ -77,6 +77,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
|
public DateTime DateLastActivity { get; private set; }
|
||||||
|
|
||||||
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
|
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
|
||||||
{
|
{
|
||||||
Properties = deviceProperties;
|
Properties = deviceProperties;
|
||||||
|
@ -386,6 +388,8 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
var transportState = await GetTransportInfo().ConfigureAwait(false);
|
var transportState = await GetTransportInfo().ConfigureAwait(false);
|
||||||
|
|
||||||
|
DateLastActivity = DateTime.UtcNow;
|
||||||
|
|
||||||
if (transportState.HasValue)
|
if (transportState.HasValue)
|
||||||
{
|
{
|
||||||
// If we're not playing anything no need to get additional data
|
// 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)
|
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;
|
_session = session;
|
||||||
|
@ -75,6 +77,24 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
_device.Start();
|
_device.Start();
|
||||||
|
|
||||||
_ssdpHandler.MessageReceived += _SsdpHandler_MessageReceived;
|
_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()
|
private string GetServerAddress()
|
||||||
|
@ -571,10 +591,21 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||||
_device.PlaybackStopped -= _device_PlaybackStopped;
|
_device.PlaybackStopped -= _device_PlaybackStopped;
|
||||||
_ssdpHandler.MessageReceived -= _SsdpHandler_MessageReceived;
|
_ssdpHandler.MessageReceived -= _SsdpHandler_MessageReceived;
|
||||||
|
|
||||||
|
DisposeUpdateTimer();
|
||||||
|
|
||||||
_device.Dispose();
|
_device.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DisposeUpdateTimer()
|
||||||
|
{
|
||||||
|
if (_updateTimer != null)
|
||||||
|
{
|
||||||
|
_updateTimer.Dispose();
|
||||||
|
_updateTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
public Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
<Compile Include="Subtitles\ISubtitleParser.cs" />
|
<Compile Include="Subtitles\ISubtitleParser.cs" />
|
||||||
<Compile Include="Subtitles\SrtParser.cs" />
|
<Compile Include="Subtitles\SrtParser.cs" />
|
||||||
<Compile Include="Subtitles\SsaParser.cs" />
|
<Compile Include="Subtitles\SsaParser.cs" />
|
||||||
<Compile Include="Subtitles\SubtitleInfo.cs" />
|
<Compile Include="Subtitles\SubtitleTrackInfo.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||||
|
|
|
@ -4,6 +4,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
public interface ISubtitleParser
|
public interface ISubtitleParser
|
||||||
{
|
{
|
||||||
SubtitleInfo Parse(Stream stream);
|
SubtitleTrackInfo Parse(Stream stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Subtitles
|
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
public class SrtParser
|
public class SrtParser : ISubtitleParser
|
||||||
{
|
{
|
||||||
public SubtitleInfo Parse(Stream stream)
|
public SubtitleTrackInfo Parse(Stream stream)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Subtitles
|
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
public class SsaParser
|
public class SsaParser : ISubtitleParser
|
||||||
{
|
{
|
||||||
public SubtitleInfo Parse(Stream stream)
|
public SubtitleTrackInfo Parse(Stream stream)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
namespace MediaBrowser.MediaEncoding.Subtitles
|
namespace MediaBrowser.MediaEncoding.Subtitles
|
||||||
{
|
{
|
||||||
public class SubtitleInfo
|
public class SubtitleTrackInfo
|
||||||
{
|
{
|
||||||
public List<SubtitleTrackEvent> TrackEvents { get; set; }
|
public List<SubtitleTrackEvent> TrackEvents { get; set; }
|
||||||
|
|
||||||
public SubtitleInfo()
|
public SubtitleTrackInfo()
|
||||||
{
|
{
|
||||||
TrackEvents = new List<SubtitleTrackEvent>();
|
TrackEvents = new List<SubtitleTrackEvent>();
|
||||||
}
|
}
|
|
@ -416,6 +416,9 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Providers\RemoteSearchResult.cs">
|
<Compile Include="..\MediaBrowser.Model\Providers\RemoteSearchResult.cs">
|
||||||
<Link>Providers\RemoteSearchResult.cs</Link>
|
<Link>Providers\RemoteSearchResult.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\MediaBrowser.Model\Providers\RemoteSubtitleInfo.cs">
|
||||||
|
<Link>Providers\RemoteSubtitleInfo.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs">
|
<Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs">
|
||||||
<Link>Querying\ArtistsQuery.cs</Link>
|
<Link>Querying\ArtistsQuery.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -403,6 +403,9 @@
|
||||||
<Compile Include="..\MediaBrowser.Model\Providers\RemoteSearchResult.cs">
|
<Compile Include="..\MediaBrowser.Model\Providers\RemoteSearchResult.cs">
|
||||||
<Link>Providers\RemoteSearchResult.cs</Link>
|
<Link>Providers\RemoteSearchResult.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\MediaBrowser.Model\Providers\RemoteSubtitleInfo.cs">
|
||||||
|
<Link>Providers\RemoteSubtitleInfo.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs">
|
<Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs">
|
||||||
<Link>Querying\ArtistsQuery.cs</Link>
|
<Link>Querying\ArtistsQuery.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
|
|
@ -760,7 +760,7 @@ namespace MediaBrowser.Model.ApiClient
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="options">The options.</param>
|
/// <param name="options">The options.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
string GetSubtitleUrl(SubtitleOptions options);
|
string GetSubtitleUrl(SubtitleDownloadOptions options);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets an image url that can be used to download an image from the api
|
/// 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 NotificationOptions NotificationOptions { get; set; }
|
||||||
|
|
||||||
|
public SubtitleOptions SubtitleOptions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
|
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -284,6 +286,8 @@ namespace MediaBrowser.Model.Configuration
|
||||||
UICulture = "en-us";
|
UICulture = "en-us";
|
||||||
|
|
||||||
NotificationOptions = new NotificationOptions();
|
NotificationOptions = new NotificationOptions();
|
||||||
|
|
||||||
|
SubtitleOptions = new SubtitleOptions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,4 +315,17 @@ namespace MediaBrowser.Model.Configuration
|
||||||
public string From { get; set; }
|
public string From { get; set; }
|
||||||
public string To { 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 string DeviceId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SubtitleOptions
|
public class SubtitleDownloadOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the item identifier.
|
/// Gets or sets the item identifier.
|
||||||
|
|
|
@ -139,6 +139,7 @@
|
||||||
<Compile Include="Notifications\NotificationsSummary.cs" />
|
<Compile Include="Notifications\NotificationsSummary.cs" />
|
||||||
<Compile Include="Providers\RemoteImageResult.cs" />
|
<Compile Include="Providers\RemoteImageResult.cs" />
|
||||||
<Compile Include="Providers\RemoteSearchResult.cs" />
|
<Compile Include="Providers\RemoteSearchResult.cs" />
|
||||||
|
<Compile Include="Providers\RemoteSubtitleInfo.cs" />
|
||||||
<Compile Include="Querying\ArtistsQuery.cs" />
|
<Compile Include="Querying\ArtistsQuery.cs" />
|
||||||
<Compile Include="Querying\EpisodeQuery.cs" />
|
<Compile Include="Querying\EpisodeQuery.cs" />
|
||||||
<Compile Include="Querying\ItemCountsQuery.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>
|
/// </summary>
|
||||||
IsFavorite = 5,
|
IsFavorite = 5,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// The is recently added
|
||||||
|
/// </summary>
|
||||||
|
IsRecentlyAdded = 6,
|
||||||
|
/// <summary>
|
||||||
/// The item is resumable
|
/// The item is resumable
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IsResumable = 7,
|
IsResumable = 7,
|
||||||
|
|
|
@ -108,6 +108,7 @@
|
||||||
<Compile Include="MediaInfo\FFProbeHelpers.cs" />
|
<Compile Include="MediaInfo\FFProbeHelpers.cs" />
|
||||||
<Compile Include="MediaInfo\FFProbeProvider.cs" />
|
<Compile Include="MediaInfo\FFProbeProvider.cs" />
|
||||||
<Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
|
<Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
|
||||||
|
<Compile Include="MediaInfo\SubtitleDownloader.cs" />
|
||||||
<Compile Include="Movies\MovieDbTrailerProvider.cs" />
|
<Compile Include="Movies\MovieDbTrailerProvider.cs" />
|
||||||
<Compile Include="Movies\MovieExternalIds.cs" />
|
<Compile Include="Movies\MovieExternalIds.cs" />
|
||||||
<Compile Include="Movies\TrailerMetadataService.cs" />
|
<Compile Include="Movies\TrailerMetadataService.cs" />
|
||||||
|
@ -187,6 +188,7 @@
|
||||||
<Compile Include="Studios\StudiosImageProvider.cs" />
|
<Compile Include="Studios\StudiosImageProvider.cs" />
|
||||||
<Compile Include="Studios\StudioMetadataService.cs" />
|
<Compile Include="Studios\StudioMetadataService.cs" />
|
||||||
<Compile Include="Subtitles\OpenSubtitleDownloader.cs" />
|
<Compile Include="Subtitles\OpenSubtitleDownloader.cs" />
|
||||||
|
<Compile Include="Subtitles\SubtitleManager.cs" />
|
||||||
<Compile Include="TV\EpisodeLocalImageProvider.cs" />
|
<Compile Include="TV\EpisodeLocalImageProvider.cs" />
|
||||||
<Compile Include="TV\EpisodeMetadataService.cs" />
|
<Compile Include="TV\EpisodeMetadataService.cs" />
|
||||||
<Compile Include="TV\EpisodeXmlProvider.cs" />
|
<Compile Include="TV\EpisodeXmlProvider.cs" />
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
|
@ -10,15 +11,16 @@ using MediaBrowser.Controller.Localization;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Controller.Subtitles;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.MediaInfo
|
namespace MediaBrowser.Providers.MediaInfo
|
||||||
{
|
{
|
||||||
|
@ -45,6 +47,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
private readonly IJsonSerializer _json;
|
private readonly IJsonSerializer _json;
|
||||||
private readonly IEncodingManager _encodingManager;
|
private readonly IEncodingManager _encodingManager;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IServerConfigurationManager _config;
|
||||||
|
private readonly ISubtitleManager _subtitleManager;
|
||||||
|
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
|
@ -96,7 +100,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
return FetchAudioInfo(item, cancellationToken);
|
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;
|
_logger = logger;
|
||||||
_isoManager = isoManager;
|
_isoManager = isoManager;
|
||||||
|
@ -108,6 +112,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
_json = json;
|
_json = json;
|
||||||
_encodingManager = encodingManager;
|
_encodingManager = encodingManager;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
_config = config;
|
||||||
|
_subtitleManager = subtitleManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
|
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
|
||||||
|
@ -134,7 +140,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
return _cachedTask;
|
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);
|
return prober.ProbeVideo(item, directoryService, cancellationToken);
|
||||||
}
|
}
|
||||||
|
@ -165,7 +171,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
|
|
||||||
if (video != null && !video.IsPlaceHolder)
|
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);
|
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.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Localization;
|
using MediaBrowser.Controller.Localization;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Controller.Subtitles;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
@ -35,10 +39,12 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
private readonly IJsonSerializer _json;
|
private readonly IJsonSerializer _json;
|
||||||
private readonly IEncodingManager _encodingManager;
|
private readonly IEncodingManager _encodingManager;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IServerConfigurationManager _config;
|
||||||
|
private readonly ISubtitleManager _subtitleManager;
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
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;
|
_logger = logger;
|
||||||
_isoManager = isoManager;
|
_isoManager = isoManager;
|
||||||
|
@ -50,6 +56,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
_json = json;
|
_json = json;
|
||||||
_encodingManager = encodingManager;
|
_encodingManager = encodingManager;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
_config = config;
|
||||||
|
_subtitleManager = subtitleManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, CancellationToken cancellationToken)
|
public async Task<ItemUpdateType> ProbeVideo<T>(T item, IDirectoryService directoryService, CancellationToken cancellationToken)
|
||||||
|
@ -118,7 +126,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var idString = item.Id.ToString("N");
|
var idString = item.Id.ToString("N");
|
||||||
var cachePath = Path.Combine(_appPaths.CachePath,
|
var cachePath = Path.Combine(_appPaths.CachePath,
|
||||||
"ffprobe-video",
|
"ffprobe-video",
|
||||||
idString.Substring(0, 2), idString, "v" + SchemaVersion + _mediaEncoder.Version + item.DateModified.Ticks.ToString(_usCulture) + ".json");
|
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);
|
FetchBdInfo(video, chapters, mediaStreams, blurayInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddExternalSubtitles(video, mediaStreams, directoryService);
|
await AddExternalSubtitles(video, mediaStreams, directoryService, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
FetchWtvInfo(video, data);
|
FetchWtvInfo(video, data);
|
||||||
|
|
||||||
|
@ -247,7 +255,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info.StartPositionTicks = chapter.start/100;
|
info.StartPositionTicks = chapter.start / 100;
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
@ -450,11 +458,42 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="video">The video.</param>
|
/// <param name="video">The video.</param>
|
||||||
/// <param name="currentStreams">The current streams.</param>
|
/// <param name="currentStreams">The current streams.</param>
|
||||||
private 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 files = GetSubtitleFiles(video, directoryService);
|
||||||
|
|
||||||
var startIndex = currentStreams.Count;
|
|
||||||
var streams = new List<MediaStream>();
|
var streams = new List<MediaStream>();
|
||||||
|
|
||||||
var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
|
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();
|
return streams;
|
||||||
|
|
||||||
currentStreams.AddRange(streams);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -627,7 +664,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||||
{
|
{
|
||||||
var path = mount == null ? item.Path : mount.MountedPath;
|
var path = mount == null ? item.Path : mount.MountedPath;
|
||||||
var dvd = new Dvd(path);
|
var dvd = new Dvd(path);
|
||||||
|
|
||||||
var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
|
var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
|
||||||
|
|
||||||
byte? titleNumber = null;
|
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.Providers;
|
||||||
|
using MediaBrowser.Controller.Subtitles;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.Providers;
|
||||||
using OpenSubtitlesHandler;
|
using OpenSubtitlesHandler;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -20,9 +22,9 @@ namespace MediaBrowser.Providers.Subtitles
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
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;
|
_httpClient = httpClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,39 +38,71 @@ namespace MediaBrowser.Providers.Subtitles
|
||||||
get { return new[] { SubtitleMediaType.Episode, SubtitleMediaType.Movie }; }
|
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)
|
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);
|
var imdbIdText = request.GetProviderId(MetadataProviders.Imdb);
|
||||||
long imdbId;
|
long imdbId;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(imdbIdText) ||
|
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)
|
switch (request.ContentType)
|
||||||
{
|
{
|
||||||
case SubtitleMediaType.Episode:
|
case SubtitleMediaType.Episode:
|
||||||
if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName))
|
if (!request.IndexNumber.HasValue || !request.ParentIndexNumber.HasValue || string.IsNullOrEmpty(request.SeriesName))
|
||||||
{
|
{
|
||||||
_logger.Debug("Information Missing");
|
_logger.Debug("Episode information missing");
|
||||||
return response;
|
return new List<RemoteSubtitleInfo>();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SubtitleMediaType.Movie:
|
case SubtitleMediaType.Movie:
|
||||||
if (string.IsNullOrEmpty(request.Name))
|
if (string.IsNullOrEmpty(request.Name))
|
||||||
{
|
{
|
||||||
_logger.Debug("Information Missing");
|
_logger.Debug("Movie name missing");
|
||||||
return response;
|
return new List<RemoteSubtitleInfo>();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -76,16 +110,18 @@ namespace MediaBrowser.Providers.Subtitles
|
||||||
if (string.IsNullOrEmpty(request.MediaPath))
|
if (string.IsNullOrEmpty(request.MediaPath))
|
||||||
{
|
{
|
||||||
_logger.Debug("Path Missing");
|
_logger.Debug("Path Missing");
|
||||||
return response;
|
return new List<RemoteSubtitleInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Utilities.HttpClient = _httpClient;
|
Utilities.HttpClient = _httpClient;
|
||||||
OpenSubtitles.SetUserAgent("OS Test User Agent");
|
OpenSubtitles.SetUserAgent("OS Test User Agent");
|
||||||
var loginResponse = OpenSubtitles.LogIn("", "", "en");
|
|
||||||
|
var loginResponse = await OpenSubtitles.LogInAsync("", "", "en", cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!(loginResponse is MethodResponseLogIn))
|
if (!(loginResponse is MethodResponseLogIn))
|
||||||
{
|
{
|
||||||
_logger.Debug("Login error");
|
_logger.Debug("Login error");
|
||||||
return response;
|
return new List<RemoteSubtitleInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var subLanguageId = request.Language;
|
var subLanguageId = request.Language;
|
||||||
|
@ -105,54 +141,42 @@ namespace MediaBrowser.Providers.Subtitles
|
||||||
var result = OpenSubtitles.SearchSubtitles(parms.ToArray());
|
var result = OpenSubtitles.SearchSubtitles(parms.ToArray());
|
||||||
if (!(result is MethodResponseSubtitleSearch))
|
if (!(result is MethodResponseSubtitleSearch))
|
||||||
{
|
{
|
||||||
_logger.Debug("invalid response type");
|
_logger.Debug("Invalid response type");
|
||||||
return null;
|
return new List<RemoteSubtitleInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
Predicate<SubtitleSearchResult> mediaFilter =
|
Predicate<SubtitleSearchResult> mediaFilter =
|
||||||
x =>
|
x =>
|
||||||
request.ContentType == SubtitleMediaType.Episode
|
request.ContentType == SubtitleMediaType.Episode
|
||||||
? int.Parse(x.SeriesSeason) == request.ParentIndexNumber && int.Parse(x.SeriesEpisode) == request.IndexNumber
|
? int.Parse(x.SeriesSeason, _usCulture) == request.ParentIndexNumber && int.Parse(x.SeriesEpisode, _usCulture) == request.IndexNumber
|
||||||
: long.Parse(x.IDMovieImdb) == imdbId;
|
: long.Parse(x.IDMovieImdb, _usCulture) == imdbId;
|
||||||
|
|
||||||
var results = ((MethodResponseSubtitleSearch)result).Results;
|
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)
|
.OrderBy(x => x.MovieHash == hash)
|
||||||
.ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize) - movieByteSize))
|
.ThenBy(x => Math.Abs(long.Parse(x.MovieByteSize, _usCulture) - movieByteSize))
|
||||||
.ThenByDescending(x => int.Parse(x.SubDownloadsCnt))
|
.ThenByDescending(x => int.Parse(x.SubDownloadsCnt, _usCulture))
|
||||||
.ThenByDescending(x => double.Parse(x.SubRating))
|
.ThenByDescending(x => double.Parse(x.SubRating, _usCulture))
|
||||||
.ToList();
|
.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())
|
Id = i.SubFormat + "-" + i.SubLanguageID + "-" + i.IDSubtitle,
|
||||||
{
|
|
||||||
_logger.Debug("No Subtitles");
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Debug("Found " + bestResult.Count + " subtitles.");
|
Name = i.SubFileName,
|
||||||
|
DateCreated = DateTime.Parse(i.SubAddDate, _usCulture),
|
||||||
var subtitle = bestResult.First();
|
IsHashMatch = i.MovieHash == hasCopy
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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");
|
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)
|
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
|
// Find an actual physical folder
|
||||||
if (folder is CollectionFolder)
|
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>
|
/// <param name="message">The message.</param>
|
||||||
public void Warn(object message)
|
public void Warn(object message)
|
||||||
{
|
{
|
||||||
_logger.Warn(GetMesssage(message));
|
// Hide StringMapTypeDeserializer messages
|
||||||
|
// _logger.Warn(GetMesssage(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -216,7 +217,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
||||||
/// <param name="args">The args.</param>
|
/// <param name="args">The args.</param>
|
||||||
public void WarnFormat(string format, params object[] args)
|
public void WarnFormat(string format, params object[] args)
|
||||||
{
|
{
|
||||||
_logger.Warn(format, args);
|
// Hide StringMapTypeDeserializer messages
|
||||||
|
// _logger.Warn(format, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using MoreLinq;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -106,16 +105,13 @@ namespace MediaBrowser.Server.Implementations.Localization
|
||||||
/// <returns>IEnumerable{CultureDto}.</returns>
|
/// <returns>IEnumerable{CultureDto}.</returns>
|
||||||
public IEnumerable<CultureDto> GetCultures()
|
public IEnumerable<CultureDto> GetCultures()
|
||||||
{
|
{
|
||||||
return CultureInfo.GetCultures(CultureTypes.AllCultures)
|
var type = GetType();
|
||||||
.OrderBy(c => c.DisplayName)
|
var path = type.Namespace + ".cultures.json";
|
||||||
.DistinctBy(c => c.TwoLetterISOLanguageName + c.ThreeLetterISOLanguageName)
|
|
||||||
.Select(c => new CultureDto
|
using (var stream = type.Assembly.GetManifestResourceStream(path))
|
||||||
{
|
{
|
||||||
Name = c.Name,
|
return _jsonSerializer.DeserializeFromStream<List<CultureDto>>(stream);
|
||||||
DisplayName = c.DisplayName,
|
}
|
||||||
ThreeLetterISOLanguageName = c.ThreeLetterISOLanguageName,
|
|
||||||
TwoLetterISOLanguageName = c.TwoLetterISOLanguageName
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -124,28 +120,13 @@ namespace MediaBrowser.Server.Implementations.Localization
|
||||||
/// <returns>IEnumerable{CountryInfo}.</returns>
|
/// <returns>IEnumerable{CountryInfo}.</returns>
|
||||||
public IEnumerable<CountryInfo> GetCountries()
|
public IEnumerable<CountryInfo> GetCountries()
|
||||||
{
|
{
|
||||||
return CultureInfo.GetCultures(CultureTypes.SpecificCultures)
|
var type = GetType();
|
||||||
.Select(c =>
|
var path = type.Namespace + ".countries.json";
|
||||||
{
|
|
||||||
try
|
using (var stream = type.Assembly.GetManifestResourceStream(path))
|
||||||
{
|
{
|
||||||
return new RegionInfo(c.LCID);
|
return _jsonSerializer.DeserializeFromStream<List<CountryInfo>>(stream);
|
||||||
}
|
}
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -627,5 +627,84 @@
|
||||||
"OptionSpecialFeatures": "Special Features",
|
"OptionSpecialFeatures": "Special Features",
|
||||||
"HeaderCollections": "Collections",
|
"HeaderCollections": "Collections",
|
||||||
"HeaderChannels": "Channels",
|
"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\GenresValidator.cs" />
|
||||||
<Compile Include="Library\Validators\MusicGenresPostScanTask.cs" />
|
<Compile Include="Library\Validators\MusicGenresPostScanTask.cs" />
|
||||||
<Compile Include="Library\Validators\MusicGenresValidator.cs" />
|
<Compile Include="Library\Validators\MusicGenresValidator.cs" />
|
||||||
<Compile Include="Library\Validators\PeoplePostScanTask.cs" />
|
|
||||||
<Compile Include="Library\Validators\PeopleValidator.cs" />
|
<Compile Include="Library\Validators\PeopleValidator.cs" />
|
||||||
<Compile Include="Library\Validators\StudiosPostScanTask.cs" />
|
<Compile Include="Library\Validators\StudiosPostScanTask.cs" />
|
||||||
<Compile Include="Library\Validators\StudiosValidator.cs" />
|
<Compile Include="Library\Validators\StudiosValidator.cs" />
|
||||||
|
@ -328,6 +327,8 @@
|
||||||
<EmbeddedResource Include="Localization\Server\ms.json" />
|
<EmbeddedResource Include="Localization\Server\ms.json" />
|
||||||
<EmbeddedResource Include="Localization\JavaScript\kk.json" />
|
<EmbeddedResource Include="Localization\JavaScript\kk.json" />
|
||||||
<EmbeddedResource Include="Localization\Server\kk.json" />
|
<EmbeddedResource Include="Localization\Server\kk.json" />
|
||||||
|
<EmbeddedResource Include="Localization\countries.json" />
|
||||||
|
<EmbeddedResource Include="Localization\cultures.json" />
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -31,11 +31,11 @@ using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Controller.Sorting;
|
using MediaBrowser.Controller.Sorting;
|
||||||
|
using MediaBrowser.Controller.Subtitles;
|
||||||
using MediaBrowser.Controller.Themes;
|
using MediaBrowser.Controller.Themes;
|
||||||
using MediaBrowser.Dlna;
|
using MediaBrowser.Dlna;
|
||||||
using MediaBrowser.Dlna.Eventing;
|
using MediaBrowser.Dlna.Eventing;
|
||||||
using MediaBrowser.Dlna.Main;
|
using MediaBrowser.Dlna.Main;
|
||||||
using MediaBrowser.Dlna.PlayTo;
|
|
||||||
using MediaBrowser.Dlna.Server;
|
using MediaBrowser.Dlna.Server;
|
||||||
using MediaBrowser.MediaEncoding.BdInfo;
|
using MediaBrowser.MediaEncoding.BdInfo;
|
||||||
using MediaBrowser.MediaEncoding.Encoder;
|
using MediaBrowser.MediaEncoding.Encoder;
|
||||||
|
@ -44,6 +44,7 @@ using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
using MediaBrowser.Model.Updates;
|
using MediaBrowser.Model.Updates;
|
||||||
using MediaBrowser.Providers.Manager;
|
using MediaBrowser.Providers.Manager;
|
||||||
|
using MediaBrowser.Providers.Subtitles;
|
||||||
using MediaBrowser.Server.Implementations;
|
using MediaBrowser.Server.Implementations;
|
||||||
using MediaBrowser.Server.Implementations.Channels;
|
using MediaBrowser.Server.Implementations.Channels;
|
||||||
using MediaBrowser.Server.Implementations.Collections;
|
using MediaBrowser.Server.Implementations.Collections;
|
||||||
|
@ -193,6 +194,7 @@ namespace MediaBrowser.ServerApplication
|
||||||
private IProviderRepository ProviderRepository { get; set; }
|
private IProviderRepository ProviderRepository { get; set; }
|
||||||
|
|
||||||
private INotificationManager NotificationManager { get; set; }
|
private INotificationManager NotificationManager { get; set; }
|
||||||
|
private ISubtitleManager SubtitleManager { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ApplicationHost"/> class.
|
/// Initializes a new instance of the <see cref="ApplicationHost"/> class.
|
||||||
|
@ -531,6 +533,9 @@ namespace MediaBrowser.ServerApplication
|
||||||
NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
|
NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
|
||||||
RegisterSingleInstance(NotificationManager);
|
RegisterSingleInstance(NotificationManager);
|
||||||
|
|
||||||
|
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor);
|
||||||
|
RegisterSingleInstance(SubtitleManager);
|
||||||
|
|
||||||
var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));
|
var displayPreferencesTask = Task.Run(async () => await ConfigureDisplayPreferencesRepositories().ConfigureAwait(false));
|
||||||
var itemsTask = Task.Run(async () => await ConfigureItemRepositories().ConfigureAwait(false));
|
var itemsTask = Task.Run(async () => await ConfigureItemRepositories().ConfigureAwait(false));
|
||||||
var userdataTask = Task.Run(async () => await ConfigureUserDataRepositories().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);
|
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);
|
RegisterSingleInstance(MediaEncoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -710,6 +715,8 @@ namespace MediaBrowser.ServerApplication
|
||||||
|
|
||||||
LiveTvManager.AddParts(GetExports<ILiveTvService>());
|
LiveTvManager.AddParts(GetExports<ILiveTvService>());
|
||||||
|
|
||||||
|
SubtitleManager.AddParts(GetExports<ISubtitleProvider>());
|
||||||
|
|
||||||
SessionManager.AddParts(GetExports<ISessionControllerFactory>());
|
SessionManager.AddParts(GetExports<ISessionControllerFactory>());
|
||||||
|
|
||||||
ChannelManager.AddParts(GetExports<IChannel>(), GetExports<IChannelFactory>());
|
ChannelManager.AddParts(GetExports<IChannel>(), GetExports<IChannelFactory>());
|
||||||
|
|
|
@ -4,6 +4,8 @@ using Mono.Unix.Native;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
#endif
|
#endif
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace MediaBrowser.ServerApplication.FFMpeg
|
namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
{
|
{
|
||||||
|
@ -32,7 +34,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
switch (arg)
|
switch (arg)
|
||||||
{
|
{
|
||||||
case "Version":
|
case "Version":
|
||||||
return "20140304";
|
return "20140506";
|
||||||
case "FFMpegFilename":
|
case "FFMpegFilename":
|
||||||
return "ffmpeg.exe";
|
return "ffmpeg.exe";
|
||||||
case "FFProbeFilename":
|
case "FFProbeFilename":
|
||||||
|
@ -42,7 +44,6 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
#if __MonoCS__
|
|
||||||
case PlatformID.Unix:
|
case PlatformID.Unix:
|
||||||
if (PlatformDetection.IsMac)
|
if (PlatformDetection.IsMac)
|
||||||
{
|
{
|
||||||
|
@ -69,7 +70,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
switch (arg)
|
switch (arg)
|
||||||
{
|
{
|
||||||
case "Version":
|
case "Version":
|
||||||
return "20140304";
|
return "20140506";
|
||||||
case "FFMpegFilename":
|
case "FFMpegFilename":
|
||||||
return "ffmpeg";
|
return "ffmpeg";
|
||||||
case "FFProbeFilename":
|
case "FFProbeFilename":
|
||||||
|
@ -85,7 +86,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
switch (arg)
|
switch (arg)
|
||||||
{
|
{
|
||||||
case "Version":
|
case "Version":
|
||||||
return "20140304";
|
return "20140505";
|
||||||
case "FFMpegFilename":
|
case "FFMpegFilename":
|
||||||
return "ffmpeg";
|
return "ffmpeg";
|
||||||
case "FFProbeFilename":
|
case "FFProbeFilename":
|
||||||
|
@ -98,7 +99,6 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
}
|
}
|
||||||
// Unsupported Unix platform
|
// Unsupported Unix platform
|
||||||
return "";
|
return "";
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
@ -106,18 +106,17 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
private static string[] GetDownloadUrls()
|
private static string[] GetDownloadUrls()
|
||||||
{
|
{
|
||||||
var pid = Environment.OSVersion.Platform;
|
var pid = Environment.OSVersion.Platform;
|
||||||
|
|
||||||
switch (pid)
|
switch (pid)
|
||||||
{
|
{
|
||||||
case PlatformID.Win32NT:
|
case PlatformID.Win32NT:
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
"http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20140304-git-f34cceb-win32-static.7z",
|
"http://ffmpeg.zeranoe.com/builds/win32/static/ffmpeg-20140506-git-2baf1c8-win32-static.7z",
|
||||||
"https://www.dropbox.com/s/6brdetuzbld93jk/ffmpeg-20140304-git-f34cceb-win32-static.7z?dl=1"
|
"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)
|
if (PlatformDetection.IsMac && PlatformDetection.IsX86_64)
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
|
@ -132,8 +131,8 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
"http://ffmpeg.gusari.org/static/32bit/ffmpeg.static.32bit.2014-03-04.tar.gz",
|
"http://ffmpeg.gusari.org/static/32bit/ffmpeg.static.32bit.latest.tar.gz",
|
||||||
"https://www.dropbox.com/s/0l76mcauqqkta31/ffmpeg.static.32bit.2014-03-04.tar.gz?dl=1"
|
"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[]
|
return new[]
|
||||||
{
|
{
|
||||||
"http://ffmpeg.gusari.org/static/64bit/ffmpeg.static.64bit.2014-03-04.tar.gz",
|
"http://ffmpeg.gusari.org/static/64bit/ffmpeg.static.64bit.latest.tar.gz",
|
||||||
"https://www.dropbox.com/s/9wlxz440mdejuqe/ffmpeg.static.64bit.2014-03-04.tar.gz?dl=1"
|
"https://www.dropbox.com/s/onuregwghywnzjo/ffmpeg.static.64bit.2014-05-05.tar.gz?dl=1"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//No Unix version available
|
//No Unix version available
|
||||||
return new string[] {};
|
return new string[] { };
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
return new string[] {};
|
return new string[] { };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __MonoCS__
|
|
||||||
public static class PlatformDetection
|
public static class PlatformDetection
|
||||||
{
|
{
|
||||||
public readonly static bool IsWindows;
|
public readonly static bool IsWindows;
|
||||||
|
@ -166,34 +163,52 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
public readonly static bool IsX86_64;
|
public readonly static bool IsX86_64;
|
||||||
public readonly static bool IsArm;
|
public readonly static bool IsArm;
|
||||||
|
|
||||||
static PlatformDetection ()
|
static PlatformDetection()
|
||||||
{
|
{
|
||||||
IsWindows = Path.DirectorySeparatorChar == '\\';
|
IsWindows = Path.DirectorySeparatorChar == '\\';
|
||||||
|
|
||||||
//Don't call uname on windows
|
//Don't call uname on windows
|
||||||
if (!IsWindows)
|
if (!IsWindows)
|
||||||
{
|
{
|
||||||
Utsname uname;
|
var uname = GetUnixName();
|
||||||
var callResult = Syscall.uname(out uname);
|
|
||||||
if (callResult == 0)
|
|
||||||
{
|
|
||||||
IsMac = uname.sysname == "Darwin";
|
|
||||||
IsLinux = !IsMac && uname.sysname == "Linux";
|
|
||||||
|
|
||||||
Regex archX86 = new Regex("(i|I)[3-6]86");
|
IsMac = uname.sysname == "Darwin";
|
||||||
IsX86 = archX86.IsMatch(uname.machine);
|
IsLinux = uname.sysname == "Linux";
|
||||||
IsX86_64 = !IsX86 && uname.machine == "x86_64";
|
|
||||||
IsArm = !IsX86 && !IsX86 && uname.machine.StartsWith("arm");
|
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
|
else
|
||||||
{
|
{
|
||||||
if (System.Environment.Is64BitOperatingSystem)
|
if (Environment.Is64BitOperatingSystem)
|
||||||
IsX86_64 = true;
|
IsX86_64 = true;
|
||||||
else
|
else
|
||||||
IsX86 = true;
|
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)
|
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
|
var info = new FFMpegInfo
|
||||||
{
|
{
|
||||||
ProbePath = Path.Combine(versionedDirectoryPath, FFMpegDownloadInfo.FFProbeFilename),
|
ProbePath = Path.Combine(versionedDirectoryPath, FFMpegDownloadInfo.FFProbeFilename),
|
||||||
Path = Path.Combine(versionedDirectoryPath, FFMpegDownloadInfo.FFMpegFilename),
|
EncoderPath = Path.Combine(versionedDirectoryPath, FFMpegDownloadInfo.FFMpegFilename),
|
||||||
Version = FFMpegDownloadInfo.Version
|
Version = FFMpegDownloadInfo.Version
|
||||||
};
|
};
|
||||||
|
|
||||||
Directory.CreateDirectory(versionedDirectoryPath);
|
Directory.CreateDirectory(versionedDirectoryPath);
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
if (!File.Exists(info.ProbePath) || !File.Exists(info.EncoderPath))
|
||||||
|
|
||||||
double ffmpegPercent = 0;
|
|
||||||
double fontPercent = 0;
|
|
||||||
var syncLock = new object();
|
|
||||||
|
|
||||||
if (!File.Exists(info.ProbePath) || !File.Exists(info.Path))
|
|
||||||
{
|
{
|
||||||
var ffmpegProgress = new ActionableProgress<double>();
|
// ffmpeg not present. See if there's an older version we can start with
|
||||||
ffmpegProgress.RegisterAction(p =>
|
var existingVersion = GetExistingVersion(info, rootEncoderPath);
|
||||||
|
|
||||||
|
// No older version. Need to download and block until complete
|
||||||
|
if (existingVersion == null)
|
||||||
{
|
{
|
||||||
ffmpegPercent = p;
|
await DownloadFFMpeg(versionedDirectoryPath, progress).ConfigureAwait(false);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
});
|
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;
|
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)
|
foreach (var url in FFMpegDownloadInfo.FfMpegUrls)
|
||||||
{
|
{
|
||||||
|
@ -114,7 +137,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
|
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
ExtractFFMpeg(tempFile, Path.GetDirectoryName(info.Path));
|
ExtractFFMpeg(tempFile, directory);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
catch (HttpException ex)
|
catch (HttpException ex)
|
||||||
|
@ -132,7 +155,7 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
|
|
||||||
private void ExtractFFMpeg(string tempFile, string targetFolder)
|
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());
|
var tempFolder = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString());
|
||||||
|
|
||||||
|
@ -171,6 +194,8 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
|
|
||||||
private void ExtractArchive(string archivePath, string targetPath)
|
private void ExtractArchive(string archivePath, string targetPath)
|
||||||
{
|
{
|
||||||
|
_logger.Info("Extracting {0} to {1}", archivePath, targetPath);
|
||||||
|
|
||||||
if (string.Equals(FFMpegDownloadInfo.ArchiveType, "7z", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(FFMpegDownloadInfo.ArchiveType, "7z", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
|
_zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
|
||||||
|
@ -182,6 +207,8 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
}
|
}
|
||||||
private void Extract7zArchive(string archivePath, string targetPath)
|
private void Extract7zArchive(string archivePath, string targetPath)
|
||||||
{
|
{
|
||||||
|
_logger.Info("Extracting {0} to {1}", archivePath, targetPath);
|
||||||
|
|
||||||
_zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
|
_zipClient.ExtractAllFrom7z(archivePath, targetPath, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,7 +228,8 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
/// Extracts the fonts.
|
/// Extracts the fonts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="targetPath">The target path.</param>
|
/// <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
|
try
|
||||||
{
|
{
|
||||||
|
@ -213,12 +241,19 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
|
|
||||||
var fontFile = Path.Combine(fontsDirectory, fontFilename);
|
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)
|
catch (HttpException ex)
|
||||||
{
|
{
|
||||||
|
@ -230,8 +265,6 @@ namespace MediaBrowser.ServerApplication.FFMpeg
|
||||||
// Don't let the server crash because of this
|
// Don't let the server crash because of this
|
||||||
_logger.ErrorException("Error writing ffmpeg font files", ex);
|
_logger.ErrorException("Error writing ffmpeg font files", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.Report(100);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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.
|
/// Gets or sets the path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The path.</value>
|
/// <value>The path.</value>
|
||||||
public string Path { get; set; }
|
public string EncoderPath { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the probe path.
|
/// Gets or sets the probe path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -217,6 +217,9 @@
|
||||||
<Content Include="dashboard-ui\css\images\items\folders\channels.png">
|
<Content Include="dashboard-ui\css\images\items\folders\channels.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</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">
|
<Content Include="dashboard-ui\css\images\items\folders\games.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
|
|
@ -20,6 +20,8 @@ using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using OpenSubtitlesHandler.Console;
|
using OpenSubtitlesHandler.Console;
|
||||||
using XmlRpcHandler;
|
using XmlRpcHandler;
|
||||||
|
|
||||||
|
@ -96,6 +98,56 @@ namespace OpenSubtitlesHandler
|
||||||
}
|
}
|
||||||
return new MethodResponseError("Fail", "Log in failed !");
|
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>
|
/// <summary>
|
||||||
/// Log out from the server. Call this to terminate the session.
|
/// Log out from the server. Call this to terminate the session.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
@ -24,6 +24,7 @@ using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Net;
|
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>
|
/// <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)
|
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);
|
//HttpWebRequest req = (HttpWebRequest)WebRequest.Create(XML_RPC_SERVER);
|
||||||
//req.ContentType = "text/xml";
|
//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
|
var options = new HttpRequestOptions
|
||||||
{
|
{
|
||||||
RequestContentBytes = request,
|
RequestContentBytes = request,
|
||||||
RequestContentType = "text/xml",
|
RequestContentType = "text/xml",
|
||||||
UserAgent = "xmlrpc-epi-php/0.2 (PHP)",
|
UserAgent = userAgent,
|
||||||
Url = XML_RPC_SERVER
|
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);
|
var result = await HttpClient.Post(options).ConfigureAwait(false);
|
||||||
|
|
||||||
return result.Content;
|
return result.Content;
|
||||||
|
|
Loading…
Reference in a new issue