mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
commit
6c2e01830c
@ -318,7 +318,7 @@ namespace MediaBrowser.Api.Images
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var size = _imageProcessor.GetImageSize(info.Path, info.DateModified);
|
var size = _imageProcessor.GetImageSize(info);
|
||||||
|
|
||||||
width = Convert.ToInt32(size.Width);
|
width = Convert.ToInt32(size.Width);
|
||||||
height = Convert.ToInt32(size.Height);
|
height = Convert.ToInt32(size.Height);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using MediaBrowser.Controller.Activity;
|
using MediaBrowser.Controller.Activity;
|
||||||
using MediaBrowser.Controller.Channels;
|
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
@ -9,11 +8,9 @@ using MediaBrowser.Controller.Library;
|
|||||||
using MediaBrowser.Controller.Localization;
|
using MediaBrowser.Controller.Localization;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Session;
|
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Logging;
|
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using ServiceStack;
|
using ServiceStack;
|
||||||
using System;
|
using System;
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
<Compile Include="Library\ChapterService.cs" />
|
<Compile Include="Library\ChapterService.cs" />
|
||||||
<Compile Include="Playback\Hls\MpegDashService.cs" />
|
<Compile Include="Playback\Hls\MpegDashService.cs" />
|
||||||
<Compile Include="Playback\MediaInfoService.cs" />
|
<Compile Include="Playback\MediaInfoService.cs" />
|
||||||
|
<Compile Include="Playback\TranscodingThrottler.cs" />
|
||||||
<Compile Include="PlaylistService.cs" />
|
<Compile Include="PlaylistService.cs" />
|
||||||
<Compile Include="Reports\ReportFieldType.cs" />
|
<Compile Include="Reports\ReportFieldType.cs" />
|
||||||
<Compile Include="Reports\ReportResult.cs" />
|
<Compile Include="Reports\ReportResult.cs" />
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.Model.Extensions;
|
using MediaBrowser.Model.Extensions;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
@ -70,12 +71,14 @@ namespace MediaBrowser.Api.Playback
|
|||||||
protected IDeviceManager DeviceManager { get; private set; }
|
protected IDeviceManager DeviceManager { get; private set; }
|
||||||
protected IChannelManager ChannelManager { get; private set; }
|
protected IChannelManager ChannelManager { get; private set; }
|
||||||
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
|
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
|
||||||
|
protected IProcessManager ProcessManager { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
|
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager)
|
protected BaseStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager)
|
||||||
{
|
{
|
||||||
|
ProcessManager = processManager;
|
||||||
DeviceManager = deviceManager;
|
DeviceManager = deviceManager;
|
||||||
SubtitleEncoder = subtitleEncoder;
|
SubtitleEncoder = subtitleEncoder;
|
||||||
ChannelManager = channelManager;
|
ChannelManager = channelManager;
|
||||||
@ -877,14 +880,6 @@ namespace MediaBrowser.Api.Playback
|
|||||||
return "copy";
|
return "copy";
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool SupportsThrottleWithStream
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the input argument.
|
/// Gets the input argument.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -908,23 +903,15 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
private string GetInputPathArgument(string transcodingJobId, StreamState state)
|
private string GetInputPathArgument(string transcodingJobId, StreamState state)
|
||||||
{
|
{
|
||||||
if (state.InputProtocol == MediaProtocol.File &&
|
//if (state.InputProtocol == MediaProtocol.File &&
|
||||||
state.RunTimeTicks.HasValue &&
|
// state.RunTimeTicks.HasValue &&
|
||||||
state.VideoType == VideoType.VideoFile &&
|
// state.VideoType == VideoType.VideoFile &&
|
||||||
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
// !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
//{
|
||||||
if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
|
// if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
|
||||||
{
|
// {
|
||||||
if (SupportsThrottleWithStream)
|
// }
|
||||||
{
|
//}
|
||||||
var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/videos/" + state.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + state.Request.MediaSourceId;
|
|
||||||
|
|
||||||
url += "&transcodingJobId=" + transcodingJobId;
|
|
||||||
|
|
||||||
return string.Format("\"{0}\"", url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var protocol = state.InputProtocol;
|
var protocol = state.InputProtocol;
|
||||||
|
|
||||||
@ -1109,9 +1096,26 @@ namespace MediaBrowser.Api.Playback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StartThrottler(state, transcodingJob);
|
||||||
|
|
||||||
return transcodingJob;
|
return transcodingJob;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
|
||||||
|
{
|
||||||
|
if (state.InputProtocol == MediaProtocol.File &&
|
||||||
|
state.RunTimeTicks.HasValue &&
|
||||||
|
state.VideoType == VideoType.VideoFile &&
|
||||||
|
!string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo)
|
||||||
|
{
|
||||||
|
state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ProcessManager);
|
||||||
|
state.TranscodingThrottler.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
|
private async void StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
@ -23,7 +24,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseHlsService : BaseStreamingService
|
public abstract class BaseHlsService : BaseStreamingService
|
||||||
{
|
{
|
||||||
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
|
protected BaseHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.Model.Extensions;
|
using MediaBrowser.Model.Extensions;
|
||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
@ -63,7 +64,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
public class DynamicHlsService : BaseHlsService
|
public class DynamicHlsService : BaseHlsService
|
||||||
{
|
{
|
||||||
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
|
public DynamicHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager)
|
||||||
{
|
{
|
||||||
NetworkManager = networkManager;
|
NetworkManager = networkManager;
|
||||||
}
|
}
|
||||||
@ -115,6 +116,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
if (File.Exists(segmentPath))
|
if (File.Exists(segmentPath))
|
||||||
{
|
{
|
||||||
|
job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
|
||||||
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +125,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
{
|
{
|
||||||
if (File.Exists(segmentPath))
|
if (File.Exists(segmentPath))
|
||||||
{
|
{
|
||||||
|
job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
|
||||||
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
|
|||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
@ -51,7 +52,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
|
|
||||||
public class MpegDashService : BaseHlsService
|
public class MpegDashService : BaseHlsService
|
||||||
{
|
{
|
||||||
public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
|
public MpegDashService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, INetworkManager networkManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager)
|
||||||
{
|
{
|
||||||
NetworkManager = networkManager;
|
NetworkManager = networkManager;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ using MediaBrowser.Common.IO;
|
|||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
@ -57,7 +58,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class VideoHlsService : BaseHlsService
|
public class VideoHlsService : BaseHlsService
|
||||||
{
|
{
|
||||||
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
|
public VideoHlsService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ using System.Threading.Tasks;
|
|||||||
namespace MediaBrowser.Api.Playback
|
namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
[Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")]
|
[Route("/Items/{Id}/MediaInfo", "GET", Summary = "Gets live playback media info for an item")]
|
||||||
|
[Route("/Items/{Id}/PlaybackInfo", "GET", Summary = "Gets live playback media info for an item")]
|
||||||
public class GetLiveMediaInfo : IReturn<LiveMediaInfoResult>
|
public class GetLiveMediaInfo : IReturn<LiveMediaInfoResult>
|
||||||
{
|
{
|
||||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
|
|||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@ -32,7 +33,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AudioService : BaseProgressiveStreamingService
|
public class AudioService : BaseProgressiveStreamingService
|
||||||
{
|
{
|
||||||
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, imageProcessor, httpClient)
|
public AudioService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager, imageProcessor, httpClient)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
|
|||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@ -29,7 +30,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
protected readonly IImageProcessor ImageProcessor;
|
protected readonly IImageProcessor ImageProcessor;
|
||||||
protected readonly IHttpClient HttpClient;
|
protected readonly IHttpClient HttpClient;
|
||||||
|
|
||||||
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager)
|
protected BaseProgressiveStreamingService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager)
|
||||||
{
|
{
|
||||||
ImageProcessor = imageProcessor;
|
ImageProcessor = imageProcessor;
|
||||||
HttpClient = httpClient;
|
HttpClient = httpClient;
|
||||||
@ -153,49 +154,12 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
|
|
||||||
using (state)
|
using (state)
|
||||||
{
|
{
|
||||||
var job = string.IsNullOrEmpty(request.TranscodingJobId) ?
|
|
||||||
null :
|
|
||||||
ApiEntryPoint.Instance.GetTranscodingJob(request.TranscodingJobId);
|
|
||||||
|
|
||||||
var limits = new List<long>();
|
|
||||||
if (state.InputBitrate.HasValue)
|
|
||||||
{
|
|
||||||
// Bytes per second
|
|
||||||
limits.Add((state.InputBitrate.Value / 8));
|
|
||||||
}
|
|
||||||
if (state.InputFileSize.HasValue && state.RunTimeTicks.HasValue)
|
|
||||||
{
|
|
||||||
var totalSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds;
|
|
||||||
|
|
||||||
if (totalSeconds > 1)
|
|
||||||
{
|
|
||||||
var timeBasedLimit = state.InputFileSize.Value / totalSeconds;
|
|
||||||
limits.Add(Convert.ToInt64(timeBasedLimit));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take the greater of the above to methods, just to be safe
|
|
||||||
var throttleLimit = limits.Count > 0 ? limits.First() : 0;
|
|
||||||
|
|
||||||
// Pad to play it safe
|
|
||||||
var bytesPerSecond = Convert.ToInt64(1.05 * throttleLimit);
|
|
||||||
|
|
||||||
// Don't even start evaluating this until at least two minutes have content have been consumed
|
|
||||||
var targetGap = throttleLimit * 120;
|
|
||||||
|
|
||||||
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||||
{
|
{
|
||||||
ResponseHeaders = responseHeaders,
|
ResponseHeaders = responseHeaders,
|
||||||
ContentType = contentType,
|
ContentType = contentType,
|
||||||
IsHeadRequest = isHeadRequest,
|
IsHeadRequest = isHeadRequest,
|
||||||
Path = state.MediaPath,
|
Path = state.MediaPath
|
||||||
Throttle = request.Throttle,
|
|
||||||
|
|
||||||
ThrottleLimit = bytesPerSecond,
|
|
||||||
|
|
||||||
MinThrottlePosition = targetGap,
|
|
||||||
|
|
||||||
ThrottleCallback = (l1, l2) => ThrottleCallack(l1, l2, bytesPerSecond, job)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -234,67 +198,6 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(3).Ticks;
|
|
||||||
|
|
||||||
private long ThrottleCallack(long currentBytesPerSecond, long bytesWritten, long originalBytesPerSecond, TranscodingJob job)
|
|
||||||
{
|
|
||||||
var bytesDownloaded = job.BytesDownloaded ?? 0;
|
|
||||||
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
|
|
||||||
var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
|
|
||||||
|
|
||||||
var path = job.Path;
|
|
||||||
|
|
||||||
if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
|
|
||||||
{
|
|
||||||
// Progressive Streaming - byte-based consideration
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
|
|
||||||
|
|
||||||
// Estimate the bytes the transcoder should be ahead
|
|
||||||
double gapFactor = _gapLengthInTicks;
|
|
||||||
gapFactor /= transcodingPositionTicks;
|
|
||||||
var targetGap = bytesTranscoded * gapFactor;
|
|
||||||
|
|
||||||
var gap = bytesTranscoded - bytesDownloaded;
|
|
||||||
|
|
||||||
if (gap < targetGap)
|
|
||||||
{
|
|
||||||
//Logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
//Logger.Error("Error getting output size");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
|
|
||||||
{
|
|
||||||
// HLS - time-based consideration
|
|
||||||
|
|
||||||
var targetGap = _gapLengthInTicks;
|
|
||||||
var gap = transcodingPositionTicks - downloadPositionTicks;
|
|
||||||
|
|
||||||
if (gap < targetGap)
|
|
||||||
{
|
|
||||||
//Logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//Logger.Debug("No throttle data for " + path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return originalBytesPerSecond;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the static remote stream result.
|
/// Gets the static remote stream result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
|
|||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Devices;
|
using MediaBrowser.Controller.Devices;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@ -63,7 +64,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class VideoService : BaseProgressiveStreamingService
|
public class VideoService : BaseProgressiveStreamingService
|
||||||
{
|
{
|
||||||
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, imageProcessor, httpClient)
|
public VideoService(IServerConfigurationManager serverConfig, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IFileSystem fileSystem, ILiveTvManager liveTvManager, IDlnaManager dlnaManager, IChannelManager channelManager, ISubtitleEncoder subtitleEncoder, IDeviceManager deviceManager, IProcessManager processManager, IImageProcessor imageProcessor, IHttpClient httpClient) : base(serverConfig, userManager, libraryManager, isoManager, mediaEncoder, fileSystem, liveTvManager, dlnaManager, channelManager, subtitleEncoder, deviceManager, processManager, imageProcessor, httpClient)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,6 @@ namespace MediaBrowser.Api.Playback
|
|||||||
public string Params { get; set; }
|
public string Params { get; set; }
|
||||||
public string ClientTime { get; set; }
|
public string ClientTime { get; set; }
|
||||||
|
|
||||||
public bool Throttle { get; set; }
|
|
||||||
public string TranscodingJobId { get; set; }
|
public string TranscodingJobId { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
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.Net;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Api.Playback
|
namespace MediaBrowser.Api.Playback
|
||||||
{
|
{
|
||||||
@ -23,6 +22,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
public string RequestedUrl { get; set; }
|
public string RequestedUrl { get; set; }
|
||||||
|
|
||||||
public StreamRequest Request { get; set; }
|
public StreamRequest Request { get; set; }
|
||||||
|
public TranscodingThrottler TranscodingThrottler { get; set; }
|
||||||
|
|
||||||
public VideoStreamRequest VideoRequest
|
public VideoStreamRequest VideoRequest
|
||||||
{
|
{
|
||||||
@ -125,6 +125,7 @@ namespace MediaBrowser.Api.Playback
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
DisposeTranscodingThrottler();
|
||||||
DisposeLiveStream();
|
DisposeLiveStream();
|
||||||
DisposeLogStream();
|
DisposeLogStream();
|
||||||
DisposeIsoMount();
|
DisposeIsoMount();
|
||||||
@ -147,6 +148,23 @@ namespace MediaBrowser.Api.Playback
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DisposeTranscodingThrottler()
|
||||||
|
{
|
||||||
|
if (TranscodingThrottler != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TranscodingThrottler.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error disposing TranscodingThrottler", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
TranscodingThrottler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DisposeIsoMount()
|
private void DisposeIsoMount()
|
||||||
{
|
{
|
||||||
if (IsoMount != null)
|
if (IsoMount != null)
|
||||||
|
164
MediaBrowser.Api/Playback/TranscodingThrottler.cs
Normal file
164
MediaBrowser.Api/Playback/TranscodingThrottler.cs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Api.Playback
|
||||||
|
{
|
||||||
|
public class TranscodingThrottler : IDisposable
|
||||||
|
{
|
||||||
|
private readonly TranscodingJob _job;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IProcessManager _processManager;
|
||||||
|
private Timer _timer;
|
||||||
|
private bool _isPaused;
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
if (_processManager.SupportsSuspension)
|
||||||
|
{
|
||||||
|
_timer = new Timer(TimerCallback, null, 5000, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TimerCallback(object state)
|
||||||
|
{
|
||||||
|
if (_job.HasExited)
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsThrottleAllowed(_job))
|
||||||
|
{
|
||||||
|
PauseTranscoding();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnpauseTranscoding();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PauseTranscoding()
|
||||||
|
{
|
||||||
|
if (!_isPaused)
|
||||||
|
{
|
||||||
|
_logger.Debug("Sending pause command to ffmpeg");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//_job.Process.StandardInput.WriteLine("p");
|
||||||
|
_processManager.SuspendProcess(_job.Process);
|
||||||
|
_isPaused = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error pausing transcoding", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnpauseTranscoding()
|
||||||
|
{
|
||||||
|
if (_isPaused)
|
||||||
|
{
|
||||||
|
_logger.Debug("Sending unpause command to ffmpeg");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//_job.Process.StandardInput.WriteLine("u");
|
||||||
|
_processManager.ResumeProcess(_job.Process);
|
||||||
|
_isPaused = false;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.ErrorException("Error unpausing transcoding", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly long _gapLengthInTicks = TimeSpan.FromMinutes(2).Ticks;
|
||||||
|
|
||||||
|
public TranscodingThrottler(TranscodingJob job, ILogger logger, IProcessManager processManager)
|
||||||
|
{
|
||||||
|
_job = job;
|
||||||
|
_logger = logger;
|
||||||
|
_processManager = processManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsThrottleAllowed(TranscodingJob job)
|
||||||
|
{
|
||||||
|
var bytesDownloaded = job.BytesDownloaded ?? 0;
|
||||||
|
var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0;
|
||||||
|
var downloadPositionTicks = job.DownloadPositionTicks ?? 0;
|
||||||
|
|
||||||
|
var path = job.Path;
|
||||||
|
|
||||||
|
if (downloadPositionTicks > 0 && transcodingPositionTicks > 0)
|
||||||
|
{
|
||||||
|
// HLS - time-based consideration
|
||||||
|
|
||||||
|
var targetGap = _gapLengthInTicks;
|
||||||
|
var gap = transcodingPositionTicks - downloadPositionTicks;
|
||||||
|
|
||||||
|
if (gap < targetGap)
|
||||||
|
{
|
||||||
|
//_logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//_logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytesDownloaded > 0 && transcodingPositionTicks > 0)
|
||||||
|
{
|
||||||
|
// Progressive Streaming - byte-based consideration
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length;
|
||||||
|
|
||||||
|
// Estimate the bytes the transcoder should be ahead
|
||||||
|
double gapFactor = _gapLengthInTicks;
|
||||||
|
gapFactor /= transcodingPositionTicks;
|
||||||
|
var targetGap = bytesTranscoded * gapFactor;
|
||||||
|
|
||||||
|
var gap = bytesTranscoded - bytesDownloaded;
|
||||||
|
|
||||||
|
if (gap < targetGap)
|
||||||
|
{
|
||||||
|
//_logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//_logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//_logger.Error("Error getting output size");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//_logger.Debug("No throttle data for " + path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
DisposeTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeTimer()
|
||||||
|
{
|
||||||
|
if (_timer != null)
|
||||||
|
{
|
||||||
|
_timer.Dispose();
|
||||||
|
_timer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
MediaBrowser.Controller/Diagnostics/IProcessManager.cs
Normal file
28
MediaBrowser.Controller/Diagnostics/IProcessManager.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.Diagnostics
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interface IProcessManager
|
||||||
|
/// </summary>
|
||||||
|
public interface IProcessManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether [supports suspension].
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if [supports suspension]; otherwise, <c>false</c>.</value>
|
||||||
|
bool SupportsSuspension { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Suspends the process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="process">The process.</param>
|
||||||
|
void SuspendProcess(Process process);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resumes the process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="process">The process.</param>
|
||||||
|
void ResumeProcess(Process process);
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@
|
|||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -30,10 +29,9 @@ namespace MediaBrowser.Controller.Drawing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the size of the image.
|
/// Gets the size of the image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="info">The information.</param>
|
||||||
/// <param name="imageDateModified">The image date modified.</param>
|
|
||||||
/// <returns>ImageSize.</returns>
|
/// <returns>ImageSize.</returns>
|
||||||
ImageSize GetImageSize(string path, DateTime imageDateModified);
|
ImageSize GetImageSize(ItemImageInfo info);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the parts.
|
/// Adds the parts.
|
||||||
|
@ -239,7 +239,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||||||
{
|
{
|
||||||
Id = i.Id.ToString("N"),
|
Id = i.Id.ToString("N"),
|
||||||
Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File,
|
Protocol = locationType == LocationType.Remote ? MediaProtocol.Http : MediaProtocol.File,
|
||||||
MediaStreams = MediaSourceManager.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList(),
|
MediaStreams = MediaSourceManager.GetMediaStreams(i.Id).ToList(),
|
||||||
Name = i.Name,
|
Name = i.Name,
|
||||||
Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path,
|
Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path,
|
||||||
RunTimeTicks = i.RunTimeTicks,
|
RunTimeTicks = i.RunTimeTicks,
|
||||||
|
@ -420,12 +420,17 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return base.GetDeletePaths();
|
return base.GetDeletePaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual IEnumerable<MediaStream> GetMediaStreams()
|
public IEnumerable<MediaStream> GetMediaStreams()
|
||||||
{
|
{
|
||||||
return MediaSourceManager.GetMediaStreams(new MediaStreamQuery
|
var mediaSource = GetMediaSources(false)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (mediaSource == null)
|
||||||
{
|
{
|
||||||
ItemId = Id
|
return new List<MediaStream>();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
return mediaSource.MediaStreams;
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual MediaStream GetDefaultVideoStream()
|
public virtual MediaStream GetDefaultVideoStream()
|
||||||
@ -474,7 +479,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
private static MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, Video i, MediaSourceType type)
|
private static MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, Video i, MediaSourceType type)
|
||||||
{
|
{
|
||||||
var mediaStreams = MediaSourceManager.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id })
|
var mediaStreams = MediaSourceManager.GetMediaStreams(i.Id)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var locationType = i.LocationType;
|
var locationType = i.LocationType;
|
||||||
|
@ -1,11 +1,29 @@
|
|||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Library
|
namespace MediaBrowser.Controller.Library
|
||||||
{
|
{
|
||||||
public interface IMediaSourceManager
|
public interface IMediaSourceManager
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the media streams.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="itemId">The item identifier.</param>
|
||||||
|
/// <returns>IEnumerable<MediaStream>.</returns>
|
||||||
|
IEnumerable<MediaStream> GetMediaStreams(Guid itemId);
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the media streams.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaSourceId">The media source identifier.</param>
|
||||||
|
/// <returns>IEnumerable<MediaStream>.</returns>
|
||||||
|
IEnumerable<MediaStream> GetMediaStreams(string mediaSourceId);
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the media streams.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="query">The query.</param>
|
||||||
|
/// <returns>IEnumerable<MediaStream>.</returns>
|
||||||
IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query);
|
IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ namespace MediaBrowser.Controller.Library
|
|||||||
event EventHandler<GenericEventArgs<User>> UserCreated;
|
event EventHandler<GenericEventArgs<User>> UserCreated;
|
||||||
event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
|
event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
|
||||||
event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
|
event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
|
||||||
|
event EventHandler<GenericEventArgs<User>> UserLockedOut;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a User by Id
|
/// Gets a User by Id
|
||||||
|
@ -104,6 +104,7 @@
|
|||||||
<Compile Include="Devices\CameraImageUploadInfo.cs" />
|
<Compile Include="Devices\CameraImageUploadInfo.cs" />
|
||||||
<Compile Include="Devices\IDeviceManager.cs" />
|
<Compile Include="Devices\IDeviceManager.cs" />
|
||||||
<Compile Include="Devices\IDeviceRepository.cs" />
|
<Compile Include="Devices\IDeviceRepository.cs" />
|
||||||
|
<Compile Include="Diagnostics\IProcessManager.cs" />
|
||||||
<Compile Include="Dlna\ControlRequest.cs" />
|
<Compile Include="Dlna\ControlRequest.cs" />
|
||||||
<Compile Include="Dlna\ControlResponse.cs" />
|
<Compile Include="Dlna\ControlResponse.cs" />
|
||||||
<Compile Include="Dlna\EventSubscriptionResponse.cs" />
|
<Compile Include="Dlna\EventSubscriptionResponse.cs" />
|
||||||
@ -341,8 +342,8 @@
|
|||||||
<Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" />
|
<Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" />
|
||||||
<Compile Include="Subtitles\SubtitleResponse.cs" />
|
<Compile Include="Subtitles\SubtitleResponse.cs" />
|
||||||
<Compile Include="Subtitles\SubtitleSearchRequest.cs" />
|
<Compile Include="Subtitles\SubtitleSearchRequest.cs" />
|
||||||
<Compile Include="Sync\ICloudSyncProvider.cs" />
|
|
||||||
<Compile Include="Sync\IServerSyncProvider.cs" />
|
<Compile Include="Sync\IServerSyncProvider.cs" />
|
||||||
|
<Compile Include="Sync\ISyncDataProvider.cs" />
|
||||||
<Compile Include="Sync\ISyncManager.cs" />
|
<Compile Include="Sync\ISyncManager.cs" />
|
||||||
<Compile Include="Sync\ISyncProvider.cs" />
|
<Compile Include="Sync\ISyncProvider.cs" />
|
||||||
<Compile Include="Sync\ISyncRepository.cs" />
|
<Compile Include="Sync\ISyncRepository.cs" />
|
||||||
|
@ -18,11 +18,6 @@ namespace MediaBrowser.Controller.Net
|
|||||||
|
|
||||||
public IDictionary<string, string> ResponseHeaders { get; set; }
|
public IDictionary<string, string> ResponseHeaders { get; set; }
|
||||||
|
|
||||||
public bool Throttle { get; set; }
|
|
||||||
public long ThrottleLimit { get; set; }
|
|
||||||
public long MinThrottlePosition { get; set; }
|
|
||||||
public Func<long, long, long> ThrottleCallback { get; set; }
|
|
||||||
|
|
||||||
public Action OnComplete { get; set; }
|
public Action OnComplete { get; set; }
|
||||||
|
|
||||||
public StaticResultOptions()
|
public StaticResultOptions()
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
using MediaBrowser.Model.Sync;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Sync
|
|
||||||
{
|
|
||||||
public interface ICloudSyncProvider
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the synchronize targets.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="userId">The user identifier.</param>
|
|
||||||
/// <returns>IEnumerable<SyncTarget>.</returns>
|
|
||||||
IEnumerable<SyncTarget> GetSyncTargets(string userId);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Transfers the item file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serverId">The server identifier.</param>
|
|
||||||
/// <param name="itemId">The item identifier.</param>
|
|
||||||
/// <param name="inputFile">The input file.</param>
|
|
||||||
/// <param name="pathParts">The path parts.</param>
|
|
||||||
/// <param name="target">The target.</param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,7 @@
|
|||||||
using MediaBrowser.Model.Sync;
|
using MediaBrowser.Model.Sync;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -7,35 +9,64 @@ namespace MediaBrowser.Controller.Sync
|
|||||||
{
|
{
|
||||||
public interface IServerSyncProvider : ISyncProvider
|
public interface IServerSyncProvider : ISyncProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets the server item ids.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serverId">The server identifier.</param>
|
|
||||||
/// <param name="target">The target.</param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <returns>Task<List<System.String>>.</returns>
|
|
||||||
Task<List<string>> GetServerItemIds(string serverId, SyncTarget target, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes the item.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serverId">The server identifier.</param>
|
|
||||||
/// <param name="itemId">The item identifier.</param>
|
|
||||||
/// <param name="target">The target.</param>
|
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <returns>Task.</returns>
|
|
||||||
Task DeleteItem(string serverId, string itemId, SyncTarget target, CancellationToken cancellationToken);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Transfers the file.
|
/// Transfers the file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serverId">The server identifier.</param>
|
|
||||||
/// <param name="itemId">The item identifier.</param>
|
|
||||||
/// <param name="inputFile">The input file.</param>
|
/// <param name="inputFile">The input file.</param>
|
||||||
/// <param name="pathParts">The path parts.</param>
|
/// <param name="path">The path.</param>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
Task SendFile(string inputFile, string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes the file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="target">The target.</param>
|
/// <param name="target">The target.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken);
|
Task DeleteFile(string path, SyncTarget target, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>Task<Stream>.</returns>
|
||||||
|
Task<Stream> GetFile(string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the full path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
string GetFullPath(IEnumerable<string> path, SyncTarget target);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the parent directory path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
string GetParentDirectoryPath(string path, SyncTarget target);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file system entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">The path.</param>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <returns>Task<List<DeviceFileInfo>>.</returns>
|
||||||
|
Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the data provider.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>ISyncDataProvider.</returns>
|
||||||
|
ISyncDataProvider GetDataProvider();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
41
MediaBrowser.Controller/Sync/ISyncDataProvider.cs
Normal file
41
MediaBrowser.Controller/Sync/ISyncDataProvider.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using MediaBrowser.Model.Sync;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Controller.Sync
|
||||||
|
{
|
||||||
|
public interface ISyncDataProvider
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the server item ids.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <param name="serverId">The server identifier.</param>
|
||||||
|
/// <returns>Task<List<System.String>>.</returns>
|
||||||
|
Task<List<string>> GetServerItemIds(SyncTarget target, string serverId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the or update.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
Task AddOrUpdate(SyncTarget target, LocalItem item);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes the specified identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <param name="id">The identifier.</param>
|
||||||
|
/// <returns>Task.</returns>
|
||||||
|
Task Delete(SyncTarget target, string id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the specified identifier.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <param name="id">The identifier.</param>
|
||||||
|
/// <returns>Task<LocalItem>.</returns>
|
||||||
|
Task<LocalItem> Get(SyncTarget target, string id);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Sync;
|
||||||
using MediaBrowser.Model.Sync;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Sync
|
namespace MediaBrowser.Controller.Sync
|
||||||
@ -20,11 +19,10 @@ namespace MediaBrowser.Controller.Sync
|
|||||||
IEnumerable<SyncTarget> GetSyncTargets(string userId);
|
IEnumerable<SyncTarget> GetSyncTargets(string userId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the device profile.
|
/// Gets all synchronize targets.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="target">The target.</param>
|
/// <returns>IEnumerable<SyncTarget>.</returns>
|
||||||
/// <returns>DeviceProfile.</returns>
|
IEnumerable<SyncTarget> GetAllSyncTargets();
|
||||||
DeviceProfile GetDeviceProfile(SyncTarget target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IHasUniqueTargetIds
|
public interface IHasUniqueTargetIds
|
||||||
|
@ -930,7 +930,7 @@ namespace MediaBrowser.Dlna.Didl
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
|
var size = _imageProcessor.GetImageSize(imageInfo);
|
||||||
|
|
||||||
width = Convert.ToInt32(size.Width);
|
width = Convert.ToInt32(size.Width);
|
||||||
height = Convert.ToInt32(size.Height);
|
height = Convert.ToInt32(size.Height);
|
||||||
|
@ -452,24 +452,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
private string GetInputPathArgument(EncodingJob job)
|
private string GetInputPathArgument(EncodingJob job)
|
||||||
{
|
{
|
||||||
//if (job.InputProtocol == MediaProtocol.File &&
|
|
||||||
// job.RunTimeTicks.HasValue &&
|
|
||||||
// job.VideoType == VideoType.VideoFile &&
|
|
||||||
// !string.Equals(job.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
||||||
//{
|
|
||||||
// if (job.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && job.IsInputVideo)
|
|
||||||
// {
|
|
||||||
// if (SupportsThrottleWithStream)
|
|
||||||
// {
|
|
||||||
// var url = "http://localhost:" + ServerConfigurationManager.Configuration.HttpServerPortNumber.ToString(UsCulture) + "/videos/" + job.Request.Id + "/stream?static=true&Throttle=true&mediaSourceId=" + job.Request.MediaSourceId;
|
|
||||||
|
|
||||||
// url += "&transcodingJobId=" + transcodingJobId;
|
|
||||||
|
|
||||||
// return string.Format("\"{0}\"", url);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
var protocol = job.InputProtocol;
|
var protocol = job.InputProtocol;
|
||||||
|
|
||||||
var inputPath = new[] { job.MediaPath };
|
var inputPath = new[] { job.MediaPath };
|
||||||
|
@ -773,7 +773,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var file = new FileStream(path, FileMode.Open))
|
using (var file = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
{
|
{
|
||||||
var detector = new CharsetDetector();
|
var detector = new CharsetDetector();
|
||||||
detector.Feed(file);
|
detector.Feed(file);
|
||||||
@ -797,12 +797,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Encoding GetFileEncoding(string srcFile)
|
private Encoding GetFileEncoding(string srcFile)
|
||||||
{
|
{
|
||||||
// *** Detect byte order mark if any - otherwise assume default
|
// *** Detect byte order mark if any - otherwise assume default
|
||||||
var buffer = new byte[5];
|
var buffer = new byte[5];
|
||||||
|
|
||||||
using (var file = new FileStream(srcFile, FileMode.Open))
|
using (var file = _fileSystem.GetFileStream(srcFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
{
|
{
|
||||||
file.Read(buffer, 0, 5);
|
file.Read(buffer, 0, 5);
|
||||||
}
|
}
|
||||||
|
@ -362,6 +362,12 @@
|
|||||||
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
|
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
|
||||||
<Link>Dlna\MediaFormatProfileResolver.cs</Link>
|
<Link>Dlna\MediaFormatProfileResolver.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\MediaBrowser.Model\Dlna\PlaybackErrorCode.cs">
|
||||||
|
<Link>Dlna\PlaybackErrorCode.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\MediaBrowser.Model\Dlna\PlaybackException.cs">
|
||||||
|
<Link>Dlna\PlaybackException.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Dlna\ProfileCondition.cs">
|
<Compile Include="..\MediaBrowser.Model\Dlna\ProfileCondition.cs">
|
||||||
<Link>Dlna\ProfileCondition.cs</Link>
|
<Link>Dlna\ProfileCondition.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -327,6 +327,12 @@
|
|||||||
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
|
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
|
||||||
<Link>Dlna\MediaFormatProfileResolver.cs</Link>
|
<Link>Dlna\MediaFormatProfileResolver.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="..\MediaBrowser.Model\Dlna\PlaybackErrorCode.cs">
|
||||||
|
<Link>Dlna\PlaybackErrorCode.cs</Link>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="..\MediaBrowser.Model\Dlna\PlaybackException.cs">
|
||||||
|
<Link>Dlna\PlaybackException.cs</Link>
|
||||||
|
</Compile>
|
||||||
<Compile Include="..\MediaBrowser.Model\Dlna\ProfileCondition.cs">
|
<Compile Include="..\MediaBrowser.Model\Dlna\ProfileCondition.cs">
|
||||||
<Link>Dlna\ProfileCondition.cs</Link>
|
<Link>Dlna\ProfileCondition.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
@ -13,11 +13,17 @@ namespace MediaBrowser.Model.ApiClient
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if [report capabilities]; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if [report capabilities]; otherwise, <c>false</c>.</value>
|
||||||
public bool ReportCapabilities { get; set; }
|
public bool ReportCapabilities { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether [update date last accessed].
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if [update date last accessed]; otherwise, <c>false</c>.</value>
|
||||||
|
public bool UpdateDateLastAccessed { get; set; }
|
||||||
|
|
||||||
public ConnectionOptions()
|
public ConnectionOptions()
|
||||||
{
|
{
|
||||||
EnableWebSocket = true;
|
EnableWebSocket = true;
|
||||||
ReportCapabilities = true;
|
ReportCapabilities = true;
|
||||||
|
UpdateDateLastAccessed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,20 +33,12 @@ namespace MediaBrowser.Model.Configuration
|
|||||||
public bool DisplayMissingEpisodes { get; set; }
|
public bool DisplayMissingEpisodes { get; set; }
|
||||||
public bool DisplayUnairedEpisodes { get; set; }
|
public bool DisplayUnairedEpisodes { get; set; }
|
||||||
|
|
||||||
public bool EnableLiveTvManagement { get; set; }
|
|
||||||
public bool EnableLiveTvAccess { get; set; }
|
|
||||||
|
|
||||||
public bool EnableMediaPlayback { get; set; }
|
|
||||||
public bool EnableContentDeletion { get; set; }
|
|
||||||
|
|
||||||
public bool GroupMoviesIntoBoxSets { get; set; }
|
public bool GroupMoviesIntoBoxSets { get; set; }
|
||||||
|
|
||||||
public string[] DisplayChannelsWithinViews { get; set; }
|
public string[] DisplayChannelsWithinViews { get; set; }
|
||||||
|
|
||||||
public string[] ExcludeFoldersFromGrouping { get; set; }
|
public string[] ExcludeFoldersFromGrouping { get; set; }
|
||||||
|
|
||||||
public UnratedItem[] BlockUnratedItems { get; set; }
|
|
||||||
|
|
||||||
public SubtitlePlaybackMode SubtitleMode { get; set; }
|
public SubtitlePlaybackMode SubtitleMode { get; set; }
|
||||||
public bool DisplayCollectionsView { get; set; }
|
public bool DisplayCollectionsView { get; set; }
|
||||||
public bool DisplayFoldersView { get; set; }
|
public bool DisplayFoldersView { get; set; }
|
||||||
@ -69,14 +61,10 @@ namespace MediaBrowser.Model.Configuration
|
|||||||
public UserConfiguration()
|
public UserConfiguration()
|
||||||
{
|
{
|
||||||
PlayDefaultAudioTrack = true;
|
PlayDefaultAudioTrack = true;
|
||||||
EnableLiveTvManagement = true;
|
|
||||||
EnableMediaPlayback = true;
|
|
||||||
EnableLiveTvAccess = true;
|
|
||||||
|
|
||||||
LatestItemsExcludes = new string[] { };
|
LatestItemsExcludes = new string[] { };
|
||||||
OrderedViews = new string[] { };
|
OrderedViews = new string[] { };
|
||||||
DisplayChannelsWithinViews = new string[] { };
|
DisplayChannelsWithinViews = new string[] { };
|
||||||
BlockUnratedItems = new UnratedItem[] { };
|
|
||||||
|
|
||||||
ExcludeFoldersFromGrouping = new string[] { };
|
ExcludeFoldersFromGrouping = new string[] { };
|
||||||
DisplayCollectionsView = true;
|
DisplayCollectionsView = true;
|
||||||
|
10
MediaBrowser.Model/Dlna/PlaybackErrorCode.cs
Normal file
10
MediaBrowser.Model/Dlna/PlaybackErrorCode.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
namespace MediaBrowser.Model.Dlna
|
||||||
|
{
|
||||||
|
public enum PlaybackErrorCode
|
||||||
|
{
|
||||||
|
NotAllowed = 0,
|
||||||
|
NoCompatibleStream = 1,
|
||||||
|
RateLimitExceeded = 2
|
||||||
|
}
|
||||||
|
}
|
9
MediaBrowser.Model/Dlna/PlaybackException.cs
Normal file
9
MediaBrowser.Model/Dlna/PlaybackException.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Model.Dlna
|
||||||
|
{
|
||||||
|
public class PlaybackException : Exception
|
||||||
|
{
|
||||||
|
public PlaybackErrorCode ErrorCode { get; set;}
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,13 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
List<StreamInfo> streams = new List<StreamInfo>();
|
List<StreamInfo> streams = new List<StreamInfo>();
|
||||||
foreach (MediaSourceInfo i in mediaSources)
|
foreach (MediaSourceInfo i in mediaSources)
|
||||||
streams.Add(BuildAudioItem(i, options));
|
{
|
||||||
|
StreamInfo streamInfo = BuildAudioItem(i, options);
|
||||||
|
if (streamInfo != null)
|
||||||
|
{
|
||||||
|
streams.Add(streamInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (StreamInfo stream in streams)
|
foreach (StreamInfo stream in streams)
|
||||||
{
|
{
|
||||||
@ -63,7 +69,13 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
List<StreamInfo> streams = new List<StreamInfo>();
|
List<StreamInfo> streams = new List<StreamInfo>();
|
||||||
foreach (MediaSourceInfo i in mediaSources)
|
foreach (MediaSourceInfo i in mediaSources)
|
||||||
streams.Add(BuildVideoItem(i, options));
|
{
|
||||||
|
StreamInfo streamInfo = BuildVideoItem(i, options);
|
||||||
|
if (streamInfo != null)
|
||||||
|
{
|
||||||
|
streams.Add(streamInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (StreamInfo stream in streams)
|
foreach (StreamInfo stream in streams)
|
||||||
{
|
{
|
||||||
@ -97,7 +109,10 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
{
|
{
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
PlaybackException error = new PlaybackException();
|
||||||
|
error.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
|
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
|
||||||
@ -186,6 +201,11 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
if (transcodingProfile != null)
|
if (transcodingProfile != null)
|
||||||
{
|
{
|
||||||
|
if (!item.SupportsTranscoding)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
playlistItem.PlayMethod = PlayMethod.Transcode;
|
playlistItem.PlayMethod = PlayMethod.Transcode;
|
||||||
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
||||||
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
|
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
|
||||||
@ -267,7 +287,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
if (subtitleStream != null)
|
if (subtitleStream != null)
|
||||||
{
|
{
|
||||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile);
|
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context);
|
||||||
|
|
||||||
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
|
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
|
||||||
playlistItem.SubtitleFormat = subtitleProfile.Format;
|
playlistItem.SubtitleFormat = subtitleProfile.Format;
|
||||||
@ -290,9 +310,14 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
if (transcodingProfile != null)
|
if (transcodingProfile != null)
|
||||||
{
|
{
|
||||||
|
if (!item.SupportsTranscoding)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (subtitleStream != null)
|
if (subtitleStream != null)
|
||||||
{
|
{
|
||||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile);
|
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context);
|
||||||
|
|
||||||
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
|
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
|
||||||
playlistItem.SubtitleFormat = subtitleProfile.Format;
|
playlistItem.SubtitleFormat = subtitleProfile.Format;
|
||||||
@ -527,7 +552,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
{
|
{
|
||||||
if (subtitleStream != null)
|
if (subtitleStream != null)
|
||||||
{
|
{
|
||||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile);
|
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context);
|
||||||
|
|
||||||
if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
|
if (subtitleProfile.Method != SubtitleDeliveryMethod.External && subtitleProfile.Method != SubtitleDeliveryMethod.Embed)
|
||||||
{
|
{
|
||||||
@ -538,14 +563,20 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
return IsAudioEligibleForDirectPlay(item, maxBitrate);
|
return IsAudioEligibleForDirectPlay(item, maxBitrate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, DeviceProfile deviceProfile)
|
public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, DeviceProfile deviceProfile, EncodingContext context)
|
||||||
{
|
{
|
||||||
// Look for an external profile that matches the stream type (text/graphical)
|
// Look for an external profile that matches the stream type (text/graphical)
|
||||||
foreach (SubtitleProfile profile in deviceProfile.SubtitleProfiles)
|
foreach (SubtitleProfile profile in deviceProfile.SubtitleProfiles)
|
||||||
{
|
{
|
||||||
if (subtitleStream.SupportsExternalStream)
|
if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
|
||||||
{
|
{
|
||||||
if (profile.Method == SubtitleDeliveryMethod.External && subtitleStream.IsTextSubtitleStream == MediaStream.IsTextFormat(profile.Format))
|
if (subtitleStream.SupportsExternalStream)
|
||||||
|
{
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For sync we can handle the longer extraction times
|
||||||
|
if (context == EncodingContext.Static && subtitleStream.IsTextSubtitleStream)
|
||||||
{
|
{
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
@ -262,7 +262,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
|
|
||||||
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream)
|
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream)
|
||||||
{
|
{
|
||||||
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile);
|
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile, Context);
|
||||||
|
|
||||||
if (subtitleProfile.Method != SubtitleDeliveryMethod.External)
|
if (subtitleProfile.Method != SubtitleDeliveryMethod.External)
|
||||||
{
|
{
|
||||||
|
@ -22,6 +22,7 @@ namespace MediaBrowser.Model.Dto
|
|||||||
|
|
||||||
public long? RunTimeTicks { get; set; }
|
public long? RunTimeTicks { get; set; }
|
||||||
public bool ReadAtNativeFramerate { get; set; }
|
public bool ReadAtNativeFramerate { get; set; }
|
||||||
|
public bool SupportsTranscoding { get; set; }
|
||||||
|
|
||||||
public VideoType? VideoType { get; set; }
|
public VideoType? VideoType { get; set; }
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ namespace MediaBrowser.Model.Dto
|
|||||||
MediaStreams = new List<MediaStream>();
|
MediaStreams = new List<MediaStream>();
|
||||||
RequiredHttpHeaders = new Dictionary<string, string>();
|
RequiredHttpHeaders = new Dictionary<string, string>();
|
||||||
PlayableStreamFileNames = new List<string>();
|
PlayableStreamFileNames = new List<string>();
|
||||||
|
SupportsTranscoding = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? DefaultAudioStreamIndex { get; set; }
|
public int? DefaultAudioStreamIndex { get; set; }
|
||||||
|
@ -125,6 +125,8 @@
|
|||||||
<Compile Include="Devices\DeviceInfo.cs" />
|
<Compile Include="Devices\DeviceInfo.cs" />
|
||||||
<Compile Include="Devices\DevicesOptions.cs" />
|
<Compile Include="Devices\DevicesOptions.cs" />
|
||||||
<Compile Include="Dlna\EncodingContext.cs" />
|
<Compile Include="Dlna\EncodingContext.cs" />
|
||||||
|
<Compile Include="Dlna\PlaybackErrorCode.cs" />
|
||||||
|
<Compile Include="Dlna\PlaybackException.cs" />
|
||||||
<Compile Include="Dlna\Profiles\DefaultProfile.cs" />
|
<Compile Include="Dlna\Profiles\DefaultProfile.cs" />
|
||||||
<Compile Include="Dlna\ResolutionConfiguration.cs" />
|
<Compile Include="Dlna\ResolutionConfiguration.cs" />
|
||||||
<Compile Include="Dlna\ResolutionNormalizer.cs" />
|
<Compile Include="Dlna\ResolutionNormalizer.cs" />
|
||||||
|
@ -19,6 +19,7 @@ namespace MediaBrowser.Model.Notifications
|
|||||||
NewLibraryContentMultiple,
|
NewLibraryContentMultiple,
|
||||||
ServerRestartRequired,
|
ServerRestartRequired,
|
||||||
TaskFailed,
|
TaskFailed,
|
||||||
CameraImageUploaded
|
CameraImageUploaded,
|
||||||
|
UserLockedOut
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -59,6 +59,8 @@ namespace MediaBrowser.Model.Users
|
|||||||
public string[] EnabledFolders { get; set; }
|
public string[] EnabledFolders { get; set; }
|
||||||
public bool EnableAllFolders { get; set; }
|
public bool EnableAllFolders { get; set; }
|
||||||
|
|
||||||
|
public int InvalidLoginAttemptCount { get; set; }
|
||||||
|
|
||||||
public UserPolicy()
|
public UserPolicy()
|
||||||
{
|
{
|
||||||
EnableLiveTvManagement = true;
|
EnableLiveTvManagement = true;
|
||||||
|
@ -397,7 +397,10 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
|
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
|
||||||
|
|
||||||
// Only one local provider allowed per item
|
// Only one local provider allowed per item
|
||||||
hasLocalMetadata = true;
|
if (IsFullLocalMetadata(localItem.Item))
|
||||||
|
{
|
||||||
|
hasLocalMetadata = true;
|
||||||
|
}
|
||||||
successfulProviderCount++;
|
successfulProviderCount++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -473,6 +476,11 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return refreshResult;
|
return refreshResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual bool IsFullLocalMetadata(TItemType item)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ImportUserData(TItemType item, List<UserItemData> userDataList, CancellationToken cancellationToken)
|
private async Task ImportUserData(TItemType item, List<UserItemData> userDataList, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var hasUserData = item as IHasUserData;
|
var hasUserData = item as IHasUserData;
|
||||||
|
@ -33,5 +33,22 @@ namespace MediaBrowser.Providers.Movies
|
|||||||
target.TmdbCollectionName = source.TmdbCollectionName;
|
target.TmdbCollectionName = source.TmdbCollectionName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool IsFullLocalMetadata(Movie item)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(item.Name))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(item.Overview))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!item.ProductionYear.HasValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return base.IsFullLocalMetadata(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,7 +146,7 @@ namespace MediaBrowser.Providers.Photos
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
|
var size = _imageProcessor.GetImageSize(imageInfo);
|
||||||
|
|
||||||
item.Width = Convert.ToInt32(size.Width);
|
item.Width = Convert.ToInt32(size.Width);
|
||||||
item.Height = Convert.ToInt32(size.Height);
|
item.Height = Convert.ToInt32(size.Height);
|
||||||
|
@ -74,5 +74,22 @@ namespace MediaBrowser.Providers.TV
|
|||||||
await provider.Run(item, CancellationToken.None).ConfigureAwait(false);
|
await provider.Run(item, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool IsFullLocalMetadata(Series item)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(item.Name))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(item.Overview))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!item.ProductionYear.HasValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return base.IsFullLocalMetadata(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@ -14,6 +15,11 @@ namespace MediaBrowser.Server.Implementations.Devices
|
|||||||
|
|
||||||
public override bool IsVisible(User user)
|
public override bool IsVisible(User user)
|
||||||
{
|
{
|
||||||
|
if (!user.Policy.EnableAllFolders && !user.Policy.EnabledFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return GetChildren(user, true).Any() &&
|
return GetChildren(user, true).Any() &&
|
||||||
base.IsVisible(user);
|
base.IsVisible(user);
|
||||||
}
|
}
|
||||||
|
@ -62,8 +62,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||||||
logger.Info("Failed to read image header for {0}. Doing it the slow way.", path);
|
logger.Info("Failed to read image header for {0}. Doing it the slow way.", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var wand = new MagickWand(path))
|
using (var wand = new MagickWand())
|
||||||
{
|
{
|
||||||
|
wand.PingImage(path);
|
||||||
var img = wand.CurrentImage;
|
var img = wand.CurrentImage;
|
||||||
|
|
||||||
return new ImageSize
|
return new ImageSize
|
||||||
|
@ -350,9 +350,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Increment this when indicator drawings change
|
/// Increment this when there's a change requiring caches to be invalidated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const string IndicatorVersion = "2";
|
private const string Version = "3";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cache file path based on a set of parameters
|
/// Gets the cache file path based on a set of parameters
|
||||||
@ -371,29 +371,19 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||||||
|
|
||||||
filename += "f=" + format;
|
filename += "f=" + format;
|
||||||
|
|
||||||
var hasIndicator = false;
|
|
||||||
|
|
||||||
if (addPlayedIndicator)
|
if (addPlayedIndicator)
|
||||||
{
|
{
|
||||||
filename += "pl=true";
|
filename += "pl=true";
|
||||||
hasIndicator = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (percentPlayed > 0)
|
if (percentPlayed > 0)
|
||||||
{
|
{
|
||||||
filename += "p=" + percentPlayed;
|
filename += "p=" + percentPlayed;
|
||||||
hasIndicator = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unwatchedCount.HasValue)
|
if (unwatchedCount.HasValue)
|
||||||
{
|
{
|
||||||
filename += "p=" + unwatchedCount.Value;
|
filename += "p=" + unwatchedCount.Value;
|
||||||
hasIndicator = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasIndicator)
|
|
||||||
{
|
|
||||||
filename += "iv=" + IndicatorVersion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(backgroundColor))
|
if (!string.IsNullOrEmpty(backgroundColor))
|
||||||
@ -401,6 +391,8 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||||||
filename += "b=" + backgroundColor;
|
filename += "b=" + backgroundColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filename += "v=" + Version;
|
||||||
|
|
||||||
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower());
|
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,6 +406,11 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||||||
return GetImageSize(path, File.GetLastWriteTimeUtc(path));
|
return GetImageSize(path, File.GetLastWriteTimeUtc(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImageSize GetImageSize(ItemImageInfo info)
|
||||||
|
{
|
||||||
|
return GetImageSize(info.Path, info.DateModified);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the size of the image.
|
/// Gets the size of the image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -421,7 +418,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||||||
/// <param name="imageDateModified">The image date modified.</param>
|
/// <param name="imageDateModified">The image date modified.</param>
|
||||||
/// <returns>ImageSize.</returns>
|
/// <returns>ImageSize.</returns>
|
||||||
/// <exception cref="System.ArgumentNullException">path</exception>
|
/// <exception cref="System.ArgumentNullException">path</exception>
|
||||||
public ImageSize GetImageSize(string path, DateTime imageDateModified)
|
private ImageSize GetImageSize(string path, DateTime imageDateModified)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
@ -666,30 +663,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||||||
return enhancedImagePath;
|
return enhancedImagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageFormat GetFormat(string path)
|
|
||||||
{
|
|
||||||
var extension = Path.GetExtension(path);
|
|
||||||
|
|
||||||
if (string.Equals(extension, ".png", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ImageFormat.Png;
|
|
||||||
}
|
|
||||||
if (string.Equals(extension, ".gif", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ImageFormat.Gif;
|
|
||||||
}
|
|
||||||
if (string.Equals(extension, ".webp", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ImageFormat.Webp;
|
|
||||||
}
|
|
||||||
if (string.Equals(extension, ".bmp", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ImageFormat.Bmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ImageFormat.Jpg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Executes the image enhancers.
|
/// Executes the image enhancers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -1598,14 +1598,11 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||||||
|
|
||||||
var path = imageInfo.Path;
|
var path = imageInfo.Path;
|
||||||
|
|
||||||
// See if we can avoid a file system lookup by looking for the file in ResolveArgs
|
|
||||||
var dateModified = imageInfo.DateModified;
|
|
||||||
|
|
||||||
ImageSize size;
|
ImageSize size;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
size = _imageProcessor.GetImageSize(path, dateModified);
|
size = _imageProcessor.GetImageSize(imageInfo);
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
|
@ -86,6 +86,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
|||||||
_userManager.UserPasswordChanged += _userManager_UserPasswordChanged;
|
_userManager.UserPasswordChanged += _userManager_UserPasswordChanged;
|
||||||
_userManager.UserDeleted += _userManager_UserDeleted;
|
_userManager.UserDeleted += _userManager_UserDeleted;
|
||||||
_userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated;
|
_userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated;
|
||||||
|
_userManager.UserLockedOut += _userManager_UserLockedOut;
|
||||||
|
|
||||||
//_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
//_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
||||||
//_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
|
//_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
|
||||||
@ -95,6 +96,16 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
|||||||
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
|
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
|
||||||
|
{
|
||||||
|
CreateLogEntry(new ActivityLogEntry
|
||||||
|
{
|
||||||
|
Name = string.Format(_localization.GetLocalizedString("UserLockedOutWithName"), e.Argument.Name),
|
||||||
|
Type = "UserLockedOut",
|
||||||
|
UserId = e.Argument.Id.ToString("N")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
void _subManager_SubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
CreateLogEntry(new ActivityLogEntry
|
||||||
@ -482,6 +493,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
|||||||
_userManager.UserPasswordChanged -= _userManager_UserPasswordChanged;
|
_userManager.UserPasswordChanged -= _userManager_UserPasswordChanged;
|
||||||
_userManager.UserDeleted -= _userManager_UserDeleted;
|
_userManager.UserDeleted -= _userManager_UserDeleted;
|
||||||
_userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated;
|
_userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated;
|
||||||
|
_userManager.UserLockedOut -= _userManager_UserLockedOut;
|
||||||
|
|
||||||
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
||||||
_config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated;
|
_config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated;
|
||||||
|
@ -78,6 +78,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
|
|||||||
_appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
|
_appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
|
||||||
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
|
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
|
||||||
_deviceManager.CameraImageUploaded +=_deviceManager_CameraImageUploaded;
|
_deviceManager.CameraImageUploaded +=_deviceManager_CameraImageUploaded;
|
||||||
|
|
||||||
|
_userManager.UserLockedOut += _userManager_UserLockedOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
async void _userManager_UserLockedOut(object sender, GenericEventArgs<User> e)
|
||||||
|
{
|
||||||
|
var type = NotificationType.UserLockedOut.ToString();
|
||||||
|
|
||||||
|
var notification = new NotificationRequest
|
||||||
|
{
|
||||||
|
NotificationType = type
|
||||||
|
};
|
||||||
|
|
||||||
|
notification.Variables["UserName"] = e.Argument.Name;
|
||||||
|
|
||||||
|
await SendNotification(notification).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
||||||
@ -235,7 +251,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var notification = new NotificationRequest
|
var notification = new NotificationRequest
|
||||||
{
|
{
|
||||||
NotificationType = type
|
NotificationType = type
|
||||||
@ -471,6 +486,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
|
|||||||
_appHost.ApplicationUpdated -= _appHost_ApplicationUpdated;
|
_appHost.ApplicationUpdated -= _appHost_ApplicationUpdated;
|
||||||
|
|
||||||
_deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded;
|
_deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded;
|
||||||
|
_userManager.UserLockedOut -= _userManager_UserLockedOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeLibraryUpdateTimer()
|
private void DisposeLibraryUpdateTimer()
|
||||||
|
@ -461,10 +461,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||||||
{
|
{
|
||||||
return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest)
|
return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest)
|
||||||
{
|
{
|
||||||
Throttle = options.Throttle,
|
|
||||||
ThrottleLimit = options.ThrottleLimit,
|
|
||||||
MinThrottlePosition = options.MinThrottlePosition,
|
|
||||||
ThrottleCallback = options.ThrottleCallback,
|
|
||||||
OnComplete = options.OnComplete
|
OnComplete = options.OnComplete
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -480,10 +476,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||||||
|
|
||||||
return new StreamWriter(stream, contentType, _logger)
|
return new StreamWriter(stream, contentType, _logger)
|
||||||
{
|
{
|
||||||
Throttle = options.Throttle,
|
|
||||||
ThrottleLimit = options.ThrottleLimit,
|
|
||||||
MinThrottlePosition = options.MinThrottlePosition,
|
|
||||||
ThrottleCallback = options.ThrottleCallback,
|
|
||||||
OnComplete = options.OnComplete
|
OnComplete = options.OnComplete
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -24,10 +24,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||||||
private long RangeLength { get; set; }
|
private long RangeLength { get; set; }
|
||||||
private long TotalContentLength { get; set; }
|
private long TotalContentLength { get; set; }
|
||||||
|
|
||||||
public bool Throttle { get; set; }
|
|
||||||
public long ThrottleLimit { get; set; }
|
|
||||||
public long MinThrottlePosition;
|
|
||||||
public Func<long, long, long> ThrottleCallback { get; set; }
|
|
||||||
public Action OnComplete { get; set; }
|
public Action OnComplete { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -165,14 +161,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||||||
/// <param name="responseStream">The response stream.</param>
|
/// <param name="responseStream">The response stream.</param>
|
||||||
public void WriteTo(Stream responseStream)
|
public void WriteTo(Stream responseStream)
|
||||||
{
|
{
|
||||||
if (Throttle)
|
|
||||||
{
|
|
||||||
responseStream = new ThrottledStream(responseStream, ThrottleLimit)
|
|
||||||
{
|
|
||||||
MinThrottlePosition = MinThrottlePosition,
|
|
||||||
ThrottleCallback = ThrottleCallback
|
|
||||||
};
|
|
||||||
}
|
|
||||||
WriteToInternal(responseStream);
|
WriteToInternal(responseStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,10 +35,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||||||
get { return _options; }
|
get { return _options; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Throttle { get; set; }
|
|
||||||
public long ThrottleLimit { get; set; }
|
|
||||||
public long MinThrottlePosition;
|
|
||||||
public Func<long, long, long> ThrottleCallback { get; set; }
|
|
||||||
public Action OnComplete { get; set; }
|
public Action OnComplete { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -82,14 +78,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||||||
/// <param name="responseStream">The response stream.</param>
|
/// <param name="responseStream">The response stream.</param>
|
||||||
public void WriteTo(Stream responseStream)
|
public void WriteTo(Stream responseStream)
|
||||||
{
|
{
|
||||||
if (Throttle)
|
|
||||||
{
|
|
||||||
responseStream = new ThrottledStream(responseStream, ThrottleLimit)
|
|
||||||
{
|
|
||||||
MinThrottlePosition = MinThrottlePosition,
|
|
||||||
ThrottleCallback = ThrottleCallback
|
|
||||||
};
|
|
||||||
}
|
|
||||||
WriteToInternal(responseStream);
|
WriteToInternal(responseStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@ -47,5 +48,59 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<MediaStream> GetMediaStreams(string mediaSourceId)
|
||||||
|
{
|
||||||
|
var list = GetMediaStreams(new MediaStreamQuery
|
||||||
|
{
|
||||||
|
ItemId = new Guid(mediaSourceId)
|
||||||
|
});
|
||||||
|
|
||||||
|
return GetMediaStreamsForItem(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<MediaStream> GetMediaStreams(Guid itemId)
|
||||||
|
{
|
||||||
|
var list = GetMediaStreams(new MediaStreamQuery
|
||||||
|
{
|
||||||
|
ItemId = itemId
|
||||||
|
});
|
||||||
|
|
||||||
|
return GetMediaStreamsForItem(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<MediaStream> GetMediaStreamsForItem(IEnumerable<MediaStream> streams)
|
||||||
|
{
|
||||||
|
var list = streams.ToList();
|
||||||
|
|
||||||
|
var subtitleStreams = list
|
||||||
|
.Where(i => i.Type == MediaStreamType.Subtitle)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (subtitleStreams.Count > 0)
|
||||||
|
{
|
||||||
|
var videoStream = list.FirstOrDefault(i => i.Type == MediaStreamType.Video);
|
||||||
|
|
||||||
|
// This is abitrary but at some point it becomes too slow to extract subtitles on the fly
|
||||||
|
// We need to learn more about when this is the case vs. when it isn't
|
||||||
|
const int maxAllowedBitrateForExternalSubtitleStream = 10000000;
|
||||||
|
|
||||||
|
var videoBitrate = videoStream == null ? maxAllowedBitrateForExternalSubtitleStream : videoStream.BitRate ?? maxAllowedBitrateForExternalSubtitleStream;
|
||||||
|
|
||||||
|
foreach (var subStream in subtitleStreams)
|
||||||
|
{
|
||||||
|
var supportsExternalStream = StreamSupportsExternalStream(subStream);
|
||||||
|
|
||||||
|
if (supportsExternalStream && videoBitrate >= maxAllowedBitrateForExternalSubtitleStream)
|
||||||
|
{
|
||||||
|
supportsExternalStream = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
subStream.SupportsExternalStream = supportsExternalStream;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
using System.Collections.Generic;
|
using MediaBrowser.Common.IO;
|
||||||
using System.Linq;
|
|
||||||
using MediaBrowser.Common.IO;
|
|
||||||
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.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using MediaBrowser.Naming.Common;
|
using MediaBrowser.Naming.Common;
|
||||||
using MediaBrowser.Naming.IO;
|
using MediaBrowser.Naming.IO;
|
||||||
using MediaBrowser.Naming.TV;
|
using MediaBrowser.Naming.TV;
|
||||||
using MediaBrowser.Server.Implementations.Logging;
|
using MediaBrowser.Server.Implementations.Logging;
|
||||||
using EpisodeInfo = MediaBrowser.Controller.Providers.EpisodeInfo;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
|
||||||
{
|
{
|
||||||
|
@ -97,6 +97,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event EventHandler<GenericEventArgs<User>> UserUpdated;
|
public event EventHandler<GenericEventArgs<User>> UserUpdated;
|
||||||
public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
|
public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
|
||||||
|
public event EventHandler<GenericEventArgs<User>> UserLockedOut;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called when [user updated].
|
/// Called when [user updated].
|
||||||
@ -259,6 +260,11 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
|
user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
|
||||||
await UpdateUser(user).ConfigureAwait(false);
|
await UpdateUser(user).ConfigureAwait(false);
|
||||||
|
await UpdateInvalidLoginAttemptCount(user, 0).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await UpdateInvalidLoginAttemptCount(user, user.Policy.InvalidLoginAttemptCount + 1).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.Info("Authentication request for {0} {1}.", user.Name, (success ? "has succeeded" : "has been denied"));
|
_logger.Info("Authentication request for {0} {1}.", user.Name, (success ? "has succeeded" : "has been denied"));
|
||||||
@ -266,6 +272,38 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UpdateInvalidLoginAttemptCount(User user, int newValue)
|
||||||
|
{
|
||||||
|
if (user.Policy.InvalidLoginAttemptCount != newValue || newValue > 0)
|
||||||
|
{
|
||||||
|
user.Policy.InvalidLoginAttemptCount = newValue;
|
||||||
|
|
||||||
|
var maxCount = user.Policy.IsAdministrator ?
|
||||||
|
3 :
|
||||||
|
5;
|
||||||
|
|
||||||
|
var fireLockout = false;
|
||||||
|
|
||||||
|
if (newValue >= maxCount)
|
||||||
|
{
|
||||||
|
_logger.Debug("Disabling user {0} due to {1} unsuccessful login attempts.", user.Name, newValue.ToString(CultureInfo.InvariantCulture));
|
||||||
|
user.Policy.IsDisabled = true;
|
||||||
|
|
||||||
|
fireLockout = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (fireLockout)
|
||||||
|
{
|
||||||
|
if (UserLockedOut != null)
|
||||||
|
{
|
||||||
|
EventHelper.FireEventIfNotNull(UserLockedOut, this, new GenericEventArgs<User>(user), _logger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string GetPasswordHash(User user)
|
private string GetPasswordHash(User user)
|
||||||
{
|
{
|
||||||
return string.IsNullOrEmpty(user.Password)
|
return string.IsNullOrEmpty(user.Password)
|
||||||
@ -332,11 +370,6 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
if (!user.Configuration.HasMigratedToPolicy)
|
if (!user.Configuration.HasMigratedToPolicy)
|
||||||
{
|
{
|
||||||
user.Policy.BlockUnratedItems = user.Configuration.BlockUnratedItems;
|
|
||||||
user.Policy.EnableContentDeletion = user.Configuration.EnableContentDeletion;
|
|
||||||
user.Policy.EnableLiveTvAccess = user.Configuration.EnableLiveTvAccess;
|
|
||||||
user.Policy.EnableLiveTvManagement = user.Configuration.EnableLiveTvManagement;
|
|
||||||
user.Policy.EnableMediaPlayback = user.Configuration.EnableMediaPlayback;
|
|
||||||
user.Policy.IsAdministrator = user.Configuration.IsAdministrator;
|
user.Policy.IsAdministrator = user.Configuration.IsAdministrator;
|
||||||
|
|
||||||
await UpdateUserPolicy(user, user.Policy, false);
|
await UpdateUserPolicy(user, user.Policy, false);
|
||||||
@ -915,10 +948,6 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.Configuration.IsAdministrator = user.Policy.IsAdministrator;
|
user.Configuration.IsAdministrator = user.Policy.IsAdministrator;
|
||||||
user.Configuration.EnableLiveTvManagement = user.Policy.EnableLiveTvManagement;
|
|
||||||
user.Configuration.EnableLiveTvAccess = user.Policy.EnableLiveTvAccess;
|
|
||||||
user.Configuration.EnableMediaPlayback = user.Policy.EnableMediaPlayback;
|
|
||||||
user.Configuration.EnableContentDeletion = user.Policy.EnableContentDeletion;
|
|
||||||
|
|
||||||
await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false);
|
await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -442,7 +442,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private const string InternalVersionNumber = "3";
|
private const string InternalVersionNumber = "4";
|
||||||
|
|
||||||
public Guid GetInternalChannelId(string serviceName, string externalId)
|
public Guid GetInternalChannelId(string serviceName, string externalId)
|
||||||
{
|
{
|
||||||
|
@ -48,8 +48,10 @@
|
|||||||
"LabelDashboardSourcePathHelp": "If running the server from source, specify the path to the dashboard-ui folder. All web client files will be served from this location.",
|
"LabelDashboardSourcePathHelp": "If running the server from source, specify the path to the dashboard-ui folder. All web client files will be served from this location.",
|
||||||
"ButtonConvertMedia": "Convert media",
|
"ButtonConvertMedia": "Convert media",
|
||||||
"ButtonOrganize": "Organize",
|
"ButtonOrganize": "Organize",
|
||||||
|
"LabelPinCode": "Pin code:",
|
||||||
"ButtonOk": "Ok",
|
"ButtonOk": "Ok",
|
||||||
"ButtonCancel": "Cancel",
|
"ButtonCancel": "Cancel",
|
||||||
|
"ButtonExit": "Exit",
|
||||||
"ButtonNew": "New",
|
"ButtonNew": "New",
|
||||||
"HeaderTV": "TV",
|
"HeaderTV": "TV",
|
||||||
"HeaderAudio": "Audio",
|
"HeaderAudio": "Audio",
|
||||||
@ -57,6 +59,12 @@
|
|||||||
"HeaderPaths": "Paths",
|
"HeaderPaths": "Paths",
|
||||||
"CategorySync": "Sync",
|
"CategorySync": "Sync",
|
||||||
"HeaderEasyPinCode": "Easy Pin Code",
|
"HeaderEasyPinCode": "Easy Pin Code",
|
||||||
|
"HeaderGrownupsOnly": "Grown-ups Only!",
|
||||||
|
"DividerOr": "-- or --",
|
||||||
|
"HeaderToAccessPleaseEnterEasyPinCode": "To access, please enter your easy pin code",
|
||||||
|
"KidsModeAdultInstruction": "Click the lock icon in the bottom right to configure or leave kids mode. Your pin code will be required.",
|
||||||
|
"ButtonConfigurePinCode": "Configure pin code",
|
||||||
|
"HeaderAdultsReadHere": "Adults Read Here!",
|
||||||
"RegisterWithPayPal": "Register with PayPal",
|
"RegisterWithPayPal": "Register with PayPal",
|
||||||
"HeaderSyncRequiresSupporterMembership": "Sync Requires a Supporter Membership",
|
"HeaderSyncRequiresSupporterMembership": "Sync Requires a Supporter Membership",
|
||||||
"HeaderEnjoyDayTrial": "Enjoy a 14 Day Free Trial",
|
"HeaderEnjoyDayTrial": "Enjoy a 14 Day Free Trial",
|
||||||
@ -670,6 +678,7 @@
|
|||||||
"NotificationOptionNewLibraryContent": "New content added",
|
"NotificationOptionNewLibraryContent": "New content added",
|
||||||
"NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
|
"NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
|
||||||
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
|
||||||
|
"NotificationOptionUserLockedOut": "User locked out",
|
||||||
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
|
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
|
||||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||||
"LabelNotificationEnabled": "Enable this notification",
|
"LabelNotificationEnabled": "Enable this notification",
|
||||||
@ -1061,6 +1070,7 @@
|
|||||||
"OptionBox": "Box",
|
"OptionBox": "Box",
|
||||||
"OptionBoxRear": "Box rear",
|
"OptionBoxRear": "Box rear",
|
||||||
"OptionDisc": "Disc",
|
"OptionDisc": "Disc",
|
||||||
|
"OptionIcon": "Icon",
|
||||||
"OptionLogo": "Logo",
|
"OptionLogo": "Logo",
|
||||||
"OptionMenu": "Menu",
|
"OptionMenu": "Menu",
|
||||||
"OptionScreenshot": "Screenshot",
|
"OptionScreenshot": "Screenshot",
|
||||||
@ -1105,6 +1115,7 @@
|
|||||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||||
"LabelRunningTimeValue": "Running time: {0}",
|
"LabelRunningTimeValue": "Running time: {0}",
|
||||||
"LabelIpAddressValue": "Ip address: {0}",
|
"LabelIpAddressValue": "Ip address: {0}",
|
||||||
|
"UserLockedOutWithName": "User {0} has been locked out",
|
||||||
"UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
|
"UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
|
||||||
"UserCreatedWithName": "User {0} has been created",
|
"UserCreatedWithName": "User {0} has been created",
|
||||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||||
@ -1114,7 +1125,7 @@
|
|||||||
"MessageApplicationUpdated": "Media Browser Server has been updated",
|
"MessageApplicationUpdated": "Media Browser Server has been updated",
|
||||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||||
"UserStartedPlayingItemWithValues": "{0} has started playing {1}",
|
"UserStartedPlayingItemWithValues": "{0} has started playing {1}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
|
"UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
|
||||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||||
@ -1369,5 +1380,7 @@
|
|||||||
"TabJobs": "Jobs",
|
"TabJobs": "Jobs",
|
||||||
"TabSyncJobs": "Sync Jobs",
|
"TabSyncJobs": "Sync Jobs",
|
||||||
"LabelTagFilterMode": "Mode:",
|
"LabelTagFilterMode": "Mode:",
|
||||||
"LabelTagFilterAllowModeHelp": "If allowed tags are used as part of a deeply nested folder structure, content that is tagged will require parent folders to be tagged as well."
|
"LabelTagFilterAllowModeHelp": "If allowed tags are used as part of a deeply nested folder structure, content that is tagged will require parent folders to be tagged as well.",
|
||||||
|
"HeaderThisUserIsCurrentlyDisabled": "This user is currently disabled",
|
||||||
|
"MessageReenableUser": "See below to reenable"
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\packages\ImageMagickSharp.1.0.0.2\lib\net45\ImageMagickSharp.dll</HintPath>
|
<HintPath>..\packages\ImageMagickSharp.1.0.0.6\lib\net45\ImageMagickSharp.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MediaBrowser.Naming, Version=1.0.5509.27636, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="MediaBrowser.Naming, Version=1.0.5509.27636, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
@ -278,6 +278,7 @@
|
|||||||
<Compile Include="Sorting\CommunityRatingComparer.cs" />
|
<Compile Include="Sorting\CommunityRatingComparer.cs" />
|
||||||
<Compile Include="Sorting\CriticRatingComparer.cs" />
|
<Compile Include="Sorting\CriticRatingComparer.cs" />
|
||||||
<Compile Include="Sorting\DateCreatedComparer.cs" />
|
<Compile Include="Sorting\DateCreatedComparer.cs" />
|
||||||
|
<Compile Include="Sorting\DateLastMediaAddedComparer.cs" />
|
||||||
<Compile Include="Sorting\DatePlayedComparer.cs" />
|
<Compile Include="Sorting\DatePlayedComparer.cs" />
|
||||||
<Compile Include="Sorting\GameSystemComparer.cs" />
|
<Compile Include="Sorting\GameSystemComparer.cs" />
|
||||||
<Compile Include="Sorting\IsFavoriteOrLikeComparer.cs" />
|
<Compile Include="Sorting\IsFavoriteOrLikeComparer.cs" />
|
||||||
@ -303,8 +304,12 @@
|
|||||||
<Compile Include="Sorting\StudioComparer.cs" />
|
<Compile Include="Sorting\StudioComparer.cs" />
|
||||||
<Compile Include="Sorting\VideoBitRateComparer.cs" />
|
<Compile Include="Sorting\VideoBitRateComparer.cs" />
|
||||||
<Compile Include="Sync\AppSyncProvider.cs" />
|
<Compile Include="Sync\AppSyncProvider.cs" />
|
||||||
<Compile Include="Sync\CloudSyncProvider.cs" />
|
<Compile Include="Sync\FolderSync\FolderSyncDataProvider.cs" />
|
||||||
|
<Compile Include="Sync\FolderSync\FolderSyncProvider.cs" />
|
||||||
|
<Compile Include="Sync\CloudSyncProfile.cs" />
|
||||||
|
<Compile Include="Sync\IHasSyncProfile.cs" />
|
||||||
<Compile Include="Sync\MediaSync.cs" />
|
<Compile Include="Sync\MediaSync.cs" />
|
||||||
|
<Compile Include="Sync\MultiProviderSync.cs" />
|
||||||
<Compile Include="Sync\SyncRegistrationInfo.cs" />
|
<Compile Include="Sync\SyncRegistrationInfo.cs" />
|
||||||
<Compile Include="Sync\SyncConfig.cs" />
|
<Compile Include="Sync\SyncConfig.cs" />
|
||||||
<Compile Include="Sync\SyncJobProcessor.cs" />
|
<Compile Include="Sync\SyncJobProcessor.cs" />
|
||||||
|
@ -143,6 +143,13 @@ namespace MediaBrowser.Server.Implementations.Notifications
|
|||||||
Type = NotificationType.CameraImageUploaded.ToString(),
|
Type = NotificationType.CameraImageUploaded.ToString(),
|
||||||
DefaultTitle = "A new camera image has been uploaded from {DeviceName}.",
|
DefaultTitle = "A new camera image has been uploaded from {DeviceName}.",
|
||||||
Variables = new List<string>{"DeviceName"}
|
Variables = new List<string>{"DeviceName"}
|
||||||
|
},
|
||||||
|
|
||||||
|
new NotificationTypeInfo
|
||||||
|
{
|
||||||
|
Type = NotificationType.UserLockedOut.ToString(),
|
||||||
|
DefaultTitle = "{UserName} has been locked out.",
|
||||||
|
Variables = new List<string>{"UserName"}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -185,6 +192,10 @@ namespace MediaBrowser.Server.Implementations.Notifications
|
|||||||
{
|
{
|
||||||
note.Category = _localization.GetLocalizedString("CategorySync");
|
note.Category = _localization.GetLocalizedString("CategorySync");
|
||||||
}
|
}
|
||||||
|
else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
note.Category = _localization.GetLocalizedString("CategoryUser");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
note.Category = _localization.GetLocalizedString("CategorySystem");
|
note.Category = _localization.GetLocalizedString("CategorySystem");
|
||||||
|
@ -108,7 +108,12 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||||||
|
|
||||||
protected Task<Stream> GetThumbCollage(List<BaseItem> items)
|
protected Task<Stream> GetThumbCollage(List<BaseItem> items)
|
||||||
{
|
{
|
||||||
return DynamicImageHelpers.GetThumbCollage(items.Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)).ToList(),
|
var files = items
|
||||||
|
.Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb))
|
||||||
|
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return DynamicImageHelpers.GetThumbCollage(files,
|
||||||
FileSystem,
|
FileSystem,
|
||||||
1600,
|
1600,
|
||||||
900,
|
900,
|
||||||
@ -117,7 +122,12 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||||||
|
|
||||||
protected Task<Stream> GetSquareCollage(List<BaseItem> items)
|
protected Task<Stream> GetSquareCollage(List<BaseItem> items)
|
||||||
{
|
{
|
||||||
return DynamicImageHelpers.GetSquareCollage(items.Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb)).ToList(),
|
var files = items
|
||||||
|
.Select(i => i.GetImagePath(ImageType.Primary) ?? i.GetImagePath(ImageType.Thumb))
|
||||||
|
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return DynamicImageHelpers.GetSquareCollage(files,
|
||||||
FileSystem,
|
FileSystem,
|
||||||
800, ApplicationPaths);
|
800, ApplicationPaths);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using MediaBrowser.Common.IO;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Photos
|
namespace MediaBrowser.Server.Implementations.Photos
|
||||||
@ -15,6 +16,11 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||||||
int width,
|
int width,
|
||||||
int height, IApplicationPaths appPaths)
|
int height, IApplicationPaths appPaths)
|
||||||
{
|
{
|
||||||
|
if (files.Any(string.IsNullOrWhiteSpace))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Empty file found in files list");
|
||||||
|
}
|
||||||
|
|
||||||
if (files.Count < 3)
|
if (files.Count < 3)
|
||||||
{
|
{
|
||||||
return await GetSingleImage(files, fileSystem).ConfigureAwait(false);
|
return await GetSingleImage(files, fileSystem).ConfigureAwait(false);
|
||||||
@ -27,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||||||
int cellHeight = height;
|
int cellHeight = height;
|
||||||
var index = 0;
|
var index = 0;
|
||||||
|
|
||||||
using (var wand = new MagickWand(width, height, "transparent"))
|
using (var wand = new MagickWand(width, height, new PixelWand(ColorName.None, 1)))
|
||||||
{
|
{
|
||||||
for (var row = 0; row < rows; row++)
|
for (var row = 0; row < rows; row++)
|
||||||
{
|
{
|
||||||
@ -57,6 +63,11 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
int size, IApplicationPaths appPaths)
|
int size, IApplicationPaths appPaths)
|
||||||
{
|
{
|
||||||
|
if (files.Any(string.IsNullOrWhiteSpace))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Empty file found in files list");
|
||||||
|
}
|
||||||
|
|
||||||
if (files.Count < 4)
|
if (files.Count < 4)
|
||||||
{
|
{
|
||||||
return await GetSingleImage(files, fileSystem).ConfigureAwait(false);
|
return await GetSingleImage(files, fileSystem).ConfigureAwait(false);
|
||||||
@ -68,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||||||
int singleSize = size / 2;
|
int singleSize = size / 2;
|
||||||
var index = 0;
|
var index = 0;
|
||||||
|
|
||||||
using (var wand = new MagickWand(size, size, "transparent"))
|
using (var wand = new MagickWand(size, size, new PixelWand(ColorName.None, 1)))
|
||||||
{
|
{
|
||||||
for (var row = 0; row < rows; row++)
|
for (var row = 0; row < rows; row++)
|
||||||
{
|
{
|
||||||
|
@ -399,7 +399,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
Client = clientType,
|
Client = clientType,
|
||||||
DeviceId = deviceId,
|
DeviceId = deviceId,
|
||||||
ApplicationVersion = appVersion,
|
ApplicationVersion = appVersion,
|
||||||
Id = Guid.NewGuid().ToString("N")
|
Id = key.GetMD5().ToString("N")
|
||||||
};
|
};
|
||||||
|
|
||||||
sessionInfo.DeviceName = deviceName;
|
sessionInfo.DeviceName = deviceName;
|
||||||
@ -798,6 +798,19 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SessionInfo GetSessionToRemoteControl(string sessionId)
|
||||||
|
{
|
||||||
|
// Accept either device id or session id
|
||||||
|
var session = Sessions.FirstOrDefault(i => string.Equals(i.Id, sessionId));
|
||||||
|
|
||||||
|
if (session == null)
|
||||||
|
{
|
||||||
|
throw new ResourceNotFoundException(string.Format("Session {0} not found.", sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken)
|
public Task SendMessageCommand(string controllingSessionId, string sessionId, MessageCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var generalCommand = new GeneralCommand
|
var generalCommand = new GeneralCommand
|
||||||
@ -818,7 +831,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
|
|
||||||
public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken)
|
public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var session = GetSession(sessionId);
|
var session = GetSessionToRemoteControl(sessionId);
|
||||||
|
|
||||||
var controllingSession = GetSession(controllingSessionId);
|
var controllingSession = GetSession(controllingSessionId);
|
||||||
AssertCanControl(session, controllingSession);
|
AssertCanControl(session, controllingSession);
|
||||||
@ -828,7 +841,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
|
|
||||||
public Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken)
|
public Task SendPlayCommand(string controllingSessionId, string sessionId, PlayRequest command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var session = GetSession(sessionId);
|
var session = GetSessionToRemoteControl(sessionId);
|
||||||
|
|
||||||
var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null;
|
var user = session.UserId.HasValue ? _userManager.GetUserById(session.UserId.Value) : null;
|
||||||
|
|
||||||
@ -955,7 +968,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
|
|
||||||
public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken)
|
public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var session = GetSession(sessionId);
|
var session = GetSessionToRemoteControl(sessionId);
|
||||||
|
|
||||||
var controllingSession = GetSession(controllingSessionId);
|
var controllingSession = GetSession(controllingSessionId);
|
||||||
AssertCanControl(session, controllingSession);
|
AssertCanControl(session, controllingSession);
|
||||||
@ -1566,11 +1579,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(mediaSourceId))
|
if (!string.IsNullOrWhiteSpace(mediaSourceId))
|
||||||
{
|
{
|
||||||
info.MediaStreams = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery
|
info.MediaStreams = _mediaSourceManager.GetMediaStreams(mediaSourceId).ToList();
|
||||||
{
|
|
||||||
ItemId = new Guid(mediaSourceId)
|
|
||||||
|
|
||||||
}).ToList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Sorting;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Implementations.Sorting
|
||||||
|
{
|
||||||
|
public class DateLastMediaAddedComparer : IUserBaseItemComparer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The user.</value>
|
||||||
|
public User User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The user manager.</value>
|
||||||
|
public IUserManager UserManager { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the user data repository.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The user data repository.</value>
|
||||||
|
public IUserDataManager UserDataRepository { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares the specified x.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The x.</param>
|
||||||
|
/// <param name="y">The y.</param>
|
||||||
|
/// <returns>System.Int32.</returns>
|
||||||
|
public int Compare(BaseItem x, BaseItem y)
|
||||||
|
{
|
||||||
|
return GetDate(x).CompareTo(GetDate(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the date.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="x">The x.</param>
|
||||||
|
/// <returns>DateTime.</returns>
|
||||||
|
private DateTime GetDate(BaseItem x)
|
||||||
|
{
|
||||||
|
var folder = x as Folder;
|
||||||
|
|
||||||
|
if (folder != null)
|
||||||
|
{
|
||||||
|
return folder.GetRecursiveChildren(User, i => !i.IsFolder)
|
||||||
|
.Select(i => i.DateCreated)
|
||||||
|
.OrderByDescending(i => i)
|
||||||
|
.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
return x.DateCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the name.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The name.</value>
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return ItemSortBy.DateLastContentAdded; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Persistence;
|
|
||||||
using MediaBrowser.Controller.Sorting;
|
using MediaBrowser.Controller.Sorting;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using System;
|
using System;
|
||||||
|
@ -8,7 +8,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Sync
|
namespace MediaBrowser.Server.Implementations.Sync
|
||||||
{
|
{
|
||||||
public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds
|
public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds, IHasSyncProfile
|
||||||
{
|
{
|
||||||
private readonly IDeviceManager _deviceManager;
|
private readonly IDeviceManager _deviceManager;
|
||||||
|
|
||||||
@ -42,5 +42,18 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
{
|
{
|
||||||
get { return "App Sync"; }
|
get { return "App Sync"; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<SyncTarget> GetAllSyncTargets()
|
||||||
|
{
|
||||||
|
return _deviceManager.GetDevices(new DeviceQuery
|
||||||
|
{
|
||||||
|
SupportsSync = true
|
||||||
|
|
||||||
|
}).Items.Select(i => new SyncTarget
|
||||||
|
{
|
||||||
|
Id = i.Id,
|
||||||
|
Name = i.Name
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
118
MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs
Normal file
118
MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Implementations.Sync
|
||||||
|
{
|
||||||
|
public class CloudSyncProfile : DeviceProfile
|
||||||
|
{
|
||||||
|
public CloudSyncProfile(bool supportsAc3, bool supportsDca)
|
||||||
|
{
|
||||||
|
Name = "Cloud Sync";
|
||||||
|
|
||||||
|
MaxStreamingBitrate = 20000000;
|
||||||
|
MaxStaticBitrate = 20000000;
|
||||||
|
|
||||||
|
var mkvAudio = "aac,mp3";
|
||||||
|
var mp4Audio = "aac";
|
||||||
|
|
||||||
|
if (supportsAc3)
|
||||||
|
{
|
||||||
|
mkvAudio += ",ac3";
|
||||||
|
mp4Audio += ",ac3";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsDca)
|
||||||
|
{
|
||||||
|
mkvAudio += ",dca";
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectPlayProfiles = new[]
|
||||||
|
{
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "mkv",
|
||||||
|
VideoCodec = "h264,mpeg4",
|
||||||
|
AudioCodec = mkvAudio,
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
},
|
||||||
|
new DirectPlayProfile
|
||||||
|
{
|
||||||
|
Container = "mp4,mov,m4v",
|
||||||
|
VideoCodec = "h264,mpeg4",
|
||||||
|
AudioCodec = mp4Audio,
|
||||||
|
Type = DlnaProfileType.Video
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ContainerProfiles = new ContainerProfile[] { };
|
||||||
|
|
||||||
|
CodecProfiles = new[]
|
||||||
|
{
|
||||||
|
new CodecProfile
|
||||||
|
{
|
||||||
|
Type = CodecType.Video,
|
||||||
|
Conditions = new []
|
||||||
|
{
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.VideoBitDepth,
|
||||||
|
Value = "8",
|
||||||
|
IsRequired = false
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.Height,
|
||||||
|
Value = "1080",
|
||||||
|
IsRequired = false
|
||||||
|
},
|
||||||
|
new ProfileCondition
|
||||||
|
{
|
||||||
|
Condition = ProfileConditionType.LessThanEqual,
|
||||||
|
Property = ProfileConditionValue.RefFrames,
|
||||||
|
Value = "12",
|
||||||
|
IsRequired = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SubtitleProfiles = new[]
|
||||||
|
{
|
||||||
|
new SubtitleProfile
|
||||||
|
{
|
||||||
|
Format = "srt",
|
||||||
|
Method = SubtitleDeliveryMethod.External
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TranscodingProfiles = new[]
|
||||||
|
{
|
||||||
|
new TranscodingProfile
|
||||||
|
{
|
||||||
|
Container = "mp3",
|
||||||
|
AudioCodec = "mp3",
|
||||||
|
Type = DlnaProfileType.Audio,
|
||||||
|
Context = EncodingContext.Static
|
||||||
|
},
|
||||||
|
|
||||||
|
new TranscodingProfile
|
||||||
|
{
|
||||||
|
Container = "mp4",
|
||||||
|
Type = DlnaProfileType.Video,
|
||||||
|
AudioCodec = "aac",
|
||||||
|
VideoCodec = "h264",
|
||||||
|
Context = EncodingContext.Static
|
||||||
|
},
|
||||||
|
|
||||||
|
new TranscodingProfile
|
||||||
|
{
|
||||||
|
Container = "jpeg",
|
||||||
|
Type = DlnaProfileType.Photo,
|
||||||
|
Context = EncodingContext.Static
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,59 +0,0 @@
|
|||||||
using MediaBrowser.Common;
|
|
||||||
using MediaBrowser.Controller.Sync;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
using MediaBrowser.Model.Sync;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Sync
|
|
||||||
{
|
|
||||||
public class CloudSyncProvider : IServerSyncProvider
|
|
||||||
{
|
|
||||||
private readonly ICloudSyncProvider[] _providers = {};
|
|
||||||
|
|
||||||
public CloudSyncProvider(IApplicationHost appHost)
|
|
||||||
{
|
|
||||||
_providers = appHost.GetExports<ICloudSyncProvider>().ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<SyncTarget> GetSyncTargets(string userId)
|
|
||||||
{
|
|
||||||
return _providers.SelectMany(i => i.GetSyncTargets(userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeviceProfile GetDeviceProfile(SyncTarget target)
|
|
||||||
{
|
|
||||||
return new DeviceProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get { return "Cloud Sync"; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private ICloudSyncProvider GetProvider(SyncTarget target)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<List<string>> GetServerItemIds(string serverId, SyncTarget target, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task DeleteItem(string serverId, string itemId, SyncTarget target, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task TransferItemFile(string serverId, string itemId, string inputFile, string[] pathParts, SyncTarget target, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var provider = GetProvider(target);
|
|
||||||
|
|
||||||
return provider.TransferItemFile(serverId, itemId, inputFile, pathParts, target, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,31 @@
|
|||||||
|
using MediaBrowser.Controller.Sync;
|
||||||
|
using MediaBrowser.Model.Sync;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Implementations.Sync.FolderSync
|
||||||
|
{
|
||||||
|
public class FolderSyncDataProvider : ISyncDataProvider
|
||||||
|
{
|
||||||
|
public Task<List<string>> GetServerItemIds(SyncTarget target, string serverId)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task AddOrUpdate(SyncTarget target, LocalItem item)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Delete(SyncTarget target, string id)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<LocalItem> Get(SyncTarget target, string id)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.Sync;
|
||||||
|
using MediaBrowser.Model.Sync;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Implementations.Sync.FolderSync
|
||||||
|
{
|
||||||
|
public class FolderSyncProvider : IServerSyncProvider
|
||||||
|
{
|
||||||
|
private readonly IApplicationPaths _appPaths;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
|
||||||
|
public FolderSyncProvider(IApplicationPaths appPaths, IUserManager userManager)
|
||||||
|
{
|
||||||
|
_appPaths = appPaths;
|
||||||
|
_userManager = userManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task SendFile(string inputFile, string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.Run(() => File.Copy(inputFile, path, true), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task DeleteFile(string path, SyncTarget target, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.Run(() => File.Delete(path), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Stream> GetFile(string path, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.FromResult((Stream)File.OpenRead(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFullPath(IEnumerable<string> paths, SyncTarget target)
|
||||||
|
{
|
||||||
|
var account = GetSyncAccounts()
|
||||||
|
.FirstOrDefault(i => string.Equals(i.Id, target.Id, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (account == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid SyncTarget supplied.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var list = paths.ToList();
|
||||||
|
list.Insert(0, account.Path);
|
||||||
|
|
||||||
|
return Path.Combine(list.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetParentDirectoryPath(string path, SyncTarget target)
|
||||||
|
{
|
||||||
|
return Path.GetDirectoryName(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<DeviceFileInfo>> GetFileSystemEntries(string path, SyncTarget target)
|
||||||
|
{
|
||||||
|
List<FileInfo> files;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
files = new DirectoryInfo(path).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList();
|
||||||
|
}
|
||||||
|
catch (DirectoryNotFoundException)
|
||||||
|
{
|
||||||
|
files = new List<FileInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.FromResult(files.Select(i => new DeviceFileInfo
|
||||||
|
{
|
||||||
|
Name = i.Name,
|
||||||
|
Path = i.FullName
|
||||||
|
|
||||||
|
}).ToList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ISyncDataProvider GetDataProvider()
|
||||||
|
{
|
||||||
|
// If single instances are needed, manage them here
|
||||||
|
return new FolderSyncDataProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name
|
||||||
|
{
|
||||||
|
get { return "Folder Sync"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<SyncTarget> GetSyncTargets(string userId)
|
||||||
|
{
|
||||||
|
return GetSyncAccounts()
|
||||||
|
.Where(i => i.UserIds.Contains(userId, StringComparer.OrdinalIgnoreCase))
|
||||||
|
.Select(GetSyncTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<SyncTarget> GetAllSyncTargets()
|
||||||
|
{
|
||||||
|
return GetSyncAccounts().Select(GetSyncTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SyncTarget GetSyncTarget(SyncAccount account)
|
||||||
|
{
|
||||||
|
return new SyncTarget
|
||||||
|
{
|
||||||
|
Id = account.Id,
|
||||||
|
Name = account.Name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<SyncAccount> GetSyncAccounts()
|
||||||
|
{
|
||||||
|
return new List<SyncAccount>();
|
||||||
|
// Dummy this up
|
||||||
|
return _userManager
|
||||||
|
.Users
|
||||||
|
.Select(i => new SyncAccount
|
||||||
|
{
|
||||||
|
Id = i.Id.ToString("N"),
|
||||||
|
UserIds = new List<string> { i.Id.ToString("N") },
|
||||||
|
Path = Path.Combine(_appPaths.DataPath, "foldersync", i.Id.ToString("N")),
|
||||||
|
Name = i.Name + "'s Folder Sync"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// An internal class to manage all configured Folder Sync accounts for differnet users
|
||||||
|
class SyncAccount
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
public List<string> UserIds { get; set; }
|
||||||
|
|
||||||
|
public SyncAccount()
|
||||||
|
{
|
||||||
|
UserIds = new List<string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs
Normal file
15
MediaBrowser.Server.Implementations/Sync/IHasSyncProfile.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Sync;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Implementations.Sync
|
||||||
|
{
|
||||||
|
public interface IHasSyncProfile
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the device profile.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="target">The target.</param>
|
||||||
|
/// <returns>DeviceProfile.</returns>
|
||||||
|
DeviceProfile GetDeviceProfile(SyncTarget target);
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,18 @@
|
|||||||
using MediaBrowser.Common.Progress;
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Common.Progress;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Sync;
|
using MediaBrowser.Controller.Sync;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.Sync;
|
using MediaBrowser.Model.Sync;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -15,22 +23,25 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
private readonly ISyncManager _syncManager;
|
private readonly ISyncManager _syncManager;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost)
|
public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost, IFileSystem fileSystem)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_syncManager = syncManager;
|
_syncManager = syncManager;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Sync(IServerSyncProvider provider,
|
public async Task Sync(IServerSyncProvider provider,
|
||||||
|
ISyncDataProvider dataProvider,
|
||||||
SyncTarget target,
|
SyncTarget target,
|
||||||
IProgress<double> progress,
|
IProgress<double> progress,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var serverId = _appHost.SystemId;
|
var serverId = _appHost.SystemId;
|
||||||
|
|
||||||
await SyncData(provider, serverId, target, cancellationToken).ConfigureAwait(false);
|
await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false);
|
||||||
progress.Report(3);
|
progress.Report(3);
|
||||||
|
|
||||||
var innerProgress = new ActionableProgress<double>();
|
var innerProgress = new ActionableProgress<double>();
|
||||||
@ -40,20 +51,21 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
totalProgress += 1;
|
totalProgress += 1;
|
||||||
progress.Report(totalProgress);
|
progress.Report(totalProgress);
|
||||||
});
|
});
|
||||||
await GetNewMedia(provider, target, serverId, innerProgress, cancellationToken);
|
await GetNewMedia(provider, dataProvider, target, serverId, innerProgress, cancellationToken);
|
||||||
|
|
||||||
// Do the data sync twice so the server knows what was removed from the device
|
// Do the data sync twice so the server knows what was removed from the device
|
||||||
await SyncData(provider, serverId, target, cancellationToken).ConfigureAwait(false);
|
await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SyncData(IServerSyncProvider provider,
|
private async Task SyncData(IServerSyncProvider provider,
|
||||||
|
ISyncDataProvider dataProvider,
|
||||||
string serverId,
|
string serverId,
|
||||||
SyncTarget target,
|
SyncTarget target,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var localIds = await provider.GetServerItemIds(serverId, target, cancellationToken).ConfigureAwait(false);
|
var localIds = await dataProvider.GetServerItemIds(target, serverId).ConfigureAwait(false);
|
||||||
|
|
||||||
var result = await _syncManager.SyncData(new SyncDataRequest
|
var result = await _syncManager.SyncData(new SyncDataRequest
|
||||||
{
|
{
|
||||||
@ -68,22 +80,23 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await RemoveItem(provider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false);
|
await RemoveItem(provider, dataProvider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.ErrorException("Error deleting item from sync target. Id: {0}", ex, itemIdToRemove);
|
_logger.ErrorException("Error deleting item from device. Id: {0}", ex, itemIdToRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetNewMedia(IServerSyncProvider provider,
|
private async Task GetNewMedia(IServerSyncProvider provider,
|
||||||
|
ISyncDataProvider dataProvider,
|
||||||
SyncTarget target,
|
SyncTarget target,
|
||||||
string serverId,
|
string serverId,
|
||||||
IProgress<double> progress,
|
IProgress<double> progress,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false);
|
var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false);
|
||||||
|
|
||||||
var numComplete = 0;
|
var numComplete = 0;
|
||||||
double startingPercent = 0;
|
double startingPercent = 0;
|
||||||
@ -106,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
progress.Report(totalProgress);
|
progress.Report(totalProgress);
|
||||||
});
|
});
|
||||||
|
|
||||||
await GetItem(provider, target, serverId, jobItem, innerProgress, cancellationToken).ConfigureAwait(false);
|
await GetItem(provider, dataProvider, target, serverId, jobItem, innerProgress, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
numComplete++;
|
numComplete++;
|
||||||
startingPercent = numComplete;
|
startingPercent = numComplete;
|
||||||
@ -117,6 +130,7 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetItem(IServerSyncProvider provider,
|
private async Task GetItem(IServerSyncProvider provider,
|
||||||
|
ISyncDataProvider dataProvider,
|
||||||
SyncTarget target,
|
SyncTarget target,
|
||||||
string serverId,
|
string serverId,
|
||||||
SyncedItem jobItem,
|
SyncedItem jobItem,
|
||||||
@ -129,6 +143,8 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
var fileTransferProgress = new ActionableProgress<double>();
|
var fileTransferProgress = new ActionableProgress<double>();
|
||||||
fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92));
|
fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92));
|
||||||
|
|
||||||
|
var localItem = CreateLocalItem(provider, target, libraryItem, serverId, jobItem.OriginalFileName);
|
||||||
|
|
||||||
await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id);
|
await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id);
|
||||||
|
|
||||||
var transferSuccess = false;
|
var transferSuccess = false;
|
||||||
@ -136,10 +152,10 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string[] pathParts = GetPathParts(serverId, libraryItem);
|
await SendFile(provider, internalSyncJobItem.OutputPath, localItem, target, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await provider.TransferItemFile(serverId, libraryItem.Id, internalSyncJobItem.OutputPath, pathParts, target, cancellationToken)
|
// Create db record
|
||||||
.ConfigureAwait(false);
|
await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false);
|
||||||
|
|
||||||
progress.Report(92);
|
progress.Report(92);
|
||||||
|
|
||||||
@ -165,18 +181,189 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task RemoveItem(IServerSyncProvider provider,
|
private async Task RemoveItem(IServerSyncProvider provider,
|
||||||
|
ISyncDataProvider dataProvider,
|
||||||
string serverId,
|
string serverId,
|
||||||
string itemId,
|
string itemId,
|
||||||
SyncTarget target,
|
SyncTarget target,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return provider.DeleteItem(serverId, itemId, target, cancellationToken);
|
var localId = GetLocalId(serverId, itemId);
|
||||||
|
var localItem = await dataProvider.Get(target, localId);
|
||||||
|
|
||||||
|
if (localItem == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var files = await GetFiles(provider, localItem, target);
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
await provider.DeleteFile(file.Path, target, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await dataProvider.Delete(target, localId).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string[] GetPathParts(string serverId, BaseItemDto item)
|
private Task SendFile(IServerSyncProvider provider, string inputPath, LocalItem item, SyncTarget target, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return null;
|
return provider.SendFile(inputPath, item.LocalPath, target, new Progress<double>(), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetLocalId(string serverId, string itemId)
|
||||||
|
{
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(serverId + itemId);
|
||||||
|
bytes = CreateMD5(bytes);
|
||||||
|
return BitConverter.ToString(bytes, 0, bytes.Length).Replace("-", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] CreateMD5(byte[] value)
|
||||||
|
{
|
||||||
|
using (var provider = MD5.Create())
|
||||||
|
{
|
||||||
|
return provider.ComputeHash(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncTarget target, BaseItemDto libraryItem, string serverId, string originalFileName)
|
||||||
|
{
|
||||||
|
var path = GetDirectoryPath(provider, libraryItem, serverId);
|
||||||
|
path.Add(GetLocalFileName(provider, libraryItem, originalFileName));
|
||||||
|
|
||||||
|
var localPath = provider.GetFullPath(path, target);
|
||||||
|
|
||||||
|
foreach (var mediaSource in libraryItem.MediaSources)
|
||||||
|
{
|
||||||
|
mediaSource.Path = localPath;
|
||||||
|
mediaSource.Protocol = MediaProtocol.File;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LocalItem
|
||||||
|
{
|
||||||
|
Item = libraryItem,
|
||||||
|
ItemId = libraryItem.Id,
|
||||||
|
ServerId = serverId,
|
||||||
|
LocalPath = localPath,
|
||||||
|
Id = GetLocalId(serverId, libraryItem.Id)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> GetDirectoryPath(IServerSyncProvider provider, BaseItemDto item, string serverId)
|
||||||
|
{
|
||||||
|
var parts = new List<string>
|
||||||
|
{
|
||||||
|
serverId
|
||||||
|
};
|
||||||
|
|
||||||
|
if (item.IsType("episode"))
|
||||||
|
{
|
||||||
|
parts.Add("TV");
|
||||||
|
parts.Add(item.SeriesName);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.SeasonName))
|
||||||
|
{
|
||||||
|
parts.Add(item.SeasonName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item.IsVideo)
|
||||||
|
{
|
||||||
|
parts.Add("Videos");
|
||||||
|
parts.Add(item.Name);
|
||||||
|
}
|
||||||
|
else if (item.IsAudio)
|
||||||
|
{
|
||||||
|
parts.Add("Music");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.AlbumArtist))
|
||||||
|
{
|
||||||
|
parts.Add(item.AlbumArtist);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.Album))
|
||||||
|
{
|
||||||
|
parts.Add(item.Album);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
parts.Add("Photos");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(item.Album))
|
||||||
|
{
|
||||||
|
parts.Add(item.Album);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.Select(i => GetValidFilename(provider, i)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetLocalFileName(IServerSyncProvider provider, BaseItemDto item, string originalFileName)
|
||||||
|
{
|
||||||
|
var filename = originalFileName;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
filename = item.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetValidFilename(provider, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetValidFilename(IServerSyncProvider provider, string filename)
|
||||||
|
{
|
||||||
|
// We can always add this method to the sync provider if it's really needed
|
||||||
|
return _fileSystem.GetValidFilename(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<ItemFileInfo>> GetFiles(IServerSyncProvider provider, LocalItem item, SyncTarget target)
|
||||||
|
{
|
||||||
|
var path = item.LocalPath;
|
||||||
|
path = provider.GetParentDirectoryPath(path, target);
|
||||||
|
|
||||||
|
var list = await provider.GetFileSystemEntries(path, target).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var itemFiles = new List<ItemFileInfo>();
|
||||||
|
|
||||||
|
var name = Path.GetFileNameWithoutExtension(item.LocalPath);
|
||||||
|
|
||||||
|
foreach (var file in list.Where(f => f.Name.Contains(name)))
|
||||||
|
{
|
||||||
|
var itemFile = new ItemFileInfo
|
||||||
|
{
|
||||||
|
Path = file.Path,
|
||||||
|
Name = file.Name
|
||||||
|
};
|
||||||
|
|
||||||
|
if (IsSubtitleFile(file.Name))
|
||||||
|
{
|
||||||
|
itemFile.Type = ItemFileType.Subtitles;
|
||||||
|
}
|
||||||
|
else if (!IsImageFile(file.Name))
|
||||||
|
{
|
||||||
|
itemFile.Type = ItemFileType.Media;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemFiles.Add(itemFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".webp" };
|
||||||
|
private bool IsImageFile(string path)
|
||||||
|
{
|
||||||
|
var ext = Path.GetExtension(path) ?? string.Empty;
|
||||||
|
|
||||||
|
return SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly string[] SupportedSubtitleExtensions = { ".srt", ".vtt" };
|
||||||
|
private bool IsSubtitleFile(string path)
|
||||||
|
{
|
||||||
|
var ext = Path.GetExtension(path) ?? string.Empty;
|
||||||
|
|
||||||
|
return SupportedSubtitleExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
using MediaBrowser.Common.IO;
|
||||||
|
using MediaBrowser.Common.Progress;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Sync;
|
||||||
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Model.Sync;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Implementations.Sync
|
||||||
|
{
|
||||||
|
public class MultiProviderSync
|
||||||
|
{
|
||||||
|
private readonly ISyncManager _syncManager;
|
||||||
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
|
||||||
|
public MultiProviderSync(ISyncManager syncManager, IServerApplicationHost appHost, ILogger logger, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
_syncManager = syncManager;
|
||||||
|
_appHost = appHost;
|
||||||
|
_logger = logger;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Sync(IEnumerable<IServerSyncProvider> providers, IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var targets = providers
|
||||||
|
.SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple<IServerSyncProvider, SyncTarget>(i, t)))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var numComplete = 0;
|
||||||
|
double startingPercent = 0;
|
||||||
|
double percentPerItem = 1;
|
||||||
|
if (targets.Count > 0)
|
||||||
|
{
|
||||||
|
percentPerItem /= targets.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var target in targets)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var currentPercent = startingPercent;
|
||||||
|
var innerProgress = new ActionableProgress<double>();
|
||||||
|
innerProgress.RegisterAction(pct =>
|
||||||
|
{
|
||||||
|
var totalProgress = pct * percentPerItem;
|
||||||
|
totalProgress += currentPercent;
|
||||||
|
progress.Report(totalProgress);
|
||||||
|
});
|
||||||
|
|
||||||
|
await new MediaSync(_logger, _syncManager, _appHost, _fileSystem)
|
||||||
|
.Sync(target.Item1, target.Item1.GetDataProvider(), target.Item2, innerProgress, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
numComplete++;
|
||||||
|
startingPercent = numComplete;
|
||||||
|
startingPercent /= targets.Count;
|
||||||
|
startingPercent *= 100;
|
||||||
|
progress.Report(startingPercent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -407,6 +407,15 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
.OrderBy(i => i.Name);
|
.OrderBy(i => i.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider)
|
||||||
|
{
|
||||||
|
return provider.GetAllSyncTargets().Select(i => new SyncTarget
|
||||||
|
{
|
||||||
|
Name = i.Name,
|
||||||
|
Id = GetSyncTargetId(provider, i)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider, string userId)
|
private IEnumerable<SyncTarget> GetSyncTargets(ISyncProvider provider, string userId)
|
||||||
{
|
{
|
||||||
return provider.GetSyncTargets(userId).Select(i => new SyncTarget
|
return provider.GetSyncTargets(userId).Select(i => new SyncTarget
|
||||||
@ -429,13 +438,6 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
return (providerId + "-" + target.Id).GetMD5().ToString("N");
|
return (providerId + "-" + target.Id).GetMD5().ToString("N");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ISyncProvider GetSyncProvider(SyncTarget target)
|
|
||||||
{
|
|
||||||
var providerId = target.Id.Split(new[] { '-' }, 2).First();
|
|
||||||
|
|
||||||
return _providers.First(i => string.Equals(providerId, GetSyncProviderId(i)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetSyncProviderId(ISyncProvider provider)
|
private string GetSyncProviderId(ISyncProvider provider)
|
||||||
{
|
{
|
||||||
return (provider.GetType().Name).GetMD5().ToString("N");
|
return (provider.GetType().Name).GetMD5().ToString("N");
|
||||||
@ -543,11 +545,11 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
{
|
{
|
||||||
foreach (var provider in _providers)
|
foreach (var provider in _providers)
|
||||||
{
|
{
|
||||||
foreach (var target in GetSyncTargets(provider, null))
|
foreach (var target in GetSyncTargets(provider))
|
||||||
{
|
{
|
||||||
if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(target.Id, targetId, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return provider.GetDeviceProfile(target);
|
return GetDeviceProfile(provider, target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -555,6 +557,18 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DeviceProfile GetDeviceProfile(ISyncProvider provider, SyncTarget target)
|
||||||
|
{
|
||||||
|
var hasProfile = provider as IHasSyncProfile;
|
||||||
|
|
||||||
|
if (hasProfile != null)
|
||||||
|
{
|
||||||
|
return hasProfile.GetDeviceProfile(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CloudSyncProfile(true, false);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task ReportSyncJobItemTransferred(string id)
|
public async Task ReportSyncJobItemTransferred(string id)
|
||||||
{
|
{
|
||||||
var jobItem = _repo.GetJobItem(id);
|
var jobItem = _repo.GetJobItem(id);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="ImageMagickSharp" version="1.0.0.2" targetFramework="net45" />
|
<package id="ImageMagickSharp" version="1.0.0.6" targetFramework="net45" />
|
||||||
<package id="MediaBrowser.Naming" version="1.0.0.32" targetFramework="net45" />
|
<package id="MediaBrowser.Naming" version="1.0.0.32" targetFramework="net45" />
|
||||||
<package id="Mono.Nat" version="1.2.21.0" targetFramework="net45" />
|
<package id="Mono.Nat" version="1.2.21.0" targetFramework="net45" />
|
||||||
<package id="morelinq" version="1.1.0" targetFramework="net45" />
|
<package id="morelinq" version="1.1.0" targetFramework="net45" />
|
||||||
|
25
MediaBrowser.Server.Mono/Diagnostics/LinuxProcessManager.cs
Normal file
25
MediaBrowser.Server.Mono/Diagnostics/LinuxProcessManager.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Mono.Diagnostics
|
||||||
|
{
|
||||||
|
public class LinuxProcessManager : IProcessManager
|
||||||
|
{
|
||||||
|
public bool SupportsSuspension
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SuspendProcess(Process process)
|
||||||
|
{
|
||||||
|
// http://jumptuck.com/2011/11/23/quick-tip-pause-process-linux/
|
||||||
|
process.StandardInput.WriteLine("^Z");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeProcess(Process process)
|
||||||
|
{
|
||||||
|
// http://jumptuck.com/2011/11/23/quick-tip-pause-process-linux/
|
||||||
|
process.StandardInput.WriteLine("fg");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -74,6 +74,7 @@
|
|||||||
<Compile Include="..\SharedVersion.cs">
|
<Compile Include="..\SharedVersion.cs">
|
||||||
<Link>Properties\SharedVersion.cs</Link>
|
<Link>Properties\SharedVersion.cs</Link>
|
||||||
</Compile>
|
</Compile>
|
||||||
|
<Compile Include="Diagnostics\LinuxProcessManager.cs" />
|
||||||
<Compile Include="Native\BaseMonoApp.cs" />
|
<Compile Include="Native\BaseMonoApp.cs" />
|
||||||
<Compile Include="Networking\CertificateGenerator.cs" />
|
<Compile Include="Networking\CertificateGenerator.cs" />
|
||||||
<Compile Include="Program.cs" />
|
<Compile Include="Program.cs" />
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.IsoMounter;
|
using MediaBrowser.IsoMounter;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Server.Mono.Diagnostics;
|
||||||
using MediaBrowser.Server.Mono.Networking;
|
using MediaBrowser.Server.Mono.Networking;
|
||||||
using MediaBrowser.Server.Startup.Common;
|
using MediaBrowser.Server.Startup.Common;
|
||||||
using Mono.Unix.Native;
|
using Mono.Unix.Native;
|
||||||
@ -189,5 +191,16 @@ namespace MediaBrowser.Server.Mono.Native
|
|||||||
public string sysname = string.Empty;
|
public string sysname = string.Empty;
|
||||||
public string machine = string.Empty;
|
public string machine = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public IProcessManager GetProcessManager()
|
||||||
|
{
|
||||||
|
if (Environment.OperatingSystem == Startup.Common.OperatingSystem.Linux)
|
||||||
|
{
|
||||||
|
return new LinuxProcessManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ProcessManager();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -380,6 +380,8 @@ namespace MediaBrowser.Server.Startup.Common
|
|||||||
|
|
||||||
RegisterSingleInstance(ServerConfigurationManager);
|
RegisterSingleInstance(ServerConfigurationManager);
|
||||||
|
|
||||||
|
RegisterSingleInstance(NativeApp.GetProcessManager());
|
||||||
|
|
||||||
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer);
|
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer);
|
||||||
RegisterSingleInstance(LocalizationManager);
|
RegisterSingleInstance(LocalizationManager);
|
||||||
|
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Server.Mono.Diagnostics
|
||||||
|
{
|
||||||
|
public class ProcessManager : IProcessManager
|
||||||
|
{
|
||||||
|
public void SuspendProcess(Process process)
|
||||||
|
{
|
||||||
|
process.PriorityClass = ProcessPriorityClass.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeProcess(Process process)
|
||||||
|
{
|
||||||
|
process.PriorityClass = ProcessPriorityClass.Normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SupportsSuspension
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
@ -84,5 +85,11 @@ namespace MediaBrowser.Server.Startup.Common
|
|||||||
/// Prevents the system stand by.
|
/// Prevents the system stand by.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void PreventSystemStandby();
|
void PreventSystemStandby();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the process manager.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>IProcessManager.</returns>
|
||||||
|
IProcessManager GetProcessManager();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
<Compile Include="ApplicationHost.cs" />
|
<Compile Include="ApplicationHost.cs" />
|
||||||
<Compile Include="ApplicationPathHelper.cs" />
|
<Compile Include="ApplicationPathHelper.cs" />
|
||||||
<Compile Include="Browser\BrowserLauncher.cs" />
|
<Compile Include="Browser\BrowserLauncher.cs" />
|
||||||
|
<Compile Include="Diagnostics\ProcessManager.cs" />
|
||||||
<Compile Include="EntryPoints\KeepServerAwake.cs" />
|
<Compile Include="EntryPoints\KeepServerAwake.cs" />
|
||||||
<Compile Include="EntryPoints\StartupWizard.cs" />
|
<Compile Include="EntryPoints\StartupWizard.cs" />
|
||||||
<Compile Include="FFMpeg\FFMpegDownloader.cs" />
|
<Compile Include="FFMpeg\FFMpegDownloader.cs" />
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
<Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||||
<SpecificVersion>False</SpecificVersion>
|
<SpecificVersion>False</SpecificVersion>
|
||||||
<HintPath>..\packages\ImageMagickSharp.1.0.0.2\lib\net45\ImageMagickSharp.dll</HintPath>
|
<HintPath>..\packages\ImageMagickSharp.1.0.0.6\lib\net45\ImageMagickSharp.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MediaBrowser.IsoMounter">
|
<Reference Include="MediaBrowser.IsoMounter">
|
||||||
<HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\MediaBrowser.IsoMounter.dll</HintPath>
|
<HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\MediaBrowser.IsoMounter.dll</HintPath>
|
||||||
@ -113,6 +113,7 @@
|
|||||||
<Compile Include="Native\Standby.cs" />
|
<Compile Include="Native\Standby.cs" />
|
||||||
<Compile Include="Native\ServerAuthorization.cs" />
|
<Compile Include="Native\ServerAuthorization.cs" />
|
||||||
<Compile Include="Native\WindowsApp.cs" />
|
<Compile Include="Native\WindowsApp.cs" />
|
||||||
|
<Compile Include="Native\WindowsProcessManager.cs" />
|
||||||
<Compile Include="Networking\CertificateGenerator.cs" />
|
<Compile Include="Networking\CertificateGenerator.cs" />
|
||||||
<Compile Include="Networking\NativeMethods.cs" />
|
<Compile Include="Networking\NativeMethods.cs" />
|
||||||
<Compile Include="Networking\NetworkManager.cs" />
|
<Compile Include="Networking\NetworkManager.cs" />
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
using MediaBrowser.Common.IO;
|
using MediaBrowser.Common.IO;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
using MediaBrowser.IsoMounter;
|
using MediaBrowser.IsoMounter;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
using MediaBrowser.Server.Mono.Diagnostics;
|
||||||
using MediaBrowser.Server.Startup.Common;
|
using MediaBrowser.Server.Startup.Common;
|
||||||
using MediaBrowser.ServerApplication.Networking;
|
using MediaBrowser.ServerApplication.Networking;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -109,5 +111,10 @@ namespace MediaBrowser.ServerApplication.Native
|
|||||||
{
|
{
|
||||||
Standby.PreventSystemStandby();
|
Standby.PreventSystemStandby();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IProcessManager GetProcessManager()
|
||||||
|
{
|
||||||
|
return new WindowsProcessManager();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,78 @@
|
|||||||
|
using MediaBrowser.Controller.Diagnostics;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace MediaBrowser.ServerApplication.Native
|
||||||
|
{
|
||||||
|
public class WindowsProcessManager : IProcessManager
|
||||||
|
{
|
||||||
|
public void SuspendProcess(Process process)
|
||||||
|
{
|
||||||
|
process.Suspend();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResumeProcess(Process process)
|
||||||
|
{
|
||||||
|
process.Resume();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SupportsSuspension
|
||||||
|
{
|
||||||
|
get { return true; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ProcessExtension
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
static extern uint SuspendThread(IntPtr hThread);
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
static extern int ResumeThread(IntPtr hThread);
|
||||||
|
|
||||||
|
public static void Suspend(this Process process)
|
||||||
|
{
|
||||||
|
foreach (ProcessThread thread in process.Threads)
|
||||||
|
{
|
||||||
|
var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
|
||||||
|
if (pOpenThread == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SuspendThread(pOpenThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void Resume(this Process process)
|
||||||
|
{
|
||||||
|
foreach (ProcessThread thread in process.Threads)
|
||||||
|
{
|
||||||
|
var pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
|
||||||
|
if (pOpenThread == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ResumeThread(pOpenThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void Print(this Process process)
|
||||||
|
{
|
||||||
|
Console.WriteLine("{0,8} {1}", process.Id, process.ProcessName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum ThreadAccess : int
|
||||||
|
{
|
||||||
|
TERMINATE = (0x0001),
|
||||||
|
SUSPEND_RESUME = (0x0002),
|
||||||
|
GET_CONTEXT = (0x0008),
|
||||||
|
SET_CONTEXT = (0x0010),
|
||||||
|
SET_INFORMATION = (0x0020),
|
||||||
|
QUERY_INFORMATION = (0x0040),
|
||||||
|
SET_THREAD_TOKEN = (0x0080),
|
||||||
|
IMPERSONATE = (0x0100),
|
||||||
|
DIRECT_IMPERSONATION = (0x0200)
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<packages>
|
<packages>
|
||||||
<package id="ImageMagickSharp" version="1.0.0.2" targetFramework="net45" />
|
<package id="ImageMagickSharp" version="1.0.0.6" targetFramework="net45" />
|
||||||
<package id="MediaBrowser.IsoMounting" version="3.0.69" targetFramework="net45" />
|
<package id="MediaBrowser.IsoMounting" version="3.0.69" targetFramework="net45" />
|
||||||
<package id="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" />
|
<package id="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" />
|
||||||
</packages>
|
</packages>
|
@ -421,6 +421,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||||||
"itembynamedetailpage.js",
|
"itembynamedetailpage.js",
|
||||||
"itemdetailpage.js",
|
"itemdetailpage.js",
|
||||||
"itemlistpage.js",
|
"itemlistpage.js",
|
||||||
|
"kids.js",
|
||||||
"librarypathmapping.js",
|
"librarypathmapping.js",
|
||||||
"reports.js",
|
"reports.js",
|
||||||
"librarysettings.js",
|
"librarysettings.js",
|
||||||
|
@ -87,6 +87,9 @@
|
|||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Content Include="dashboard-ui\css\images\kids\bg.jpg">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Include="dashboard-ui\css\images\server.png">
|
<Content Include="dashboard-ui\css\images\server.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
@ -114,6 +117,9 @@
|
|||||||
<Content Include="dashboard-ui\forgotpasswordpin.html">
|
<Content Include="dashboard-ui\forgotpasswordpin.html">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="dashboard-ui\kids.html">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Include="dashboard-ui\mysync.html">
|
<Content Include="dashboard-ui\mysync.html">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
@ -129,6 +135,9 @@
|
|||||||
<Content Include="dashboard-ui\scripts\forgotpasswordpin.js">
|
<Content Include="dashboard-ui\scripts\forgotpasswordpin.js">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="dashboard-ui\scripts\kids.js">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Include="dashboard-ui\scripts\selectserver.js">
|
<Content Include="dashboard-ui\scripts\selectserver.js">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
@ -214,7 +214,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
using (var filestream = FileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
{
|
{
|
||||||
stream.CopyTo(filestream);
|
stream.CopyTo(filestream);
|
||||||
}
|
}
|
||||||
|
@ -520,4 +520,7 @@ Global
|
|||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
GlobalSection(Performance) = preSolution
|
||||||
|
HasPerformanceSessions = true
|
||||||
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Common.Internal</id>
|
<id>MediaBrowser.Common.Internal</id>
|
||||||
<version>3.0.576</version>
|
<version>3.0.579</version>
|
||||||
<title>MediaBrowser.Common.Internal</title>
|
<title>MediaBrowser.Common.Internal</title>
|
||||||
<authors>Luke</authors>
|
<authors>Luke</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
|
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
|
||||||
<copyright>Copyright © Media Browser 2013</copyright>
|
<copyright>Copyright © Media Browser 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="MediaBrowser.Common" version="3.0.576" />
|
<dependency id="MediaBrowser.Common" version="3.0.579" />
|
||||||
<dependency id="NLog" version="3.2.0.0" />
|
<dependency id="NLog" version="3.2.0.0" />
|
||||||
<dependency id="SimpleInjector" version="2.7.0" />
|
<dependency id="SimpleInjector" version="2.7.0" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Common</id>
|
<id>MediaBrowser.Common</id>
|
||||||
<version>3.0.576</version>
|
<version>3.0.579</version>
|
||||||
<title>MediaBrowser.Common</title>
|
<title>MediaBrowser.Common</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Media Browser Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Model.Signed</id>
|
<id>MediaBrowser.Model.Signed</id>
|
||||||
<version>3.0.576</version>
|
<version>3.0.579</version>
|
||||||
<title>MediaBrowser.Model - Signed Edition</title>
|
<title>MediaBrowser.Model - Signed Edition</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Media Browser Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>MediaBrowser.Server.Core</id>
|
<id>MediaBrowser.Server.Core</id>
|
||||||
<version>3.0.576</version>
|
<version>3.0.579</version>
|
||||||
<title>Media Browser.Server.Core</title>
|
<title>Media Browser.Server.Core</title>
|
||||||
<authors>Media Browser Team</authors>
|
<authors>Media Browser Team</authors>
|
||||||
<owners>ebr,Luke,scottisafool</owners>
|
<owners>ebr,Luke,scottisafool</owners>
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<description>Contains core components required to build plugins for Media Browser Server.</description>
|
<description>Contains core components required to build plugins for Media Browser Server.</description>
|
||||||
<copyright>Copyright © Media Browser 2013</copyright>
|
<copyright>Copyright © Media Browser 2013</copyright>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency id="MediaBrowser.Common" version="3.0.576" />
|
<dependency id="MediaBrowser.Common" version="3.0.579" />
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user