mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-06 03:26:07 +02:00
commit
6c2e01830c
96 changed files with 1624 additions and 475 deletions
|
@ -318,7 +318,7 @@ namespace MediaBrowser.Api.Images
|
|||
|
||||
try
|
||||
{
|
||||
var size = _imageProcessor.GetImageSize(info.Path, info.DateModified);
|
||||
var size = _imageProcessor.GetImageSize(info);
|
||||
|
||||
width = Convert.ToInt32(size.Width);
|
||||
height = Convert.ToInt32(size.Height);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using MediaBrowser.Controller.Activity;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
|
@ -9,11 +8,9 @@ using MediaBrowser.Controller.Library;
|
|||
using MediaBrowser.Controller.Localization;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using ServiceStack;
|
||||
using System;
|
||||
|
|
|
@ -81,6 +81,7 @@
|
|||
<Compile Include="Library\ChapterService.cs" />
|
||||
<Compile Include="Playback\Hls\MpegDashService.cs" />
|
||||
<Compile Include="Playback\MediaInfoService.cs" />
|
||||
<Compile Include="Playback\TranscodingThrottler.cs" />
|
||||
<Compile Include="PlaylistService.cs" />
|
||||
<Compile Include="Reports\ReportFieldType.cs" />
|
||||
<Compile Include="Reports\ReportResult.cs" />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Diagnostics;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
|
@ -70,12 +71,14 @@ namespace MediaBrowser.Api.Playback
|
|||
protected IDeviceManager DeviceManager { get; private set; }
|
||||
protected IChannelManager ChannelManager { get; private set; }
|
||||
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
|
||||
protected IProcessManager ProcessManager { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
|
||||
/// </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;
|
||||
SubtitleEncoder = subtitleEncoder;
|
||||
ChannelManager = channelManager;
|
||||
|
@ -877,14 +880,6 @@ namespace MediaBrowser.Api.Playback
|
|||
return "copy";
|
||||
}
|
||||
|
||||
private bool SupportsThrottleWithStream
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the input argument.
|
||||
/// </summary>
|
||||
|
@ -908,23 +903,15 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
private string GetInputPathArgument(string transcodingJobId, StreamState state)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
//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)
|
||||
// {
|
||||
// }
|
||||
//}
|
||||
|
||||
var protocol = state.InputProtocol;
|
||||
|
||||
|
@ -1109,9 +1096,26 @@ namespace MediaBrowser.Api.Playback
|
|||
}
|
||||
}
|
||||
|
||||
StartThrottler(state, 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)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Diagnostics;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
|
@ -23,7 +24,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
/// </summary>
|
||||
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.Diagnostics;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
|
@ -63,7 +64,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
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;
|
||||
}
|
||||
|
@ -115,6 +116,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
if (File.Exists(segmentPath))
|
||||
{
|
||||
job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
|
||||
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
@ -123,6 +125,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
{
|
||||
if (File.Exists(segmentPath))
|
||||
{
|
||||
job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType);
|
||||
return await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
|
|||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Diagnostics;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
|
@ -51,7 +52,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ using MediaBrowser.Common.IO;
|
|||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Diagnostics;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
|
@ -57,7 +58,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
|||
/// </summary>
|
||||
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
|
||||
{
|
||||
[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>
|
||||
{
|
||||
[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.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Diagnostics;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -32,7 +33,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
/// </summary>
|
||||
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.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Diagnostics;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -29,7 +30,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
protected readonly IImageProcessor ImageProcessor;
|
||||
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;
|
||||
HttpClient = httpClient;
|
||||
|
@ -153,49 +154,12 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
|
||||
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
|
||||
{
|
||||
ResponseHeaders = responseHeaders,
|
||||
ContentType = contentType,
|
||||
IsHeadRequest = isHeadRequest,
|
||||
Path = state.MediaPath,
|
||||
Throttle = request.Throttle,
|
||||
|
||||
ThrottleLimit = bytesPerSecond,
|
||||
|
||||
MinThrottlePosition = targetGap,
|
||||
|
||||
ThrottleCallback = (l1, l2) => ThrottleCallack(l1, l2, bytesPerSecond, job)
|
||||
Path = state.MediaPath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
/// Gets the static remote stream result.
|
||||
/// </summary>
|
||||
|
|
|
@ -3,6 +3,7 @@ using MediaBrowser.Common.Net;
|
|||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Diagnostics;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Library;
|
||||
|
@ -63,7 +64,7 @@ namespace MediaBrowser.Api.Playback.Progressive
|
|||
/// </summary>
|
||||
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 ClientTime { get; set; }
|
||||
|
||||
public bool Throttle { 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.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Net;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using MediaBrowser.Model.Net;
|
||||
|
||||
namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
|
@ -23,6 +22,7 @@ namespace MediaBrowser.Api.Playback
|
|||
public string RequestedUrl { get; set; }
|
||||
|
||||
public StreamRequest Request { get; set; }
|
||||
public TranscodingThrottler TranscodingThrottler { get; set; }
|
||||
|
||||
public VideoStreamRequest VideoRequest
|
||||
{
|
||||
|
@ -125,6 +125,7 @@ namespace MediaBrowser.Api.Playback
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeTranscodingThrottler();
|
||||
DisposeLiveStream();
|
||||
DisposeLogStream();
|
||||
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()
|
||||
{
|
||||
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.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -30,10 +29,9 @@ namespace MediaBrowser.Controller.Drawing
|
|||
/// <summary>
|
||||
/// Gets the size of the image.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="imageDateModified">The image date modified.</param>
|
||||
/// <param name="info">The information.</param>
|
||||
/// <returns>ImageSize.</returns>
|
||||
ImageSize GetImageSize(string path, DateTime imageDateModified);
|
||||
ImageSize GetImageSize(ItemImageInfo info);
|
||||
|
||||
/// <summary>
|
||||
/// Adds the parts.
|
||||
|
|
|
@ -239,7 +239,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||
{
|
||||
Id = i.Id.ToString("N"),
|
||||
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,
|
||||
Path = enablePathSubstituion ? GetMappedPath(i.Path, locationType) : i.Path,
|
||||
RunTimeTicks = i.RunTimeTicks,
|
||||
|
|
|
@ -420,12 +420,17 @@ namespace MediaBrowser.Controller.Entities
|
|||
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()
|
||||
|
@ -474,7 +479,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
|
||||
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();
|
||||
|
||||
var locationType = i.LocationType;
|
||||
|
|
|
@ -1,11 +1,29 @@
|
|||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MediaBrowser.Controller.Library
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace MediaBrowser.Controller.Library
|
|||
event EventHandler<GenericEventArgs<User>> UserCreated;
|
||||
event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
|
||||
event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
|
||||
event EventHandler<GenericEventArgs<User>> UserLockedOut;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a User by Id
|
||||
|
|
|
@ -104,6 +104,7 @@
|
|||
<Compile Include="Devices\CameraImageUploadInfo.cs" />
|
||||
<Compile Include="Devices\IDeviceManager.cs" />
|
||||
<Compile Include="Devices\IDeviceRepository.cs" />
|
||||
<Compile Include="Diagnostics\IProcessManager.cs" />
|
||||
<Compile Include="Dlna\ControlRequest.cs" />
|
||||
<Compile Include="Dlna\ControlResponse.cs" />
|
||||
<Compile Include="Dlna\EventSubscriptionResponse.cs" />
|
||||
|
@ -341,8 +342,8 @@
|
|||
<Compile Include="Subtitles\SubtitleDownloadEventArgs.cs" />
|
||||
<Compile Include="Subtitles\SubtitleResponse.cs" />
|
||||
<Compile Include="Subtitles\SubtitleSearchRequest.cs" />
|
||||
<Compile Include="Sync\ICloudSyncProvider.cs" />
|
||||
<Compile Include="Sync\IServerSyncProvider.cs" />
|
||||
<Compile Include="Sync\ISyncDataProvider.cs" />
|
||||
<Compile Include="Sync\ISyncManager.cs" />
|
||||
<Compile Include="Sync\ISyncProvider.cs" />
|
||||
<Compile Include="Sync\ISyncRepository.cs" />
|
||||
|
|
|
@ -18,11 +18,6 @@ namespace MediaBrowser.Controller.Net
|
|||
|
||||
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 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -7,35 +9,64 @@ namespace MediaBrowser.Controller.Sync
|
|||
{
|
||||
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>
|
||||
/// Transfers the 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="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="cancellationToken">The cancellation token.</param>
|
||||
/// <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;
|
||||
|
||||
namespace MediaBrowser.Controller.Sync
|
||||
|
@ -18,13 +17,12 @@ namespace MediaBrowser.Controller.Sync
|
|||
/// <param name="userId">The user identifier.</param>
|
||||
/// <returns>IEnumerable<SyncTarget>.</returns>
|
||||
IEnumerable<SyncTarget> GetSyncTargets(string userId);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the device profile.
|
||||
/// Gets all synchronize targets.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <returns>DeviceProfile.</returns>
|
||||
DeviceProfile GetDeviceProfile(SyncTarget target);
|
||||
/// <returns>IEnumerable<SyncTarget>.</returns>
|
||||
IEnumerable<SyncTarget> GetAllSyncTargets();
|
||||
}
|
||||
|
||||
public interface IHasUniqueTargetIds
|
||||
|
|
|
@ -930,7 +930,7 @@ namespace MediaBrowser.Dlna.Didl
|
|||
|
||||
try
|
||||
{
|
||||
var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
|
||||
var size = _imageProcessor.GetImageSize(imageInfo);
|
||||
|
||||
width = Convert.ToInt32(size.Width);
|
||||
height = Convert.ToInt32(size.Height);
|
||||
|
|
|
@ -452,24 +452,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
|
||||
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 inputPath = new[] { job.MediaPath };
|
||||
|
|
|
@ -773,7 +773,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
{
|
||||
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();
|
||||
detector.Feed(file);
|
||||
|
@ -797,12 +797,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||
return null;
|
||||
}
|
||||
|
||||
private static Encoding GetFileEncoding(string srcFile)
|
||||
private Encoding GetFileEncoding(string srcFile)
|
||||
{
|
||||
// *** Detect byte order mark if any - otherwise assume default
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -362,6 +362,12 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
|
||||
<Link>Dlna\MediaFormatProfileResolver.cs</Link>
|
||||
</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">
|
||||
<Link>Dlna\ProfileCondition.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -327,6 +327,12 @@
|
|||
<Compile Include="..\MediaBrowser.Model\Dlna\MediaFormatProfileResolver.cs">
|
||||
<Link>Dlna\MediaFormatProfileResolver.cs</Link>
|
||||
</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">
|
||||
<Link>Dlna\ProfileCondition.cs</Link>
|
||||
</Compile>
|
||||
|
|
|
@ -13,11 +13,17 @@ namespace MediaBrowser.Model.ApiClient
|
|||
/// </summary>
|
||||
/// <value><c>true</c> if [report capabilities]; otherwise, <c>false</c>.</value>
|
||||
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()
|
||||
{
|
||||
EnableWebSocket = true;
|
||||
ReportCapabilities = true;
|
||||
UpdateDateLastAccessed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,20 +33,12 @@ namespace MediaBrowser.Model.Configuration
|
|||
public bool DisplayMissingEpisodes { 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 string[] DisplayChannelsWithinViews { get; set; }
|
||||
|
||||
public string[] ExcludeFoldersFromGrouping { get; set; }
|
||||
|
||||
public UnratedItem[] BlockUnratedItems { get; set; }
|
||||
|
||||
public SubtitlePlaybackMode SubtitleMode { get; set; }
|
||||
public bool DisplayCollectionsView { get; set; }
|
||||
public bool DisplayFoldersView { get; set; }
|
||||
|
@ -69,14 +61,10 @@ namespace MediaBrowser.Model.Configuration
|
|||
public UserConfiguration()
|
||||
{
|
||||
PlayDefaultAudioTrack = true;
|
||||
EnableLiveTvManagement = true;
|
||||
EnableMediaPlayback = true;
|
||||
EnableLiveTvAccess = true;
|
||||
|
||||
LatestItemsExcludes = new string[] { };
|
||||
OrderedViews = new string[] { };
|
||||
DisplayChannelsWithinViews = new string[] { };
|
||||
BlockUnratedItems = new UnratedItem[] { };
|
||||
|
||||
ExcludeFoldersFromGrouping = new string[] { };
|
||||
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>();
|
||||
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)
|
||||
{
|
||||
|
@ -63,7 +69,13 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
List<StreamInfo> streams = new List<StreamInfo>();
|
||||
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)
|
||||
{
|
||||
|
@ -97,7 +109,10 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
return stream;
|
||||
}
|
||||
return null;
|
||||
|
||||
PlaybackException error = new PlaybackException();
|
||||
error.ErrorCode = PlaybackErrorCode.NoCompatibleStream;
|
||||
throw error;
|
||||
}
|
||||
|
||||
private StreamInfo BuildAudioItem(MediaSourceInfo item, AudioOptions options)
|
||||
|
@ -186,6 +201,11 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
if (transcodingProfile != null)
|
||||
{
|
||||
if (!item.SupportsTranscoding)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
playlistItem.PlayMethod = PlayMethod.Transcode;
|
||||
playlistItem.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
|
||||
playlistItem.EstimateContentLength = transcodingProfile.EstimateContentLength;
|
||||
|
@ -267,7 +287,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
if (subtitleStream != null)
|
||||
{
|
||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile);
|
||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context);
|
||||
|
||||
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
|
||||
playlistItem.SubtitleFormat = subtitleProfile.Format;
|
||||
|
@ -290,9 +310,14 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
if (transcodingProfile != null)
|
||||
{
|
||||
if (!item.SupportsTranscoding)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (subtitleStream != null)
|
||||
{
|
||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile);
|
||||
SubtitleProfile subtitleProfile = GetSubtitleProfile(subtitleStream, options.Profile, options.Context);
|
||||
|
||||
playlistItem.SubtitleDeliveryMethod = subtitleProfile.Method;
|
||||
playlistItem.SubtitleFormat = subtitleProfile.Format;
|
||||
|
@ -527,7 +552,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
{
|
||||
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)
|
||||
{
|
||||
|
@ -538,14 +563,20 @@ namespace MediaBrowser.Model.Dlna
|
|||
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)
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -262,7 +262,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
|
||||
private SubtitleStreamInfo GetSubtitleStreamInfo(MediaStream stream)
|
||||
{
|
||||
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile);
|
||||
SubtitleProfile subtitleProfile = StreamBuilder.GetSubtitleProfile(stream, DeviceProfile, Context);
|
||||
|
||||
if (subtitleProfile.Method != SubtitleDeliveryMethod.External)
|
||||
{
|
||||
|
|
|
@ -22,6 +22,7 @@ namespace MediaBrowser.Model.Dto
|
|||
|
||||
public long? RunTimeTicks { get; set; }
|
||||
public bool ReadAtNativeFramerate { get; set; }
|
||||
public bool SupportsTranscoding { get; set; }
|
||||
|
||||
public VideoType? VideoType { get; set; }
|
||||
|
||||
|
@ -45,6 +46,7 @@ namespace MediaBrowser.Model.Dto
|
|||
MediaStreams = new List<MediaStream>();
|
||||
RequiredHttpHeaders = new Dictionary<string, string>();
|
||||
PlayableStreamFileNames = new List<string>();
|
||||
SupportsTranscoding = true;
|
||||
}
|
||||
|
||||
public int? DefaultAudioStreamIndex { get; set; }
|
||||
|
|
|
@ -125,6 +125,8 @@
|
|||
<Compile Include="Devices\DeviceInfo.cs" />
|
||||
<Compile Include="Devices\DevicesOptions.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\ResolutionConfiguration.cs" />
|
||||
<Compile Include="Dlna\ResolutionNormalizer.cs" />
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace MediaBrowser.Model.Notifications
|
|||
NewLibraryContentMultiple,
|
||||
ServerRestartRequired,
|
||||
TaskFailed,
|
||||
CameraImageUploaded
|
||||
CameraImageUploaded,
|
||||
UserLockedOut
|
||||
}
|
||||
}
|
|
@ -59,6 +59,8 @@ namespace MediaBrowser.Model.Users
|
|||
public string[] EnabledFolders { get; set; }
|
||||
public bool EnableAllFolders { get; set; }
|
||||
|
||||
public int InvalidLoginAttemptCount { get; set; }
|
||||
|
||||
public UserPolicy()
|
||||
{
|
||||
EnableLiveTvManagement = true;
|
||||
|
|
|
@ -397,7 +397,10 @@ namespace MediaBrowser.Providers.Manager
|
|||
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
|
||||
|
||||
// Only one local provider allowed per item
|
||||
hasLocalMetadata = true;
|
||||
if (IsFullLocalMetadata(localItem.Item))
|
||||
{
|
||||
hasLocalMetadata = true;
|
||||
}
|
||||
successfulProviderCount++;
|
||||
break;
|
||||
}
|
||||
|
@ -473,6 +476,11 @@ namespace MediaBrowser.Providers.Manager
|
|||
return refreshResult;
|
||||
}
|
||||
|
||||
protected virtual bool IsFullLocalMetadata(TItemType item)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task ImportUserData(TItemType item, List<UserItemData> userDataList, CancellationToken cancellationToken)
|
||||
{
|
||||
var hasUserData = item as IHasUserData;
|
||||
|
|
|
@ -33,5 +33,22 @@ namespace MediaBrowser.Providers.Movies
|
|||
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
|
||||
{
|
||||
var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
|
||||
var size = _imageProcessor.GetImageSize(imageInfo);
|
||||
|
||||
item.Width = Convert.ToInt32(size.Width);
|
||||
item.Height = Convert.ToInt32(size.Height);
|
||||
|
|
|
@ -74,5 +74,22 @@ namespace MediaBrowser.Providers.TV
|
|||
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.Controller.Entities;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -14,6 +15,11 @@ namespace MediaBrowser.Server.Implementations.Devices
|
|||
|
||||
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() &&
|
||||
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);
|
||||
}
|
||||
|
||||
using (var wand = new MagickWand(path))
|
||||
using (var wand = new MagickWand())
|
||||
{
|
||||
wand.PingImage(path);
|
||||
var img = wand.CurrentImage;
|
||||
|
||||
return new ImageSize
|
||||
|
|
|
@ -350,9 +350,9 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increment this when indicator drawings change
|
||||
/// Increment this when there's a change requiring caches to be invalidated
|
||||
/// </summary>
|
||||
private const string IndicatorVersion = "2";
|
||||
private const string Version = "3";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache file path based on a set of parameters
|
||||
|
@ -371,29 +371,19 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||
|
||||
filename += "f=" + format;
|
||||
|
||||
var hasIndicator = false;
|
||||
|
||||
if (addPlayedIndicator)
|
||||
{
|
||||
filename += "pl=true";
|
||||
hasIndicator = true;
|
||||
}
|
||||
|
||||
if (percentPlayed > 0)
|
||||
{
|
||||
filename += "p=" + percentPlayed;
|
||||
hasIndicator = true;
|
||||
}
|
||||
|
||||
if (unwatchedCount.HasValue)
|
||||
{
|
||||
filename += "p=" + unwatchedCount.Value;
|
||||
hasIndicator = true;
|
||||
}
|
||||
|
||||
if (hasIndicator)
|
||||
{
|
||||
filename += "iv=" + IndicatorVersion;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(backgroundColor))
|
||||
|
@ -401,6 +391,8 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||
filename += "b=" + backgroundColor;
|
||||
}
|
||||
|
||||
filename += "v=" + Version;
|
||||
|
||||
return GetCachePath(ResizedImageCachePath, filename, "." + format.ToString().ToLower());
|
||||
}
|
||||
|
||||
|
@ -414,6 +406,11 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||
return GetImageSize(path, File.GetLastWriteTimeUtc(path));
|
||||
}
|
||||
|
||||
public ImageSize GetImageSize(ItemImageInfo info)
|
||||
{
|
||||
return GetImageSize(info.Path, info.DateModified);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the size of the image.
|
||||
/// </summary>
|
||||
|
@ -421,7 +418,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||
/// <param name="imageDateModified">The image date modified.</param>
|
||||
/// <returns>ImageSize.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">path</exception>
|
||||
public ImageSize GetImageSize(string path, DateTime imageDateModified)
|
||||
private ImageSize GetImageSize(string path, DateTime imageDateModified)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
|
@ -666,30 +663,6 @@ namespace MediaBrowser.Server.Implementations.Drawing
|
|||
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>
|
||||
/// Executes the image enhancers.
|
||||
/// </summary>
|
||||
|
|
|
@ -1598,14 +1598,11 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||
|
||||
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;
|
||||
|
||||
try
|
||||
{
|
||||
size = _imageProcessor.GetImageSize(path, dateModified);
|
||||
size = _imageProcessor.GetImageSize(imageInfo);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
|
|
|
@ -86,6 +86,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
|||
_userManager.UserPasswordChanged += _userManager_UserPasswordChanged;
|
||||
_userManager.UserDeleted += _userManager_UserDeleted;
|
||||
_userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated;
|
||||
_userManager.UserLockedOut += _userManager_UserLockedOut;
|
||||
|
||||
//_config.ConfigurationUpdated += _config_ConfigurationUpdated;
|
||||
//_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
|
||||
|
@ -95,6 +96,16 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
|||
_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)
|
||||
{
|
||||
CreateLogEntry(new ActivityLogEntry
|
||||
|
@ -482,6 +493,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
|
|||
_userManager.UserPasswordChanged -= _userManager_UserPasswordChanged;
|
||||
_userManager.UserDeleted -= _userManager_UserDeleted;
|
||||
_userManager.UserConfigurationUpdated -= _userManager_UserConfigurationUpdated;
|
||||
_userManager.UserLockedOut -= _userManager_UserLockedOut;
|
||||
|
||||
_config.ConfigurationUpdated -= _config_ConfigurationUpdated;
|
||||
_config.NamedConfigurationUpdated -= _config_NamedConfigurationUpdated;
|
||||
|
|
|
@ -78,6 +78,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
|
|||
_appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
|
||||
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
|
||||
_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)
|
||||
|
@ -235,7 +251,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
|
|||
return;
|
||||
}
|
||||
|
||||
|
||||
var notification = new NotificationRequest
|
||||
{
|
||||
NotificationType = type
|
||||
|
@ -471,6 +486,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
|
|||
_appHost.ApplicationUpdated -= _appHost_ApplicationUpdated;
|
||||
|
||||
_deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded;
|
||||
_userManager.UserLockedOut -= _userManager_UserLockedOut;
|
||||
}
|
||||
|
||||
private void DisposeLibraryUpdateTimer()
|
||||
|
|
|
@ -461,10 +461,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
{
|
||||
return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest)
|
||||
{
|
||||
Throttle = options.Throttle,
|
||||
ThrottleLimit = options.ThrottleLimit,
|
||||
MinThrottlePosition = options.MinThrottlePosition,
|
||||
ThrottleCallback = options.ThrottleCallback,
|
||||
OnComplete = options.OnComplete
|
||||
};
|
||||
}
|
||||
|
@ -480,10 +476,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
|
||||
return new StreamWriter(stream, contentType, _logger)
|
||||
{
|
||||
Throttle = options.Throttle,
|
||||
ThrottleLimit = options.ThrottleLimit,
|
||||
MinThrottlePosition = options.MinThrottlePosition,
|
||||
ThrottleCallback = options.ThrottleCallback,
|
||||
OnComplete = options.OnComplete
|
||||
};
|
||||
}
|
||||
|
|
|
@ -24,10 +24,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
private long RangeLength { 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; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -165,14 +161,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
/// <param name="responseStream">The response stream.</param>
|
||||
public void WriteTo(Stream responseStream)
|
||||
{
|
||||
if (Throttle)
|
||||
{
|
||||
responseStream = new ThrottledStream(responseStream, ThrottleLimit)
|
||||
{
|
||||
MinThrottlePosition = MinThrottlePosition,
|
||||
ThrottleCallback = ThrottleCallback
|
||||
};
|
||||
}
|
||||
WriteToInternal(responseStream);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
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; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -82,14 +78,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer
|
|||
/// <param name="responseStream">The response stream.</param>
|
||||
public void WriteTo(Stream responseStream)
|
||||
{
|
||||
if (Throttle)
|
||||
{
|
||||
responseStream = new ThrottledStream(responseStream, ThrottleLimit)
|
||||
{
|
||||
MinThrottlePosition = MinThrottlePosition,
|
||||
ThrottleCallback = ThrottleCallback
|
||||
};
|
||||
}
|
||||
WriteToInternal(responseStream);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -47,5 +48,59 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
{
|
||||
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 System.Linq;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Naming.Common;
|
||||
using MediaBrowser.Naming.IO;
|
||||
using MediaBrowser.Naming.TV;
|
||||
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
|
||||
{
|
||||
|
|
|
@ -97,6 +97,7 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
/// </summary>
|
||||
public event EventHandler<GenericEventArgs<User>> UserUpdated;
|
||||
public event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
|
||||
public event EventHandler<GenericEventArgs<User>> UserLockedOut;
|
||||
|
||||
/// <summary>
|
||||
/// Called when [user updated].
|
||||
|
@ -259,6 +260,11 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
{
|
||||
user.LastActivityDate = user.LastLoginDate = DateTime.UtcNow;
|
||||
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"));
|
||||
|
@ -266,6 +272,38 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
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)
|
||||
{
|
||||
return string.IsNullOrEmpty(user.Password)
|
||||
|
@ -332,11 +370,6 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
{
|
||||
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;
|
||||
|
||||
await UpdateUserPolicy(user, user.Policy, false);
|
||||
|
@ -915,10 +948,6 @@ namespace MediaBrowser.Server.Implementations.Library
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -442,7 +442,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
|
|||
return null;
|
||||
}
|
||||
|
||||
private const string InternalVersionNumber = "3";
|
||||
private const string InternalVersionNumber = "4";
|
||||
|
||||
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.",
|
||||
"ButtonConvertMedia": "Convert media",
|
||||
"ButtonOrganize": "Organize",
|
||||
"LabelPinCode": "Pin code:",
|
||||
"ButtonOk": "Ok",
|
||||
"ButtonCancel": "Cancel",
|
||||
"ButtonExit": "Exit",
|
||||
"ButtonNew": "New",
|
||||
"HeaderTV": "TV",
|
||||
"HeaderAudio": "Audio",
|
||||
|
@ -57,6 +59,12 @@
|
|||
"HeaderPaths": "Paths",
|
||||
"CategorySync": "Sync",
|
||||
"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",
|
||||
"HeaderSyncRequiresSupporterMembership": "Sync Requires a Supporter Membership",
|
||||
"HeaderEnjoyDayTrial": "Enjoy a 14 Day Free Trial",
|
||||
|
@ -670,6 +678,7 @@
|
|||
"NotificationOptionNewLibraryContent": "New content added",
|
||||
"NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
|
||||
"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.",
|
||||
"NotificationOptionServerRestartRequired": "Server restart required",
|
||||
"LabelNotificationEnabled": "Enable this notification",
|
||||
|
@ -1061,6 +1070,7 @@
|
|||
"OptionBox": "Box",
|
||||
"OptionBoxRear": "Box rear",
|
||||
"OptionDisc": "Disc",
|
||||
"OptionIcon": "Icon",
|
||||
"OptionLogo": "Logo",
|
||||
"OptionMenu": "Menu",
|
||||
"OptionScreenshot": "Screenshot",
|
||||
|
@ -1105,6 +1115,7 @@
|
|||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"LabelRunningTimeValue": "Running time: {0}",
|
||||
"LabelIpAddressValue": "Ip address: {0}",
|
||||
"UserLockedOutWithName": "User {0} has been locked out",
|
||||
"UserConfigurationUpdatedWithName": "User configuration has been updated for {0}",
|
||||
"UserCreatedWithName": "User {0} has been created",
|
||||
"UserPasswordChangedWithName": "Password has been changed for user {0}",
|
||||
|
@ -1114,7 +1125,7 @@
|
|||
"MessageApplicationUpdated": "Media Browser Server has been updated",
|
||||
"AuthenticationSucceededWithUserName": "{0} successfully authenticated",
|
||||
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
|
||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||
"UserDownloadingItemWithValues": "{0} is downloading {1}",
|
||||
"UserStartedPlayingItemWithValues": "{0} has started playing {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} has stopped playing {1}",
|
||||
"AppDeviceValues": "App: {0}, Device: {1}",
|
||||
|
@ -1369,5 +1380,7 @@
|
|||
"TabJobs": "Jobs",
|
||||
"TabSyncJobs": "Sync Jobs",
|
||||
"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>
|
||||
<Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<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 Include="MediaBrowser.Naming, Version=1.0.5509.27636, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
|
@ -278,6 +278,7 @@
|
|||
<Compile Include="Sorting\CommunityRatingComparer.cs" />
|
||||
<Compile Include="Sorting\CriticRatingComparer.cs" />
|
||||
<Compile Include="Sorting\DateCreatedComparer.cs" />
|
||||
<Compile Include="Sorting\DateLastMediaAddedComparer.cs" />
|
||||
<Compile Include="Sorting\DatePlayedComparer.cs" />
|
||||
<Compile Include="Sorting\GameSystemComparer.cs" />
|
||||
<Compile Include="Sorting\IsFavoriteOrLikeComparer.cs" />
|
||||
|
@ -303,8 +304,12 @@
|
|||
<Compile Include="Sorting\StudioComparer.cs" />
|
||||
<Compile Include="Sorting\VideoBitRateComparer.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\MultiProviderSync.cs" />
|
||||
<Compile Include="Sync\SyncRegistrationInfo.cs" />
|
||||
<Compile Include="Sync\SyncConfig.cs" />
|
||||
<Compile Include="Sync\SyncJobProcessor.cs" />
|
||||
|
|
|
@ -143,6 +143,13 @@ namespace MediaBrowser.Server.Implementations.Notifications
|
|||
Type = NotificationType.CameraImageUploaded.ToString(),
|
||||
DefaultTitle = "A new camera image has been uploaded from {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");
|
||||
}
|
||||
else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
note.Category = _localization.GetLocalizedString("CategoryUser");
|
||||
}
|
||||
else
|
||||
{
|
||||
note.Category = _localization.GetLocalizedString("CategorySystem");
|
||||
|
|
|
@ -108,7 +108,12 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||
|
||||
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,
|
||||
1600,
|
||||
900,
|
||||
|
@ -117,7 +122,12 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||
|
||||
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,
|
||||
800, ApplicationPaths);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using MediaBrowser.Common.IO;
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Server.Implementations.Photos
|
||||
|
@ -15,6 +16,11 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||
int width,
|
||||
int height, IApplicationPaths appPaths)
|
||||
{
|
||||
if (files.Any(string.IsNullOrWhiteSpace))
|
||||
{
|
||||
throw new ArgumentException("Empty file found in files list");
|
||||
}
|
||||
|
||||
if (files.Count < 3)
|
||||
{
|
||||
return await GetSingleImage(files, fileSystem).ConfigureAwait(false);
|
||||
|
@ -27,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||
int cellHeight = height;
|
||||
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++)
|
||||
{
|
||||
|
@ -57,6 +63,11 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||
IFileSystem fileSystem,
|
||||
int size, IApplicationPaths appPaths)
|
||||
{
|
||||
if (files.Any(string.IsNullOrWhiteSpace))
|
||||
{
|
||||
throw new ArgumentException("Empty file found in files list");
|
||||
}
|
||||
|
||||
if (files.Count < 4)
|
||||
{
|
||||
return await GetSingleImage(files, fileSystem).ConfigureAwait(false);
|
||||
|
@ -68,7 +79,7 @@ namespace MediaBrowser.Server.Implementations.Photos
|
|||
int singleSize = size / 2;
|
||||
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++)
|
||||
{
|
||||
|
|
|
@ -399,7 +399,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
Client = clientType,
|
||||
DeviceId = deviceId,
|
||||
ApplicationVersion = appVersion,
|
||||
Id = Guid.NewGuid().ToString("N")
|
||||
Id = key.GetMD5().ToString("N")
|
||||
};
|
||||
|
||||
sessionInfo.DeviceName = deviceName;
|
||||
|
@ -798,6 +798,19 @@ namespace MediaBrowser.Server.Implementations.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)
|
||||
{
|
||||
var generalCommand = new GeneralCommand
|
||||
|
@ -818,7 +831,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
|
||||
public Task SendGeneralCommand(string controllingSessionId, string sessionId, GeneralCommand command, CancellationToken cancellationToken)
|
||||
{
|
||||
var session = GetSession(sessionId);
|
||||
var session = GetSessionToRemoteControl(sessionId);
|
||||
|
||||
var controllingSession = GetSession(controllingSessionId);
|
||||
AssertCanControl(session, controllingSession);
|
||||
|
@ -828,7 +841,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
|
||||
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;
|
||||
|
||||
|
@ -955,7 +968,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
|
||||
public Task SendPlaystateCommand(string controllingSessionId, string sessionId, PlaystateRequest command, CancellationToken cancellationToken)
|
||||
{
|
||||
var session = GetSession(sessionId);
|
||||
var session = GetSessionToRemoteControl(sessionId);
|
||||
|
||||
var controllingSession = GetSession(controllingSessionId);
|
||||
AssertCanControl(session, controllingSession);
|
||||
|
@ -1566,11 +1579,7 @@ namespace MediaBrowser.Server.Implementations.Session
|
|||
|
||||
if (!string.IsNullOrWhiteSpace(mediaSourceId))
|
||||
{
|
||||
info.MediaStreams = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery
|
||||
{
|
||||
ItemId = new Guid(mediaSourceId)
|
||||
|
||||
}).ToList();
|
||||
info.MediaStreams = _mediaSourceManager.GetMediaStreams(mediaSourceId).ToList();
|
||||
}
|
||||
|
||||
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.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Sorting;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using System;
|
||||
|
|
|
@ -8,7 +8,7 @@ using System.Linq;
|
|||
|
||||
namespace MediaBrowser.Server.Implementations.Sync
|
||||
{
|
||||
public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds
|
||||
public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds, IHasSyncProfile
|
||||
{
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
|
||||
|
@ -42,5 +42,18 @@ namespace MediaBrowser.Server.Implementations.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.Sync;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Sync;
|
||||
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.Tasks;
|
||||
|
||||
|
@ -15,22 +23,25 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||
private readonly ISyncManager _syncManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
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;
|
||||
_syncManager = syncManager;
|
||||
_appHost = appHost;
|
||||
_fileSystem = fileSystem;
|
||||
}
|
||||
|
||||
public async Task Sync(IServerSyncProvider provider,
|
||||
public async Task Sync(IServerSyncProvider provider,
|
||||
ISyncDataProvider dataProvider,
|
||||
SyncTarget target,
|
||||
IProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var serverId = _appHost.SystemId;
|
||||
|
||||
await SyncData(provider, serverId, target, cancellationToken).ConfigureAwait(false);
|
||||
await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false);
|
||||
progress.Report(3);
|
||||
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
|
@ -40,20 +51,21 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||
totalProgress += 1;
|
||||
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
|
||||
await SyncData(provider, serverId, target, cancellationToken).ConfigureAwait(false);
|
||||
await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
private async Task SyncData(IServerSyncProvider provider,
|
||||
ISyncDataProvider dataProvider,
|
||||
string serverId,
|
||||
SyncTarget target,
|
||||
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
|
||||
{
|
||||
|
@ -68,23 +80,24 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||
{
|
||||
try
|
||||
{
|
||||
await RemoveItem(provider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false);
|
||||
await RemoveItem(provider, dataProvider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
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,
|
||||
ISyncDataProvider dataProvider,
|
||||
SyncTarget target,
|
||||
string serverId,
|
||||
IProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false);
|
||||
|
||||
var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false);
|
||||
|
||||
var numComplete = 0;
|
||||
double startingPercent = 0;
|
||||
double percentPerItem = 1;
|
||||
|
@ -106,7 +119,7 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||
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++;
|
||||
startingPercent = numComplete;
|
||||
|
@ -117,6 +130,7 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||
}
|
||||
|
||||
private async Task GetItem(IServerSyncProvider provider,
|
||||
ISyncDataProvider dataProvider,
|
||||
SyncTarget target,
|
||||
string serverId,
|
||||
SyncedItem jobItem,
|
||||
|
@ -129,6 +143,8 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||
var fileTransferProgress = new ActionableProgress<double>();
|
||||
fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92));
|
||||
|
||||
var localItem = CreateLocalItem(provider, target, libraryItem, serverId, jobItem.OriginalFileName);
|
||||
|
||||
await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id);
|
||||
|
||||
var transferSuccess = false;
|
||||
|
@ -136,10 +152,10 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||
|
||||
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)
|
||||
.ConfigureAwait(false);
|
||||
// Create db record
|
||||
await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false);
|
||||
|
||||
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 itemId,
|
||||
SyncTarget target,
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
return provider.GetSyncTargets(userId).Select(i => new SyncTarget
|
||||
|
@ -429,13 +438,6 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||
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)
|
||||
{
|
||||
return (provider.GetType().Name).GetMD5().ToString("N");
|
||||
|
@ -543,11 +545,11 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||
{
|
||||
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))
|
||||
{
|
||||
return provider.GetDeviceProfile(target);
|
||||
return GetDeviceProfile(provider, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -555,6 +557,18 @@ namespace MediaBrowser.Server.Implementations.Sync
|
|||
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)
|
||||
{
|
||||
var jobItem = _repo.GetJobItem(id);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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="Mono.Nat" version="1.2.21.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">
|
||||
<Link>Properties\SharedVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Diagnostics\LinuxProcessManager.cs" />
|
||||
<Compile Include="Native\BaseMonoApp.cs" />
|
||||
<Compile Include="Networking\CertificateGenerator.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Diagnostics;
|
||||
using MediaBrowser.IsoMounter;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Server.Mono.Diagnostics;
|
||||
using MediaBrowser.Server.Mono.Networking;
|
||||
using MediaBrowser.Server.Startup.Common;
|
||||
using Mono.Unix.Native;
|
||||
|
@ -189,5 +191,16 @@ namespace MediaBrowser.Server.Mono.Native
|
|||
public string sysname = 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(NativeApp.GetProcessManager());
|
||||
|
||||
LocalizationManager = new LocalizationManager(ServerConfigurationManager, FileSystemManager, JsonSerializer);
|
||||
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.Controller.Diagnostics;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
@ -84,5 +85,11 @@ namespace MediaBrowser.Server.Startup.Common
|
|||
/// Prevents the system stand by.
|
||||
/// </summary>
|
||||
void PreventSystemStandby();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the process manager.
|
||||
/// </summary>
|
||||
/// <returns>IProcessManager.</returns>
|
||||
IProcessManager GetProcessManager();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,7 @@
|
|||
<Compile Include="ApplicationHost.cs" />
|
||||
<Compile Include="ApplicationPathHelper.cs" />
|
||||
<Compile Include="Browser\BrowserLauncher.cs" />
|
||||
<Compile Include="Diagnostics\ProcessManager.cs" />
|
||||
<Compile Include="EntryPoints\KeepServerAwake.cs" />
|
||||
<Compile Include="EntryPoints\StartupWizard.cs" />
|
||||
<Compile Include="FFMpeg\FFMpegDownloader.cs" />
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<ItemGroup>
|
||||
<Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<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 Include="MediaBrowser.IsoMounter">
|
||||
<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\ServerAuthorization.cs" />
|
||||
<Compile Include="Native\WindowsApp.cs" />
|
||||
<Compile Include="Native\WindowsProcessManager.cs" />
|
||||
<Compile Include="Networking\CertificateGenerator.cs" />
|
||||
<Compile Include="Networking\NativeMethods.cs" />
|
||||
<Compile Include="Networking\NetworkManager.cs" />
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Diagnostics;
|
||||
using MediaBrowser.IsoMounter;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Server.Mono.Diagnostics;
|
||||
using MediaBrowser.Server.Startup.Common;
|
||||
using MediaBrowser.ServerApplication.Networking;
|
||||
using System.Collections.Generic;
|
||||
|
@ -109,5 +111,10 @@ namespace MediaBrowser.ServerApplication.Native
|
|||
{
|
||||
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"?>
|
||||
<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="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" />
|
||||
</packages>
|
|
@ -421,6 +421,7 @@ namespace MediaBrowser.WebDashboard.Api
|
|||
"itembynamedetailpage.js",
|
||||
"itemdetailpage.js",
|
||||
"itemlistpage.js",
|
||||
"kids.js",
|
||||
"librarypathmapping.js",
|
||||
"reports.js",
|
||||
"librarysettings.js",
|
||||
|
|
|
@ -87,6 +87,9 @@
|
|||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="dashboard-ui\css\images\kids\bg.jpg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\css\images\server.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
@ -114,6 +117,9 @@
|
|||
<Content Include="dashboard-ui\forgotpasswordpin.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\kids.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\mysync.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
@ -129,6 +135,9 @@
|
|||
<Content Include="dashboard-ui\scripts\forgotpasswordpin.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\scripts\kids.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="dashboard-ui\scripts\selectserver.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</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);
|
||||
}
|
||||
|
|
|
@ -520,4 +520,7 @@ Global
|
|||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(Performance) = preSolution
|
||||
HasPerformanceSessions = true
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>MediaBrowser.Common.Internal</id>
|
||||
<version>3.0.576</version>
|
||||
<version>3.0.579</version>
|
||||
<title>MediaBrowser.Common.Internal</title>
|
||||
<authors>Luke</authors>
|
||||
<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>
|
||||
<copyright>Copyright © Media Browser 2013</copyright>
|
||||
<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="SimpleInjector" version="2.7.0" />
|
||||
</dependencies>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>MediaBrowser.Common</id>
|
||||
<version>3.0.576</version>
|
||||
<version>3.0.579</version>
|
||||
<title>MediaBrowser.Common</title>
|
||||
<authors>Media Browser Team</authors>
|
||||
<owners>ebr,Luke,scottisafool</owners>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>MediaBrowser.Model.Signed</id>
|
||||
<version>3.0.576</version>
|
||||
<version>3.0.579</version>
|
||||
<title>MediaBrowser.Model - Signed Edition</title>
|
||||
<authors>Media Browser Team</authors>
|
||||
<owners>ebr,Luke,scottisafool</owners>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>MediaBrowser.Server.Core</id>
|
||||
<version>3.0.576</version>
|
||||
<version>3.0.579</version>
|
||||
<title>Media Browser.Server.Core</title>
|
||||
<authors>Media Browser Team</authors>
|
||||
<owners>ebr,Luke,scottisafool</owners>
|
||||
|
@ -12,7 +12,7 @@
|
|||
<description>Contains core components required to build plugins for Media Browser Server.</description>
|
||||
<copyright>Copyright © Media Browser 2013</copyright>
|
||||
<dependencies>
|
||||
<dependency id="MediaBrowser.Common" version="3.0.576" />
|
||||
<dependency id="MediaBrowser.Common" version="3.0.579" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
|
|
Loading…
Reference in a new issue