mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-09-06 05:47:14 -04:00
Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser
This commit is contained in:
commit
9ff262bc2a
@ -9,6 +9,7 @@ using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
@ -247,6 +248,11 @@ namespace MediaBrowser.Api.Playback
|
||||
}
|
||||
}
|
||||
|
||||
if (type == MediaStreamType.Video)
|
||||
{
|
||||
streams = streams.Where(i => !string.Equals(i.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
}
|
||||
|
||||
if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
|
||||
{
|
||||
return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
|
||||
@ -303,9 +309,9 @@ namespace MediaBrowser.Api.Playback
|
||||
case EncodingQuality.HighSpeed:
|
||||
return 2;
|
||||
case EncodingQuality.HighQuality:
|
||||
return 2;
|
||||
return isWebm ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
|
||||
case EncodingQuality.MaxQuality:
|
||||
return isWebm ? 2 : 0;
|
||||
return isWebm ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
|
||||
default:
|
||||
throw new Exception("Unrecognized MediaEncodingQuality value.");
|
||||
}
|
||||
@ -1018,7 +1024,7 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
return string.Format(" -b:v {0}", bitrate.Value.ToString(UsCulture));
|
||||
}
|
||||
|
||||
|
||||
return string.Format(" -maxrate {0} -bufsize {1}",
|
||||
bitrate.Value.ToString(UsCulture),
|
||||
(bitrate.Value * 2).ToString(UsCulture));
|
||||
@ -1256,6 +1262,69 @@ namespace MediaBrowser.Api.Playback
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the dlna headers.
|
||||
/// </summary>
|
||||
/// <param name="request">The request.</param>
|
||||
private void ParseDlnaHeaders(StreamRequest request)
|
||||
{
|
||||
if (!request.StartTimeTicks.HasValue)
|
||||
{
|
||||
var timeSeek = GetHeader("TimeSeekRange.dlna.org");
|
||||
|
||||
request.StartTimeTicks = ParseTimeSeekHeader(timeSeek);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the time seek header.
|
||||
/// </summary>
|
||||
private long? ParseTimeSeekHeader(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.IndexOf("npt=", StringComparison.OrdinalIgnoreCase) != 0)
|
||||
{
|
||||
throw new ArgumentException("Invalid timeseek header");
|
||||
}
|
||||
value = value.Substring(4).Split(new[] { '-' }, 2)[0];
|
||||
|
||||
if (value.IndexOf(':') == -1)
|
||||
{
|
||||
// Parses npt times in the format of '417.33'
|
||||
double seconds;
|
||||
if (double.TryParse(value, NumberStyles.Any, UsCulture, out seconds))
|
||||
{
|
||||
return TimeSpan.FromSeconds(seconds).Ticks;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Invalid timeseek header");
|
||||
}
|
||||
|
||||
// Parses npt times in the format of '10:19:25.7'
|
||||
var tokens = value.Split(new[] { ':' }, 3);
|
||||
double secondsSum = 0;
|
||||
var timeFactor = 3600;
|
||||
|
||||
foreach (var time in tokens)
|
||||
{
|
||||
double digit;
|
||||
if (double.TryParse(time, NumberStyles.Any, UsCulture, out digit))
|
||||
{
|
||||
secondsSum += (digit * timeFactor);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Invalid timeseek header");
|
||||
}
|
||||
timeFactor /= 60;
|
||||
}
|
||||
return TimeSpan.FromSeconds(secondsSum).Ticks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the state.
|
||||
/// </summary>
|
||||
@ -1264,6 +1333,8 @@ namespace MediaBrowser.Api.Playback
|
||||
/// <returns>StreamState.</returns>
|
||||
protected async Task<StreamState> GetState(StreamRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
ParseDlnaHeaders(request);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.Params))
|
||||
{
|
||||
ParseParams(request);
|
||||
@ -1428,9 +1499,112 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
ApplyDeviceProfileSettings(state);
|
||||
|
||||
if (videoRequest != null && state.VideoStream != null)
|
||||
{
|
||||
if (CanStreamCopyVideo(videoRequest, state.VideoStream, state.VideoType))
|
||||
{
|
||||
videoRequest.VideoCodec = "copy";
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream, VideoType videoType)
|
||||
{
|
||||
if (videoStream.IsInterlaced)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Not going to attempt this with folder rips
|
||||
if (videoType != VideoType.VideoFile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Source and target codecs must match
|
||||
if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If client is requesting a specific video profile, it must match the source
|
||||
if (!string.IsNullOrEmpty(request.Profile) && !string.Equals(request.Profile, videoStream.Profile))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Video width must fall within requested value
|
||||
if (request.MaxWidth.HasValue)
|
||||
{
|
||||
if (!videoStream.Width.HasValue || videoStream.Width.Value > request.MaxWidth.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Video height must fall within requested value
|
||||
if (request.MaxHeight.HasValue)
|
||||
{
|
||||
if (!videoStream.Height.HasValue || videoStream.Height.Value > request.MaxHeight.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Video framerate must fall within requested value
|
||||
var requestedFramerate = request.MaxFramerate ?? request.Framerate;
|
||||
if (requestedFramerate.HasValue)
|
||||
{
|
||||
var videoFrameRate = videoStream.AverageFrameRate ?? videoStream.RealFrameRate;
|
||||
|
||||
if (!videoFrameRate.HasValue || videoFrameRate.Value > requestedFramerate.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Video bitrate must fall within requested value
|
||||
if (request.VideoBitRate.HasValue)
|
||||
{
|
||||
if (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If a specific level was requested, the source must match or be less than
|
||||
if (!string.IsNullOrEmpty(request.Level))
|
||||
{
|
||||
double requestLevel;
|
||||
|
||||
if (double.TryParse(request.Level, NumberStyles.Any, UsCulture, out requestLevel))
|
||||
{
|
||||
if (!videoStream.Level.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (videoStream.Level.Value > requestLevel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return SupportsAutomaticVideoStreamCopy;
|
||||
}
|
||||
|
||||
protected virtual bool SupportsAutomaticVideoStreamCopy
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyDeviceProfileSettings(StreamState state)
|
||||
{
|
||||
var headers = new Dictionary<string, string>();
|
||||
@ -1509,8 +1683,6 @@ namespace MediaBrowser.Api.Playback
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
protected void AddDlnaHeaders(StreamState state, IDictionary<string, string> responseHeaders, bool isStaticallyStreamed)
|
||||
{
|
||||
var timeSeek = GetHeader("TimeSeekRange.dlna.org");
|
||||
|
||||
var transferMode = GetHeader("transferMode.dlna.org");
|
||||
responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
|
||||
responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
|
||||
@ -1521,11 +1693,21 @@ namespace MediaBrowser.Api.Playback
|
||||
// first bit means Time based seek supported, second byte range seek supported (not sure about the order now), so 01 = only byte seek, 10 = time based, 11 = both, 00 = none
|
||||
var orgOp = ";DLNA.ORG_OP=";
|
||||
|
||||
// Time-based seeking currently only possible when transcoding
|
||||
orgOp += isStaticallyStreamed ? "0" : "1";
|
||||
if (state.RunTimeTicks.HasValue)
|
||||
{
|
||||
// Time-based seeking currently only possible when transcoding
|
||||
orgOp += isStaticallyStreamed ? "0" : "1";
|
||||
|
||||
// Byte-based seeking only possible when not transcoding
|
||||
orgOp += isStaticallyStreamed || state.TranscodeSeekInfo == TranscodeSeekInfo.Bytes ? "1" : "0";
|
||||
// Byte-based seeking only possible when not transcoding
|
||||
orgOp += isStaticallyStreamed || state.TranscodeSeekInfo == TranscodeSeekInfo.Bytes ? "1" : "0";
|
||||
|
||||
AddTimeSeekResponseHeaders(state, responseHeaders);
|
||||
}
|
||||
else
|
||||
{
|
||||
// No seeking is available if we don't know the content runtime
|
||||
orgOp += "00";
|
||||
}
|
||||
|
||||
// 0 = native, 1 = transcoded
|
||||
var orgCi = isStaticallyStreamed ? ";DLNA.ORG_CI=0" : ";DLNA.ORG_CI=1";
|
||||
@ -1568,15 +1750,6 @@ namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
contentFeatures = "DLNA.ORG_PN=MPEG_PS_PAL";
|
||||
}
|
||||
//else if (string.Equals(extension, ".wmv", StringComparison.OrdinalIgnoreCase))
|
||||
//{
|
||||
// contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE";
|
||||
//}
|
||||
//else if (string.Equals(extension, ".asf", StringComparison.OrdinalIgnoreCase))
|
||||
//{
|
||||
// // ??
|
||||
// contentFeatures = "DLNA.ORG_PN=WMVHIGH_BASE";
|
||||
//}
|
||||
|
||||
if (!string.IsNullOrEmpty(contentFeatures))
|
||||
{
|
||||
@ -1589,6 +1762,15 @@ namespace MediaBrowser.Api.Playback
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
|
||||
{
|
||||
var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);
|
||||
var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(UsCulture);
|
||||
|
||||
responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds);
|
||||
responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enforces the resolution limit.
|
||||
/// </summary>
|
||||
|
@ -98,6 +98,14 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls);
|
||||
}
|
||||
|
||||
protected override bool SupportsAutomaticVideoStreamCopy
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specified request.
|
||||
/// </summary>
|
||||
|
@ -1,5 +1,5 @@
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using System.Collections.Generic;
|
||||
|
@ -78,14 +78,7 @@
|
||||
<Compile Include="Channels\Channel.cs" />
|
||||
<Compile Include="Collections\CollectionCreationOptions.cs" />
|
||||
<Compile Include="Collections\ICollectionManager.cs" />
|
||||
<Compile Include="Dlna\CodecProfile.cs" />
|
||||
<Compile Include="Dlna\ContainerProfile.cs" />
|
||||
<Compile Include="Dlna\DeviceIdentification.cs" />
|
||||
<Compile Include="Dlna\DirectPlayProfile.cs" />
|
||||
<Compile Include="Dlna\IDlnaManager.cs" />
|
||||
<Compile Include="Dlna\DeviceProfile.cs" />
|
||||
<Compile Include="Dlna\ResponseProfile.cs" />
|
||||
<Compile Include="Dlna\TranscodingProfile.cs" />
|
||||
<Compile Include="Drawing\IImageProcessor.cs" />
|
||||
<Compile Include="Drawing\ImageFormat.cs" />
|
||||
<Compile Include="Drawing\ImageProcessingOptions.cs" />
|
||||
|
@ -1,4 +1,5 @@
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
|
@ -70,6 +70,7 @@
|
||||
<Compile Include="PlayTo\PlayToServerEntryPoint.cs" />
|
||||
<Compile Include="PlayTo\ServiceAction.cs" />
|
||||
<Compile Include="Profiles\Foobar2000Profile.cs" />
|
||||
<Compile Include="Profiles\Windows81Profile.cs" />
|
||||
<Compile Include="Ssdp\SsdpHelper.cs" />
|
||||
<Compile Include="PlayTo\SsdpHttpClient.cs" />
|
||||
<Compile Include="PlayTo\StateVariable.cs" />
|
||||
@ -78,7 +79,7 @@
|
||||
<Compile Include="PlayTo\TransportStateEventArgs.cs" />
|
||||
<Compile Include="PlayTo\uBaseObject.cs" />
|
||||
<Compile Include="PlayTo\uContainer.cs" />
|
||||
<Compile Include="PlayTo\uIcon.cs" />
|
||||
<Compile Include="PlayTo\DeviceIcon.cs" />
|
||||
<Compile Include="PlayTo\uParser.cs" />
|
||||
<Compile Include="PlayTo\uPnpNamespaces.cs" />
|
||||
<Compile Include="Profiles\DefaultProfile.cs" />
|
||||
|
@ -1,4 +1,5 @@
|
||||
using MediaBrowser.Common.Net;
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
@ -453,10 +454,10 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
|
||||
var volumeValue = volume == null ? null : volume.Value;
|
||||
|
||||
if (volumeValue == null)
|
||||
if (string.IsNullOrWhiteSpace(volumeValue))
|
||||
return;
|
||||
|
||||
Volume = Int32.Parse(volumeValue);
|
||||
Volume = int.Parse(volumeValue, UsCulture);
|
||||
|
||||
//Reset the Mute value if Volume is bigger than zero
|
||||
if (Volume > 0 && _muteVol > 0)
|
||||
@ -555,17 +556,17 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
|
||||
var duration = durationElem == null ? null : durationElem.Value;
|
||||
|
||||
if (duration != null)
|
||||
if (!string.IsNullOrWhiteSpace(duration))
|
||||
{
|
||||
Duration = TimeSpan.Parse(duration);
|
||||
Duration = TimeSpan.Parse(duration, UsCulture);
|
||||
}
|
||||
|
||||
var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
|
||||
var position = positionElem == null ? null : positionElem.Value;
|
||||
|
||||
if (position != null)
|
||||
if (!string.IsNullOrWhiteSpace(position))
|
||||
{
|
||||
Position = TimeSpan.Parse(position);
|
||||
Position = TimeSpan.Parse(position, UsCulture);
|
||||
}
|
||||
|
||||
var track = result.Document.Descendants("TrackMetaData").Select(i => i.Value)
|
||||
@ -701,7 +702,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
|
||||
if (icon != null)
|
||||
{
|
||||
deviceProperties.Icon = uIcon.Create(icon);
|
||||
deviceProperties.Icon = CreateIcon(icon);
|
||||
}
|
||||
|
||||
var isRenderer = false;
|
||||
@ -746,6 +747,33 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
|
||||
#endregion
|
||||
|
||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
||||
private static DeviceIcon CreateIcon(XElement element)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
throw new ArgumentNullException("element");
|
||||
}
|
||||
|
||||
var mimeType = element.GetDescendantValue(uPnpNamespaces.ud.GetName("mimetype"));
|
||||
var width = element.GetDescendantValue(uPnpNamespaces.ud.GetName("width"));
|
||||
var height = element.GetDescendantValue(uPnpNamespaces.ud.GetName("height"));
|
||||
var depth = element.GetDescendantValue(uPnpNamespaces.ud.GetName("depth"));
|
||||
var url = element.GetDescendantValue(uPnpNamespaces.ud.GetName("url"));
|
||||
|
||||
var widthValue = int.Parse(width, NumberStyles.Any, UsCulture);
|
||||
var heightValue = int.Parse(height, NumberStyles.Any, UsCulture);
|
||||
|
||||
return new DeviceIcon
|
||||
{
|
||||
Depth = depth,
|
||||
Height = heightValue,
|
||||
MimeType = mimeType,
|
||||
Url = url,
|
||||
Width = widthValue
|
||||
};
|
||||
}
|
||||
|
||||
private static DeviceService Create(XElement element)
|
||||
{
|
||||
var type = element.GetDescendantValue(uPnpNamespaces.ud.GetName("serviceType"));
|
||||
|
21
MediaBrowser.Dlna/PlayTo/DeviceIcon.cs
Normal file
21
MediaBrowser.Dlna/PlayTo/DeviceIcon.cs
Normal file
@ -0,0 +1,21 @@
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class DeviceIcon
|
||||
{
|
||||
public string Url { get; set; }
|
||||
|
||||
public string MimeType { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
|
||||
public string Depth { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}x{1}", Height, Width);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
@ -46,7 +47,7 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
}
|
||||
}
|
||||
|
||||
public uIcon Icon { get; set; }
|
||||
public DeviceIcon Icon { get; set; }
|
||||
|
||||
private readonly List<DeviceService> _services = new List<DeviceService>();
|
||||
public List<DeviceService> Services
|
||||
|
@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Session;
|
||||
|
@ -1,4 +1,4 @@
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@ -184,7 +185,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.Filesize:
|
||||
case ProfileConditionValue.AudioProfile:
|
||||
case ProfileConditionValue.Has64BitOffsets:
|
||||
case ProfileConditionValue.VideoBitDepth:
|
||||
@ -444,8 +444,6 @@ namespace MediaBrowser.Dlna.PlayTo
|
||||
return audioStream == null ? null : audioStream.BitRate;
|
||||
case ProfileConditionValue.AudioChannels:
|
||||
return audioStream == null ? null : audioStream.Channels;
|
||||
case ProfileConditionValue.Filesize:
|
||||
return new FileInfo(mediaPath).Length;
|
||||
case ProfileConditionValue.VideoBitrate:
|
||||
return videoStream == null ? null : videoStream.BitRate;
|
||||
case ProfileConditionValue.VideoFramerate:
|
||||
|
@ -1,48 +0,0 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace MediaBrowser.Dlna.PlayTo
|
||||
{
|
||||
public class uIcon
|
||||
{
|
||||
public string Url { get; private set; }
|
||||
|
||||
public string MimeType { get; private set; }
|
||||
|
||||
public int Width { get; private set; }
|
||||
|
||||
public int Height { get; private set; }
|
||||
|
||||
public string Depth { get; private set; }
|
||||
|
||||
public uIcon(string mimeType, string width, string height, string depth, string url)
|
||||
{
|
||||
MimeType = mimeType;
|
||||
Width = (!string.IsNullOrEmpty(width)) ? int.Parse(width) : 0;
|
||||
Height = (!string.IsNullOrEmpty(height)) ? int.Parse(height) : 0;
|
||||
Depth = depth;
|
||||
Url = url;
|
||||
}
|
||||
|
||||
public static uIcon Create(XElement element)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
throw new ArgumentNullException("element");
|
||||
}
|
||||
|
||||
var mimeType = element.GetDescendantValue(uPnpNamespaces.ud.GetName("mimetype"));
|
||||
var width = element.GetDescendantValue(uPnpNamespaces.ud.GetName("width"));
|
||||
var height = element.GetDescendantValue(uPnpNamespaces.ud.GetName("height"));
|
||||
var depth = element.GetDescendantValue(uPnpNamespaces.ud.GetName("depth"));
|
||||
var url = element.GetDescendantValue(uPnpNamespaces.ud.GetName("url"));
|
||||
|
||||
return new uIcon(mimeType, width, height, depth, url);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}x{1}", Height, Width);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
141
MediaBrowser.Dlna/Profiles/Windows81Profile.cs
Normal file
141
MediaBrowser.Dlna/Profiles/Windows81Profile.cs
Normal file
@ -0,0 +1,141 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
[XmlRoot("Profile")]
|
||||
public class Windows81Profile : DefaultProfile
|
||||
{
|
||||
public Windows81Profile()
|
||||
{
|
||||
Name = "Windows 8/RT";
|
||||
|
||||
Identification = new DeviceIdentification
|
||||
{
|
||||
Manufacturer = "Microsoft SDK Customer"
|
||||
};
|
||||
|
||||
TranscodingProfiles = new[]
|
||||
{
|
||||
new TranscodingProfile
|
||||
{
|
||||
Container = "mp3",
|
||||
AudioCodec = "mp3",
|
||||
Type = DlnaProfileType.Audio
|
||||
},
|
||||
new TranscodingProfile
|
||||
{
|
||||
Container = "ts",
|
||||
VideoCodec = "h264",
|
||||
AudioCodec = "aac",
|
||||
Type = DlnaProfileType.Video,
|
||||
VideoProfile = "Baseline"
|
||||
}
|
||||
};
|
||||
|
||||
DirectPlayProfiles = new[]
|
||||
{
|
||||
new DirectPlayProfile
|
||||
{
|
||||
Container = "mp4,mov",
|
||||
VideoCodec = "h264,mpeg4",
|
||||
AudioCodec = "aac,ac3,eac3,mp3,pcm",
|
||||
Type = DlnaProfileType.Video
|
||||
},
|
||||
|
||||
new DirectPlayProfile
|
||||
{
|
||||
Container = "ts",
|
||||
VideoCodec = "h264",
|
||||
AudioCodec = "aac,ac3,eac3,mp3,mp2,pcm",
|
||||
Type = DlnaProfileType.Video
|
||||
},
|
||||
|
||||
new DirectPlayProfile
|
||||
{
|
||||
Container = "asf",
|
||||
VideoCodec = "wmv2,wmv3,vc1",
|
||||
AudioCodec = "wmav2,wmapro,wmavoice",
|
||||
Type = DlnaProfileType.Video
|
||||
},
|
||||
|
||||
new DirectPlayProfile
|
||||
{
|
||||
Container = "avi",
|
||||
VideoCodec = "mpeg4,msmpeg4,mjpeg",
|
||||
AudioCodec = "mp3,ac3,eac3,mp2,pcm",
|
||||
Type = DlnaProfileType.Video
|
||||
},
|
||||
|
||||
new DirectPlayProfile
|
||||
{
|
||||
Container = "mp4",
|
||||
AudioCodec = "aac",
|
||||
Type = DlnaProfileType.Audio
|
||||
},
|
||||
|
||||
new DirectPlayProfile
|
||||
{
|
||||
Container = "mp3",
|
||||
AudioCodec = "mp3",
|
||||
Type = DlnaProfileType.Audio
|
||||
},
|
||||
|
||||
new DirectPlayProfile
|
||||
{
|
||||
Container = "jpeg",
|
||||
Type = DlnaProfileType.Photo
|
||||
}
|
||||
};
|
||||
|
||||
CodecProfiles = new[]
|
||||
{
|
||||
new CodecProfile
|
||||
{
|
||||
Type = CodecType.Video,
|
||||
Conditions = new []
|
||||
{
|
||||
new ProfileCondition
|
||||
{
|
||||
Condition = ProfileConditionType.LessThanEqual,
|
||||
Property = ProfileConditionValue.VideoBitDepth,
|
||||
Value = "8",
|
||||
IsRequired = false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
new CodecProfile
|
||||
{
|
||||
Type = CodecType.VideoAudio,
|
||||
Codec = "aac,eac3",
|
||||
Conditions = new []
|
||||
{
|
||||
new ProfileCondition
|
||||
{
|
||||
Condition = ProfileConditionType.LessThanEqual,
|
||||
Property = ProfileConditionValue.AudioChannels,
|
||||
Value = "8"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
new CodecProfile
|
||||
{
|
||||
Type = CodecType.VideoAudio,
|
||||
Codec = "ac3",
|
||||
Conditions = new []
|
||||
{
|
||||
new ProfileCondition
|
||||
{
|
||||
Condition = ProfileConditionType.LessThanEqual,
|
||||
Property = ProfileConditionValue.AudioChannels,
|
||||
Value = "6"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Xml.Serialization;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace MediaBrowser.Dlna.Profiles
|
||||
{
|
||||
|
@ -224,9 +224,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
case EncodingQuality.HighSpeed:
|
||||
return 2;
|
||||
case EncodingQuality.HighQuality:
|
||||
return 2;
|
||||
return isWebm ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
|
||||
case EncodingQuality.MaxQuality:
|
||||
return isWebm ? 2 : 0;
|
||||
return isWebm ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
|
||||
default:
|
||||
throw new Exception("Unrecognized MediaEncodingQuality value.");
|
||||
}
|
||||
|
@ -101,9 +101,36 @@
|
||||
<Compile Include="..\MediaBrowser.Model\Configuration\UserConfiguration.cs">
|
||||
<Link>Configuration\UserConfiguration.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\CodecProfile.cs">
|
||||
<Link>Dlna\CodecProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\ContainerProfile.cs">
|
||||
<Link>Dlna\ContainerProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DeviceIdentification.cs">
|
||||
<Link>Dlna\DeviceIdentification.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DeviceProfile.cs">
|
||||
<Link>Dlna\DeviceProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DeviceProfileInfo.cs">
|
||||
<Link>Dlna\DeviceProfileInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DirectPlayProfile.cs">
|
||||
<Link>Dlna\DirectPlayProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\ResponseProfile.cs">
|
||||
<Link>Dlna\ResponseProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamBuilder.cs">
|
||||
<Link>Dlna\StreamBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
|
||||
<Link>Dlna\StreamInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\TranscodingProfile.cs">
|
||||
<Link>Dlna\TranscodingProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Drawing\DrawingUtils.cs">
|
||||
<Link>Drawing\DrawingUtils.cs</Link>
|
||||
</Compile>
|
||||
@ -224,9 +251,6 @@
|
||||
<Compile Include="..\MediaBrowser.Model\Entities\VirtualFolderInfo.cs">
|
||||
<Link>Entities\VirtualFolderInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Extensions\ModelExtensions.cs">
|
||||
<Link>Extensions\ModelExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\FileOrganization\FileOrganizationQuery.cs">
|
||||
<Link>FileOrganization\FileOrganizationQuery.cs</Link>
|
||||
</Compile>
|
||||
|
@ -88,9 +88,36 @@
|
||||
<Compile Include="..\MediaBrowser.Model\Configuration\UserConfiguration.cs">
|
||||
<Link>Configuration\UserConfiguration.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\CodecProfile.cs">
|
||||
<Link>Dlna\CodecProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\ContainerProfile.cs">
|
||||
<Link>Dlna\ContainerProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DeviceIdentification.cs">
|
||||
<Link>Dlna\DeviceIdentification.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DeviceProfile.cs">
|
||||
<Link>Dlna\DeviceProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DeviceProfileInfo.cs">
|
||||
<Link>Dlna\DeviceProfileInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\DirectPlayProfile.cs">
|
||||
<Link>Dlna\DirectPlayProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\ResponseProfile.cs">
|
||||
<Link>Dlna\ResponseProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamBuilder.cs">
|
||||
<Link>Dlna\StreamBuilder.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\StreamInfo.cs">
|
||||
<Link>Dlna\StreamInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Dlna\TranscodingProfile.cs">
|
||||
<Link>Dlna\TranscodingProfile.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Drawing\DrawingUtils.cs">
|
||||
<Link>Drawing\DrawingUtils.cs</Link>
|
||||
</Compile>
|
||||
@ -211,9 +238,6 @@
|
||||
<Compile Include="..\MediaBrowser.Model\Entities\VirtualFolderInfo.cs">
|
||||
<Link>Entities\VirtualFolderInfo.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\Extensions\ModelExtensions.cs">
|
||||
<Link>Extensions\ModelExtensions.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="..\MediaBrowser.Model\FileOrganization\FileOrganizationQuery.cs">
|
||||
<Link>FileOrganization\FileOrganizationQuery.cs</Link>
|
||||
</Compile>
|
||||
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class CodecProfile
|
||||
{
|
||||
@ -22,7 +22,7 @@ namespace MediaBrowser.Controller.Dlna
|
||||
|
||||
public List<string> GetCodecs()
|
||||
{
|
||||
return (Codec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
return (Codec ?? string.Empty).Split(',').Where(i => !string.IsNullOrEmpty(i)).ToList();
|
||||
}
|
||||
|
||||
public bool ContainsCodec(string codec)
|
||||
@ -73,7 +73,6 @@ namespace MediaBrowser.Controller.Dlna
|
||||
AudioChannels,
|
||||
AudioBitrate,
|
||||
AudioProfile,
|
||||
Filesize,
|
||||
Width,
|
||||
Height,
|
||||
Has64BitOffsets,
|
@ -2,7 +2,7 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class ContainerProfile
|
||||
{
|
||||
@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Dlna
|
||||
|
||||
public List<string> GetContainers()
|
||||
{
|
||||
return (Container ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
return (Container ?? string.Empty).Split(',').Where(i => !string.IsNullOrEmpty(i)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
|
||||
using System.Xml.Serialization;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class DeviceIdentification
|
||||
{
|
@ -1,11 +1,10 @@
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
[XmlRoot("Profile")]
|
||||
public class DeviceProfile
|
||||
@ -89,7 +88,7 @@ namespace MediaBrowser.Controller.Dlna
|
||||
|
||||
public List<string> GetSupportedMediaTypes()
|
||||
{
|
||||
return (SupportedMediaTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
return (SupportedMediaTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrEmpty(i)).ToList();
|
||||
}
|
||||
|
||||
public TranscodingProfile GetAudioTranscodingProfile(string container, string audioCodec)
|
@ -2,7 +2,7 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class DirectPlayProfile
|
||||
{
|
||||
@ -20,17 +20,17 @@ namespace MediaBrowser.Controller.Dlna
|
||||
|
||||
public List<string> GetContainers()
|
||||
{
|
||||
return (Container ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
return (Container ?? string.Empty).Split(',').Where(i => !string.IsNullOrEmpty(i)).ToList();
|
||||
}
|
||||
|
||||
public List<string> GetAudioCodecs()
|
||||
{
|
||||
return (AudioCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
return (AudioCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrEmpty(i)).ToList();
|
||||
}
|
||||
|
||||
public List<string> GetVideoCodecs()
|
||||
{
|
||||
return (VideoCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
return (VideoCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrEmpty(i)).ToList();
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class ResponseProfile
|
||||
{
|
||||
@ -33,17 +33,17 @@ namespace MediaBrowser.Controller.Dlna
|
||||
|
||||
public List<string> GetContainers()
|
||||
{
|
||||
return (Container ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
return (Container ?? string.Empty).Split(',').Where(i => !string.IsNullOrEmpty(i)).ToList();
|
||||
}
|
||||
|
||||
public List<string> GetAudioCodecs()
|
||||
{
|
||||
return (AudioCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
return (AudioCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrEmpty(i)).ToList();
|
||||
}
|
||||
|
||||
public List<string> GetVideoCodecs()
|
||||
{
|
||||
return (VideoCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
return (VideoCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrEmpty(i)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
533
MediaBrowser.Model/Dlna/StreamBuilder.cs
Normal file
533
MediaBrowser.Model/Dlna/StreamBuilder.cs
Normal file
@ -0,0 +1,533 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class StreamBuilder
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
public StreamInfo BuildAudioItem(AudioOptions options)
|
||||
{
|
||||
ValidateAudioInput(options);
|
||||
|
||||
var mediaSources = options.MediaSources;
|
||||
|
||||
// If the client wants a specific media soure, filter now
|
||||
if (!string.IsNullOrEmpty(options.MediaSourceId))
|
||||
{
|
||||
// Avoid implicitly captured closure
|
||||
var mediaSourceId = options.MediaSourceId;
|
||||
|
||||
mediaSources = mediaSources
|
||||
.Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var streams = mediaSources.Select(i => BuildAudioItem(options.ItemId, i, options.Profile)).ToList();
|
||||
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
stream.DeviceId = options.DeviceId;
|
||||
stream.DeviceProfileId = options.Profile.Id;
|
||||
}
|
||||
|
||||
return GetOptimalStream(streams);
|
||||
}
|
||||
|
||||
public StreamInfo BuildVideoItem(VideoOptions options)
|
||||
{
|
||||
ValidateInput(options);
|
||||
|
||||
var mediaSources = options.MediaSources;
|
||||
|
||||
// If the client wants a specific media soure, filter now
|
||||
if (!string.IsNullOrEmpty(options.MediaSourceId))
|
||||
{
|
||||
// Avoid implicitly captured closure
|
||||
var mediaSourceId = options.MediaSourceId;
|
||||
|
||||
mediaSources = mediaSources
|
||||
.Where(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var streams = mediaSources.Select(i => BuildVideoItem(i, options)).ToList();
|
||||
|
||||
foreach (var stream in streams)
|
||||
{
|
||||
stream.DeviceId = options.DeviceId;
|
||||
stream.DeviceProfileId = options.Profile.Id;
|
||||
}
|
||||
|
||||
return GetOptimalStream(streams);
|
||||
}
|
||||
|
||||
private StreamInfo GetOptimalStream(List<StreamInfo> streams)
|
||||
{
|
||||
// Grab the first one that can be direct streamed
|
||||
// If that doesn't produce anything, just take the first
|
||||
return streams.FirstOrDefault(i => i.IsDirectStream) ??
|
||||
streams.FirstOrDefault();
|
||||
}
|
||||
|
||||
private StreamInfo BuildAudioItem(string itemId, MediaSourceInfo item, DeviceProfile profile)
|
||||
{
|
||||
var playlistItem = new StreamInfo
|
||||
{
|
||||
ItemId = itemId,
|
||||
MediaType = DlnaProfileType.Audio,
|
||||
MediaSourceId = item.Id
|
||||
};
|
||||
|
||||
var audioStream = item.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
|
||||
var directPlay = profile.DirectPlayProfiles
|
||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsAudioProfileSupported(i, item, audioStream));
|
||||
|
||||
if (directPlay != null)
|
||||
{
|
||||
var audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||
|
||||
// Make sure audio codec profiles are satisfied
|
||||
if (!string.IsNullOrEmpty(audioCodec) && profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec))
|
||||
.All(i => AreConditionsSatisfied(i.Conditions, item.Path, null, audioStream)))
|
||||
{
|
||||
playlistItem.IsDirectStream = true;
|
||||
playlistItem.Container = item.Container;
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
}
|
||||
|
||||
var transcodingProfile = profile.TranscodingProfiles
|
||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType);
|
||||
|
||||
if (transcodingProfile != null)
|
||||
{
|
||||
playlistItem.IsDirectStream = false;
|
||||
playlistItem.Container = transcodingProfile.Container;
|
||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec;
|
||||
|
||||
var audioTranscodingConditions = profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(transcodingProfile.AudioCodec))
|
||||
.Take(1)
|
||||
.SelectMany(i => i.Conditions);
|
||||
|
||||
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
||||
}
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
|
||||
private StreamInfo BuildVideoItem(MediaSourceInfo item, VideoOptions options)
|
||||
{
|
||||
var playlistItem = new StreamInfo
|
||||
{
|
||||
ItemId = options.ItemId,
|
||||
MediaType = DlnaProfileType.Video,
|
||||
MediaSourceId = item.Id
|
||||
};
|
||||
|
||||
var audioStream = item.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
var videoStream = item.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
|
||||
|
||||
if (IsEligibleForDirectPlay(item, options))
|
||||
{
|
||||
// See if it can be direct played
|
||||
var directPlay = options.Profile.DirectPlayProfiles
|
||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType && IsVideoProfileSupported(i, item, videoStream, audioStream));
|
||||
|
||||
if (directPlay != null)
|
||||
{
|
||||
var videoCodec = videoStream == null ? null : videoStream.Codec;
|
||||
|
||||
// Make sure video codec profiles are satisfied
|
||||
if (!string.IsNullOrEmpty(videoCodec) && options.Profile.CodecProfiles.Where(i => i.Type == CodecType.Video && i.ContainsCodec(videoCodec))
|
||||
.All(i => AreConditionsSatisfied(i.Conditions, item.Path, videoStream, audioStream)))
|
||||
{
|
||||
var audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||
|
||||
// Make sure audio codec profiles are satisfied
|
||||
if (string.IsNullOrEmpty(audioCodec) || options.Profile.CodecProfiles.Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec))
|
||||
.All(i => AreConditionsSatisfied(i.Conditions, item.Path, videoStream, audioStream)))
|
||||
{
|
||||
playlistItem.IsDirectStream = true;
|
||||
playlistItem.Container = item.Container;
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Can't direct play, find the transcoding profile
|
||||
var transcodingProfile = options.Profile.TranscodingProfiles
|
||||
.FirstOrDefault(i => i.Type == playlistItem.MediaType);
|
||||
|
||||
if (transcodingProfile != null)
|
||||
{
|
||||
playlistItem.IsDirectStream = false;
|
||||
playlistItem.Container = transcodingProfile.Container;
|
||||
playlistItem.AudioCodec = transcodingProfile.AudioCodec.Split(',').FirstOrDefault();
|
||||
playlistItem.VideoCodec = transcodingProfile.VideoCodec;
|
||||
|
||||
var videoTranscodingConditions = options.Profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.Video && i.ContainsCodec(transcodingProfile.VideoCodec))
|
||||
.Take(1)
|
||||
.SelectMany(i => i.Conditions);
|
||||
|
||||
ApplyTranscodingConditions(playlistItem, videoTranscodingConditions);
|
||||
|
||||
var audioTranscodingConditions = options.Profile.CodecProfiles
|
||||
.Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(transcodingProfile.AudioCodec))
|
||||
.Take(1)
|
||||
.SelectMany(i => i.Conditions);
|
||||
|
||||
ApplyTranscodingConditions(playlistItem, audioTranscodingConditions);
|
||||
}
|
||||
|
||||
return playlistItem;
|
||||
}
|
||||
|
||||
private bool IsEligibleForDirectPlay(MediaSourceInfo item, VideoOptions options)
|
||||
{
|
||||
if (options.SubtitleStreamIndex.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.AudioStreamIndex.HasValue &&
|
||||
item.MediaStreams.Count(i => i.Type == MediaStreamType.Audio) > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ValidateInput(VideoOptions options)
|
||||
{
|
||||
ValidateAudioInput(options);
|
||||
|
||||
if (options.AudioStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
|
||||
{
|
||||
throw new ArgumentException("MediaSourceId is required when a specific audio stream is requested");
|
||||
}
|
||||
|
||||
if (options.SubtitleStreamIndex.HasValue && string.IsNullOrEmpty(options.MediaSourceId))
|
||||
{
|
||||
throw new ArgumentException("MediaSourceId is required when a specific subtitle stream is requested");
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateAudioInput(AudioOptions options)
|
||||
{
|
||||
if (string.IsNullOrEmpty(options.ItemId))
|
||||
{
|
||||
throw new ArgumentException("ItemId is required");
|
||||
}
|
||||
if (string.IsNullOrEmpty(options.DeviceId))
|
||||
{
|
||||
throw new ArgumentException("DeviceId is required");
|
||||
}
|
||||
if (options.Profile == null)
|
||||
{
|
||||
throw new ArgumentException("Profile is required");
|
||||
}
|
||||
if (options.MediaSources == null)
|
||||
{
|
||||
throw new ArgumentException("MediaSources is required");
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyTranscodingConditions(StreamInfo item, IEnumerable<ProfileCondition> conditions)
|
||||
{
|
||||
foreach (var condition in conditions
|
||||
.Where(i => !string.IsNullOrEmpty(i.Value)))
|
||||
{
|
||||
var value = condition.Value;
|
||||
|
||||
switch (condition.Property)
|
||||
{
|
||||
case ProfileConditionValue.AudioBitrate:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.AudioBitrate = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.AudioChannels:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.MaxAudioChannels = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.AudioProfile:
|
||||
case ProfileConditionValue.Has64BitOffsets:
|
||||
case ProfileConditionValue.VideoBitDepth:
|
||||
case ProfileConditionValue.VideoProfile:
|
||||
{
|
||||
// Not supported yet
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.Height:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.MaxHeight = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.VideoBitrate:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.VideoBitrate = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.VideoFramerate:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.MaxFramerate = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.VideoLevel:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.VideoLevel = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ProfileConditionValue.Width:
|
||||
{
|
||||
int num;
|
||||
if (int.TryParse(value, NumberStyles.Any, _usCulture, out num))
|
||||
{
|
||||
item.MaxWidth = num;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentException("Unrecognized ProfileConditionValue");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAudioProfileSupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream audioStream)
|
||||
{
|
||||
if (profile.Container.Length > 0)
|
||||
{
|
||||
// Check container type
|
||||
var mediaContainer = item.Container ?? string.Empty;
|
||||
if (!profile.GetContainers().Any(i => string.Equals(i, mediaContainer, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsVideoProfileSupported(DirectPlayProfile profile, MediaSourceInfo item, MediaStream videoStream, MediaStream audioStream)
|
||||
{
|
||||
// Only plain video files can be direct played
|
||||
if (item.VideoType != VideoType.VideoFile)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile.Container.Length > 0)
|
||||
{
|
||||
// Check container type
|
||||
var mediaContainer = item.Container ?? string.Empty;
|
||||
if (!profile.GetContainers().Any(i => string.Equals(i, mediaContainer, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check video codec
|
||||
var videoCodecs = profile.GetVideoCodecs();
|
||||
if (videoCodecs.Count > 0)
|
||||
{
|
||||
var videoCodec = videoStream == null ? null : videoStream.Codec;
|
||||
if (string.IsNullOrEmpty(videoCodec) || !videoCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var audioCodecs = profile.GetAudioCodecs();
|
||||
if (audioCodecs.Count > 0)
|
||||
{
|
||||
// Check audio codecs
|
||||
var audioCodec = audioStream == null ? null : audioStream.Codec;
|
||||
if (string.IsNullOrEmpty(audioCodec) || !audioCodecs.Contains(audioCodec, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool AreConditionsSatisfied(IEnumerable<ProfileCondition> conditions, string mediaPath, MediaStream videoStream, MediaStream audioStream)
|
||||
{
|
||||
return conditions.All(i => IsConditionSatisfied(i, mediaPath, videoStream, audioStream));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is condition satisfied] [the specified condition].
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition.</param>
|
||||
/// <param name="mediaPath">The media path.</param>
|
||||
/// <param name="videoStream">The video stream.</param>
|
||||
/// <param name="audioStream">The audio stream.</param>
|
||||
/// <returns><c>true</c> if [is condition satisfied] [the specified condition]; otherwise, <c>false</c>.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">Unexpected ProfileConditionType</exception>
|
||||
private bool IsConditionSatisfied(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream)
|
||||
{
|
||||
if (condition.Property == ProfileConditionValue.Has64BitOffsets)
|
||||
{
|
||||
// TODO: Determine how to evaluate this
|
||||
}
|
||||
|
||||
if (condition.Property == ProfileConditionValue.VideoProfile)
|
||||
{
|
||||
var profile = videoStream == null ? null : videoStream.Profile;
|
||||
|
||||
if (!string.IsNullOrEmpty(profile))
|
||||
{
|
||||
switch (condition.Condition)
|
||||
{
|
||||
case ProfileConditionType.Equals:
|
||||
return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
|
||||
case ProfileConditionType.NotEquals:
|
||||
return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected ProfileConditionType");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (condition.Property == ProfileConditionValue.AudioProfile)
|
||||
{
|
||||
var profile = audioStream == null ? null : audioStream.Profile;
|
||||
|
||||
if (!string.IsNullOrEmpty(profile))
|
||||
{
|
||||
switch (condition.Condition)
|
||||
{
|
||||
case ProfileConditionType.Equals:
|
||||
return string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
|
||||
case ProfileConditionType.NotEquals:
|
||||
return !string.Equals(profile, condition.Value, StringComparison.OrdinalIgnoreCase);
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected ProfileConditionType");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
var actualValue = GetConditionValue(condition, mediaPath, videoStream, audioStream);
|
||||
|
||||
if (actualValue.HasValue)
|
||||
{
|
||||
long expected;
|
||||
if (long.TryParse(condition.Value, NumberStyles.Any, _usCulture, out expected))
|
||||
{
|
||||
switch (condition.Condition)
|
||||
{
|
||||
case ProfileConditionType.Equals:
|
||||
return actualValue.Value == expected;
|
||||
case ProfileConditionType.GreaterThanEqual:
|
||||
return actualValue.Value >= expected;
|
||||
case ProfileConditionType.LessThanEqual:
|
||||
return actualValue.Value <= expected;
|
||||
case ProfileConditionType.NotEquals:
|
||||
return actualValue.Value != expected;
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected ProfileConditionType");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Value doesn't exist in metadata. Fail it if required.
|
||||
return !condition.IsRequired;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the condition value.
|
||||
/// </summary>
|
||||
/// <param name="condition">The condition.</param>
|
||||
/// <param name="mediaPath">The media path.</param>
|
||||
/// <param name="videoStream">The video stream.</param>
|
||||
/// <param name="audioStream">The audio stream.</param>
|
||||
/// <returns>System.Nullable{System.Int64}.</returns>
|
||||
/// <exception cref="System.InvalidOperationException">Unexpected Property</exception>
|
||||
private long? GetConditionValue(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream)
|
||||
{
|
||||
switch (condition.Property)
|
||||
{
|
||||
case ProfileConditionValue.AudioBitrate:
|
||||
return audioStream == null ? null : audioStream.BitRate;
|
||||
case ProfileConditionValue.AudioChannels:
|
||||
return audioStream == null ? null : audioStream.Channels;
|
||||
case ProfileConditionValue.VideoBitrate:
|
||||
return videoStream == null ? null : videoStream.BitRate;
|
||||
case ProfileConditionValue.VideoFramerate:
|
||||
return videoStream == null ? null : (ConvertToLong(videoStream.AverageFrameRate ?? videoStream.RealFrameRate));
|
||||
case ProfileConditionValue.Height:
|
||||
return videoStream == null ? null : videoStream.Height;
|
||||
case ProfileConditionValue.Width:
|
||||
return videoStream == null ? null : videoStream.Width;
|
||||
case ProfileConditionValue.VideoLevel:
|
||||
return videoStream == null ? null : ConvertToLong(videoStream.Level);
|
||||
default:
|
||||
throw new InvalidOperationException("Unexpected Property");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts to long.
|
||||
/// </summary>
|
||||
/// <param name="val">The value.</param>
|
||||
/// <returns>System.Nullable{System.Int64}.</returns>
|
||||
private long? ConvertToLong(float? val)
|
||||
{
|
||||
return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts to long.
|
||||
/// </summary>
|
||||
/// <param name="val">The value.</param>
|
||||
/// <returns>System.Nullable{System.Int64}.</returns>
|
||||
private long? ConvertToLong(double? val)
|
||||
{
|
||||
return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
124
MediaBrowser.Model/Dlna/StreamInfo.cs
Normal file
124
MediaBrowser.Model/Dlna/StreamInfo.cs
Normal file
@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using MediaBrowser.Model.Dto;
|
||||
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
/// <summary>
|
||||
/// Class StreamInfo.
|
||||
/// </summary>
|
||||
public class StreamInfo
|
||||
{
|
||||
public string ItemId { get; set; }
|
||||
|
||||
public string MediaSourceId { get; set; }
|
||||
|
||||
public bool IsDirectStream { get; set; }
|
||||
|
||||
public DlnaProfileType MediaType { get; set; }
|
||||
|
||||
public string Container { get; set; }
|
||||
|
||||
public long StartPositionTicks { get; set; }
|
||||
|
||||
public string VideoCodec { get; set; }
|
||||
|
||||
public string AudioCodec { get; set; }
|
||||
|
||||
public int? AudioStreamIndex { get; set; }
|
||||
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
|
||||
public int? MaxAudioChannels { get; set; }
|
||||
|
||||
public int? AudioBitrate { get; set; }
|
||||
|
||||
public int? VideoBitrate { get; set; }
|
||||
|
||||
public int? VideoLevel { get; set; }
|
||||
|
||||
public int? MaxWidth { get; set; }
|
||||
public int? MaxHeight { get; set; }
|
||||
|
||||
public int? MaxFramerate { get; set; }
|
||||
|
||||
public string DeviceProfileId { get; set; }
|
||||
public string DeviceId { get; set; }
|
||||
|
||||
public string ToUrl(string baseUrl)
|
||||
{
|
||||
return ToDlnaUrl(baseUrl);
|
||||
}
|
||||
|
||||
public string ToDlnaUrl(string baseUrl)
|
||||
{
|
||||
if (string.IsNullOrEmpty(baseUrl))
|
||||
{
|
||||
throw new ArgumentNullException(baseUrl);
|
||||
}
|
||||
|
||||
var dlnaCommand = BuildDlnaParam(this);
|
||||
|
||||
var extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container;
|
||||
|
||||
baseUrl = baseUrl.TrimEnd('/');
|
||||
|
||||
if (MediaType == DlnaProfileType.Audio)
|
||||
{
|
||||
return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand);
|
||||
}
|
||||
return string.Format("{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, dlnaCommand);
|
||||
}
|
||||
|
||||
private static string BuildDlnaParam(StreamInfo item)
|
||||
{
|
||||
var usCulture = new CultureInfo("en-US");
|
||||
|
||||
var list = new List<string>
|
||||
{
|
||||
item.DeviceProfileId ?? string.Empty,
|
||||
item.DeviceId ?? string.Empty,
|
||||
item.MediaSourceId ?? string.Empty,
|
||||
(item.IsDirectStream).ToString().ToLower(),
|
||||
item.VideoCodec ?? string.Empty,
|
||||
item.AudioCodec ?? string.Empty,
|
||||
item.AudioStreamIndex.HasValue ? item.AudioStreamIndex.Value.ToString(usCulture) : string.Empty,
|
||||
item.SubtitleStreamIndex.HasValue ? item.SubtitleStreamIndex.Value.ToString(usCulture) : string.Empty,
|
||||
item.VideoBitrate.HasValue ? item.VideoBitrate.Value.ToString(usCulture) : string.Empty,
|
||||
item.AudioBitrate.HasValue ? item.AudioBitrate.Value.ToString(usCulture) : string.Empty,
|
||||
item.MaxAudioChannels.HasValue ? item.MaxAudioChannels.Value.ToString(usCulture) : string.Empty,
|
||||
item.MaxFramerate.HasValue ? item.MaxFramerate.Value.ToString(usCulture) : string.Empty,
|
||||
item.MaxWidth.HasValue ? item.MaxWidth.Value.ToString(usCulture) : string.Empty,
|
||||
item.MaxHeight.HasValue ? item.MaxHeight.Value.ToString(usCulture) : string.Empty,
|
||||
item.StartPositionTicks.ToString(usCulture),
|
||||
item.VideoLevel.HasValue ? item.VideoLevel.Value.ToString(usCulture) : string.Empty
|
||||
};
|
||||
|
||||
return string.Format("Params={0}", string.Join(";", list.ToArray()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class AudioOptions.
|
||||
/// </summary>
|
||||
public class AudioOptions
|
||||
{
|
||||
public string ItemId { get; set; }
|
||||
public List<MediaSourceInfo> MediaSources { get; set; }
|
||||
public int? MaxBitrateSetting { get; set; }
|
||||
public DeviceProfile Profile { get; set; }
|
||||
public string MediaSourceId { get; set; }
|
||||
public string DeviceId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Class VideoOptions.
|
||||
/// </summary>
|
||||
public class VideoOptions : AudioOptions
|
||||
{
|
||||
public int? AudioStreamIndex { get; set; }
|
||||
public int? SubtitleStreamIndex { get; set; }
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.Dlna
|
||||
namespace MediaBrowser.Model.Dlna
|
||||
{
|
||||
public class TranscodingProfile
|
||||
{
|
||||
@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.Dlna
|
||||
|
||||
public List<string> GetAudioCodecs()
|
||||
{
|
||||
return (AudioCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
|
||||
return (AudioCodec ?? string.Empty).Split(',').Where(i => !string.IsNullOrEmpty(i)).ToList();
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ namespace MediaBrowser.Model.Dto
|
||||
|
||||
public string Path { get; set; }
|
||||
|
||||
public string Container { get; set; }
|
||||
|
||||
public LocationType LocationType { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
@ -1,20 +0,0 @@
|
||||
|
||||
namespace MediaBrowser.Model.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Class ModelExtensions
|
||||
/// </summary>
|
||||
static class ModelExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Values the or default.
|
||||
/// </summary>
|
||||
/// <param name="str">The STR.</param>
|
||||
/// <param name="def">The def.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
internal static string ValueOrDefault(this string str, string def = "")
|
||||
{
|
||||
return string.IsNullOrEmpty(str) ? def : str;
|
||||
}
|
||||
}
|
||||
}
|
@ -66,7 +66,16 @@
|
||||
<Compile Include="Configuration\MetadataPlugin.cs" />
|
||||
<Compile Include="Configuration\MetadataOptions.cs" />
|
||||
<Compile Include="Configuration\ServerConfiguration.cs" />
|
||||
<Compile Include="Dlna\CodecProfile.cs" />
|
||||
<Compile Include="Dlna\ContainerProfile.cs" />
|
||||
<Compile Include="Dlna\DeviceIdentification.cs" />
|
||||
<Compile Include="Dlna\DeviceProfile.cs" />
|
||||
<Compile Include="Dlna\DeviceProfileInfo.cs" />
|
||||
<Compile Include="Dlna\DirectPlayProfile.cs" />
|
||||
<Compile Include="Dlna\ResponseProfile.cs" />
|
||||
<Compile Include="Dlna\StreamBuilder.cs" />
|
||||
<Compile Include="Dlna\StreamInfo.cs" />
|
||||
<Compile Include="Dlna\TranscodingProfile.cs" />
|
||||
<Compile Include="Drawing\ImageOutputFormat.cs" />
|
||||
<Compile Include="Dto\BaseItemPerson.cs" />
|
||||
<Compile Include="Dto\ChapterInfoDto.cs" />
|
||||
@ -155,7 +164,6 @@
|
||||
<Compile Include="Entities\ParentalRating.cs" />
|
||||
<Compile Include="Dto\StreamOptions.cs" />
|
||||
<Compile Include="Entities\VirtualFolderInfo.cs" />
|
||||
<Compile Include="Extensions\ModelExtensions.cs" />
|
||||
<Compile Include="IO\IZipClient.cs" />
|
||||
<Compile Include="Logging\ILogger.cs" />
|
||||
<Compile Include="Logging\LogSeverity.cs" />
|
||||
@ -223,6 +231,7 @@
|
||||
<HintPath>..\packages\PropertyChanged.Fody.1.41.0.0\Lib\NET35\PropertyChanged.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="System.XML" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<PropertyGroup>
|
||||
|
@ -1,5 +1,4 @@
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using System;
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MediaBrowser.Model.Updates
|
||||
@ -39,7 +38,18 @@ namespace MediaBrowser.Model.Updates
|
||||
[IgnoreDataMember]
|
||||
public Version version
|
||||
{
|
||||
get { return _version ?? (_version = new Version(versionStr.ValueOrDefault("0.0.0.1"))); }
|
||||
get { return _version ?? (_version = new Version(ValueOrDefault(versionStr, "0.0.0.1"))); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Values the or default.
|
||||
/// </summary>
|
||||
/// <param name="str">The STR.</param>
|
||||
/// <param name="def">The def.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
private static string ValueOrDefault(string str, string def = "")
|
||||
{
|
||||
return string.IsNullOrEmpty(str) ? def : str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -34,7 +34,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
|
||||
{
|
||||
var collectionType = args.GetCollectionType();
|
||||
|
||||
if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))
|
||||
if (string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.IsNullOrWhiteSpace(collectionType))
|
||||
{
|
||||
return new Controller.Entities.Audio.Audio();
|
||||
}
|
||||
|
@ -1,31 +1 @@
|
||||
{
|
||||
"SettingsSaved": "Einstellungen gespeichert",
|
||||
"AddUser": "Benutzer hinzuf\u00fcgen",
|
||||
"Users": "Benutzer",
|
||||
"Delete": "L\u00f6schen",
|
||||
"Administrator": "Administrator",
|
||||
"Password": "Passwort",
|
||||
"CreatePassword": "Passwort erstellen",
|
||||
"DeleteImage": "Bild l\u00f6schen",
|
||||
"DeleteImageConfirmation": "M\u00f6chten Sie das Bild wirklich l\u00f6schen?",
|
||||
"FileReadCancelled": "Das Einlesen der Datei wurde abgebrochen.",
|
||||
"FileNotFound": "Datei nicht gefunden",
|
||||
"FileReadError": "Beim Lesen der Datei ist ein Fehler aufgetreten.",
|
||||
"DeleteUser": "Benutzer l\u00f6schen",
|
||||
"DeleteUserConfirmation": "M\u00f6chten Sie {0} wirklich l\u00f6schen?",
|
||||
"PasswordResetHeader": "Passwort zur\u00fccksetzen",
|
||||
"PasswordResetComplete": "Das Passwort wurde zur\u00fcckgesetzt.",
|
||||
"PasswordResetConfirmation": "M\u00f6chten Sie das Passwort wirklich zur\u00fccksetzen?",
|
||||
"PasswordSaved": "Passwort gespeichert",
|
||||
"PasswordMatchError": "Passwort und Passwortbest\u00e4tigung stimmen nicht \u00fcberein.",
|
||||
"OptionOff": "Aus",
|
||||
"OptionOn": "Ein",
|
||||
"OptionRelease": "Release",
|
||||
"OptionBeta": "Beta",
|
||||
"OptionDev": "Dev",
|
||||
"UninstallPluginHeader": "Deinstalliere Plugin",
|
||||
"UninstallPluginConfirmation": "M\u00f6chten Sie {0} wirklich deinstallieren?",
|
||||
"NoPluginConfigurationMessage": "Bei diesem Plugin kann nichts eingestellt werden.",
|
||||
"NoPluginsInstalledMessage": "Sie haben keine Plugins installiert.",
|
||||
"BrowsePluginCatalogMessage": "Durchsuchen Sie unsere Bibliothek um alle verf\u00fcgbaren Plugins anzuzeigen."
|
||||
}
|
||||
{"SettingsSaved":"Einstellungen gespeichert","AddUser":"Benutzer hinzuf\u00fcgen","Users":"Benutzer","Delete":"L\u00f6schen","Administrator":"Administrator","Password":"Passwort","DeleteImage":"Bild l\u00f6schen","DeleteImageConfirmation":"M\u00f6chten Sie das Bild wirklich l\u00f6schen?","FileReadCancelled":"Das Einlesen der Datei wurde abgebrochen.","FileNotFound":"Datei nicht gefunden","FileReadError":"Beim Lesen der Datei ist ein Fehler aufgetreten.","DeleteUser":"Benutzer l\u00f6schen","DeleteUserConfirmation":"M\u00f6chten Sie {0} wirklich l\u00f6schen?","PasswordResetHeader":"Passwort zur\u00fccksetzen","PasswordResetComplete":"Das Passwort wurde zur\u00fcckgesetzt.","PasswordResetConfirmation":"M\u00f6chten Sie das Passwort wirklich zur\u00fccksetzen?","PasswordSaved":"Passwort gespeichert","PasswordMatchError":"Passwort und Passwortbest\u00e4tigung stimmen nicht \u00fcberein.","OptionOff":"Aus","OptionOn":"Ein","OptionRelease":"Release","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Deinstalliere Plugin","UninstallPluginConfirmation":"M\u00f6chten Sie {0} wirklich deinstallieren?","NoPluginConfigurationMessage":"Bei diesem Plugin kann nichts eingestellt werden.","NoPluginsInstalledMessage":"Sie haben keine Plugins installiert.","BrowsePluginCatalogMessage":"Durchsuchen Sie unsere Bibliothek um alle verf\u00fcgbaren Plugins anzuzeigen."}
|
@ -1,31 +1 @@
|
||||
{
|
||||
"SettingsSaved": "Settings saved.",
|
||||
"AddUser": "Add User",
|
||||
"Users": "Users",
|
||||
"Delete": "Delete",
|
||||
"Administrator": "Administrator",
|
||||
"Password": "Password",
|
||||
"CreatePassword": "Create Password",
|
||||
"DeleteImage": "Delete Image",
|
||||
"DeleteImageConfirmation": "Are you sure you wish to delete this image?",
|
||||
"FileReadCancelled": "The file read has been cancelled.",
|
||||
"FileNotFound": "File not found.",
|
||||
"FileReadError": "An error occurred while reading the file.",
|
||||
"DeleteUser": "Delete User",
|
||||
"DeleteUserConfirmation": "Are you sure you wish to delete {0}?",
|
||||
"PasswordResetHeader": "Password Reset",
|
||||
"PasswordResetComplete": "The password has been reset.",
|
||||
"PasswordResetConfirmation": "Are you sure you wish to reset the password?",
|
||||
"PasswordSaved": "Password saved.",
|
||||
"PasswordMatchError": "Password and password confirmation must match.",
|
||||
"OptionOff": "Off",
|
||||
"OptionOn": "On",
|
||||
"OptionRelease": "Release",
|
||||
"OptionBeta": "Beta",
|
||||
"OptionDev": "Dev",
|
||||
"UninstallPluginHeader": "Uninstall Plugin",
|
||||
"UninstallPluginConfirmation": "Are you sure you wish to uninstall {0}?",
|
||||
"NoPluginConfigurationMessage": "This plugin has nothing to configure.",
|
||||
"NoPluginsInstalledMessage": "You have no plugins installed.",
|
||||
"BrowsePluginCatalogMessage": "Browse our plugin catalog to view available plugins."
|
||||
}
|
||||
{"SettingsSaved":"Settings saved.","AddUser":"Add User","Users":"Users","Delete":"Delete","Administrator":"Administrator","Password":"Password","DeleteImage":"Delete Image","DeleteImageConfirmation":"Are you sure you wish to delete this image?","FileReadCancelled":"The file read has been cancelled.","FileNotFound":"File not found.","FileReadError":"An error occurred while reading the file.","DeleteUser":"Delete User","DeleteUserConfirmation":"Are you sure you wish to delete {0}?","PasswordResetHeader":"Password Reset","PasswordResetComplete":"The password has been reset.","PasswordResetConfirmation":"Are you sure you wish to reset the password?","PasswordSaved":"Password saved.","PasswordMatchError":"Password and password confirmation must match.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Release","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Uninstall Plugin","UninstallPluginConfirmation":"Are you sure you wish to uninstall {0}?","NoPluginConfigurationMessage":"This plugin has nothing to configure.","NoPluginsInstalledMessage":"You have no plugins installed.","BrowsePluginCatalogMessage":"Browse our plugin catalog to view available plugins."}
|
@ -1,31 +1 @@
|
||||
{
|
||||
"SettingsSaved": "Configuracion guardada",
|
||||
"AddUser": "Agregar usuario",
|
||||
"Users": "Usuarios",
|
||||
"Delete": "Borrar",
|
||||
"Administrator": "Administrador",
|
||||
"Password": "Contrase\u00f1a",
|
||||
"CreatePassword": "Crear Contrase\u00f1a",
|
||||
"DeleteImage": "Borrar Imagen",
|
||||
"DeleteImageConfirmation": "Esta seguro que desea borrar esta imagen?",
|
||||
"FileReadCancelled": "La lectura del archivo se ha cancelado.",
|
||||
"FileNotFound": "Archivo no encontrado.",
|
||||
"FileReadError": "Se encontr\u00f3 un error al leer el archivo.",
|
||||
"DeleteUser": "Borrar Usuario",
|
||||
"DeleteUserConfirmation": "Esta seguro que desea eliminar a {0}?",
|
||||
"PasswordResetHeader": "Restablecer contrase\u00f1a",
|
||||
"PasswordResetComplete": "La contrase\u00f1a se ha restablecido.",
|
||||
"PasswordResetConfirmation": "Esta seguro que desea restablecer la contrase\u00f1a?",
|
||||
"PasswordSaved": "Contrase\u00f1a guardada.",
|
||||
"PasswordMatchError": "La contrase\u00f1a y la confirmaci\u00f3n de la contrase\u00f1a deben de ser iguales.",
|
||||
"OptionOff": "Apagado",
|
||||
"OptionOn": "Prendido",
|
||||
"OptionRelease": "Liberar",
|
||||
"OptionBeta": "Beta",
|
||||
"OptionDev": "Desarrollo",
|
||||
"UninstallPluginHeader": "Desinstalar Plugin",
|
||||
"UninstallPluginConfirmation": "Esta seguro que desea desinstalar {0}?",
|
||||
"NoPluginConfigurationMessage": "El plugin no requiere configuraci\u00f3n",
|
||||
"NoPluginsInstalledMessage": "No tiene plugins instalados.",
|
||||
"BrowsePluginCatalogMessage": "Navegar el catalogo de plugins para ver los plugins disponibles."
|
||||
}
|
||||
{"SettingsSaved":"Configuracion guardada","AddUser":"Agregar usuario","Users":"Usuarios","Delete":"Borrar","Administrator":"Administrador","Password":"Contrase\u00f1a","DeleteImage":"Borrar Imagen","DeleteImageConfirmation":"Esta seguro que desea borrar esta imagen?","FileReadCancelled":"La lectura del archivo se ha cancelado.","FileNotFound":"Archivo no encontrado.","FileReadError":"Se encontr\u00f3 un error al leer el archivo.","DeleteUser":"Borrar Usuario","DeleteUserConfirmation":"Esta seguro que desea eliminar a {0}?","PasswordResetHeader":"Restablecer contrase\u00f1a","PasswordResetComplete":"La contrase\u00f1a se ha restablecido.","PasswordResetConfirmation":"Esta seguro que desea restablecer la contrase\u00f1a?","PasswordSaved":"Contrase\u00f1a guardada.","PasswordMatchError":"La contrase\u00f1a y la confirmaci\u00f3n de la contrase\u00f1a deben de ser iguales.","OptionOff":"Apagado","OptionOn":"Prendido","OptionRelease":"Liberar","OptionBeta":"Beta","OptionDev":"Desarrollo","UninstallPluginHeader":"Desinstalar Plugin","UninstallPluginConfirmation":"Esta seguro que desea desinstalar {0}?","NoPluginConfigurationMessage":"El plugin no requiere configuraci\u00f3n","NoPluginsInstalledMessage":"No tiene plugins instalados.","BrowsePluginCatalogMessage":"Navegar el catalogo de plugins para ver los plugins disponibles."}
|
@ -1,31 +1 @@
|
||||
{
|
||||
"SettingsSaved": "Param\u00e8tres sauvegard\u00e9s.",
|
||||
"AddUser": "Ajout\u00e9 Usager",
|
||||
"Users": "Usagers",
|
||||
"Delete": "Supprimer",
|
||||
"Administrator": "Administrateur",
|
||||
"Password": "Mot de passe",
|
||||
"CreatePassword": "Cr\u00e9er mot de passe",
|
||||
"DeleteImage": "Supprimer Image",
|
||||
"DeleteImageConfirmation": "\u00cates-vous s\u00fbr de vouloir supprimer l'image?",
|
||||
"FileReadCancelled": "La lecture du fichier a \u00e9t\u00e9 annul\u00e9e.",
|
||||
"FileNotFound": "Fichier non trouv\u00e9",
|
||||
"FileReadError": "Un erreur est survenue pendant la lecture du fichier.",
|
||||
"DeleteUser": "Supprimer Usager",
|
||||
"DeleteUserConfirmation": "\u00cates-vous s\u00fbr de vouloir supprimer {0}?",
|
||||
"PasswordResetHeader": "Red\u00e9marrage du mot de passe",
|
||||
"PasswordResetComplete": "Le mot de passe a \u00e9t\u00e9 red\u00e9marr\u00e9.",
|
||||
"PasswordResetConfirmation": "\u00cates-vous s\u00fbr de vouloir red\u00e9marrer le mot de passe?",
|
||||
"PasswordSaved": "Mot de passe sauvegard\u00e9.",
|
||||
"PasswordMatchError": "Mot de passe et confirmation de mot de passe doivent correspondre.",
|
||||
"OptionOff": "Off",
|
||||
"OptionOn": "On",
|
||||
"OptionRelease": "Lancement",
|
||||
"OptionBeta": "Beta",
|
||||
"OptionDev": "Dev",
|
||||
"UninstallPluginHeader": "D\u00e9sinstaller module d'extention",
|
||||
"UninstallPluginConfirmation": "\u00cates-vous s\u00fbr de vouloir d\u00e9sinstaller {0}?",
|
||||
"NoPluginConfigurationMessage": "Ce module d'extension n'a rien \u00e0 configurer.",
|
||||
"NoPluginsInstalledMessage": "Vous n'avez aucun module d'extension install\u00e9.",
|
||||
"BrowsePluginCatalogMessage": "Explorer notre catalogue de modules d'extension pour voir ce qui est disponible."
|
||||
}
|
||||
{"SettingsSaved":"Param\u00e8tres sauvegard\u00e9s.","AddUser":"Ajouter utilisateur","Users":"Utilisateur","Delete":"Supprimer","Administrator":"Administrateur","Password":"Mot de passe","DeleteImage":"Supprimer Image","DeleteImageConfirmation":"\u00cates-vous s\u00fbr de vouloir supprimer l'image?","FileReadCancelled":"La lecture du fichier a \u00e9t\u00e9 annul\u00e9e.","FileNotFound":"Fichier non trouv\u00e9","FileReadError":"Un erreur est survenue pendant la lecture du fichier.","DeleteUser":"Supprimer utilisateur","DeleteUserConfirmation":"\u00cates-vous s\u00fbr de vouloir supprimer {0}?","PasswordResetHeader":"R\u00e9initialisation du mot de passe","PasswordResetComplete":"Le mot de passe a \u00e9t\u00e9 r\u00e9initialis\u00e9.","PasswordResetConfirmation":"\u00cates-vous s\u00fbr de vouloir r\u00e9initialiser le mot de passe?","PasswordSaved":"Mot de passe sauvegard\u00e9.","PasswordMatchError":"Mot de passe et confirmation de mot de passe doivent correspondre.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Lancement","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"D\u00e9sinstaller Plug-in","UninstallPluginConfirmation":"\u00cates-vous s\u00fbr de vouloir d\u00e9sinstaller {0}?","NoPluginConfigurationMessage":"Ce module d'extension n'a rien \u00e0 configurer.","NoPluginsInstalledMessage":"Vous n'avez aucun module d'extension install\u00e9.","BrowsePluginCatalogMessage":"Explorer notre catalogue de modules d'extension pour voir ce qui est disponible."}
|
@ -0,0 +1 @@
|
||||
{"SettingsSaved":"Settaggi salvati.","AddUser":"Aggiungi utente","Users":"Utenti","Delete":"Elimina","Administrator":"Amministratore","Password":"Password","DeleteImage":"Elimina immagine","DeleteImageConfirmation":"Sei sicuro di voler eliminare questa immagine?","FileReadCancelled":"Il file letto \u00e8 stato cancellato.","FileNotFound":"File non trovato","FileReadError":"Errore durante la lettura del file.","DeleteUser":"Elimina utente","DeleteUserConfirmation":"Sei sicuro di voler eliminare {0}?","PasswordResetHeader":"Ripristina Password","PasswordResetComplete":"la password \u00e8 stata ripristinata.","PasswordResetConfirmation":"Sei sicuro di voler ripristinare la password?","PasswordSaved":"Password salvata.","PasswordMatchError":"Le password non coincidono.","OptionOff":"Spegni","OptionOn":"Accendi","OptionRelease":"Versione","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Disinstalla Plugin","UninstallPluginConfirmation":"Sei sicuro di voler Disinstallare {0}?","NoPluginConfigurationMessage":"Questo Plugin non \u00e8 stato configurato.","NoPluginsInstalledMessage":"non ci sono Plugins installati.","BrowsePluginCatalogMessage":"Sfoglia il catalogo dei Plugins."}
|
@ -5,7 +5,6 @@
|
||||
"Delete": "Delete",
|
||||
"Administrator": "Administrator",
|
||||
"Password": "Password",
|
||||
"CreatePassword": "Create Password",
|
||||
"DeleteImage": "Delete Image",
|
||||
"DeleteImageConfirmation": "Are you sure you wish to delete this image?",
|
||||
"FileReadCancelled": "The file read has been cancelled.",
|
||||
|
@ -1,31 +1 @@
|
||||
{
|
||||
"SettingsSaved": "Instellingen opgeslagen.",
|
||||
"AddUser": "Gebruiker toevoegen",
|
||||
"Users": "Gebruikers",
|
||||
"Delete": "Verwijderen",
|
||||
"Administrator": "Beheerder",
|
||||
"Password": "Wachtwoord",
|
||||
"CreatePassword": "Maak wachtwoord",
|
||||
"DeleteImage": "Verwijder afbeelding",
|
||||
"DeleteImageConfirmation": "Weet je zeker dat je deze afbeelding wilt verwijderen?",
|
||||
"FileReadCancelled": "Het lezen van het bestand is geannuleerd",
|
||||
"FileNotFound": "Bestand niet gevonden.",
|
||||
"FileReadError": "Er is een fout opgetreden bij het lezen van het bestand.",
|
||||
"DeleteUser": "Verwijder gebruiker",
|
||||
"DeleteUserConfirmation": "Weet je zeker dat je {0} wilt verwijderen?",
|
||||
"PasswordResetHeader": "Wachtwoord opnieuw instellen",
|
||||
"PasswordResetComplete": "Het wachtwoord is opnieuw ingesteld.",
|
||||
"PasswordResetConfirmation": "Weet je zeker dat je het wachtwoord opnieuw in wilt stellen?",
|
||||
"PasswordSaved": "Wachtwoord opgeslagen.",
|
||||
"PasswordMatchError": "Wachtwoord en wachtwoord bevestiging moeten hetzelfde zijn.",
|
||||
"OptionOff": "Uit",
|
||||
"OptionOn": "Aan",
|
||||
"OptionRelease": "Release",
|
||||
"OptionBeta": "Beta",
|
||||
"OptionDev": "Dev",
|
||||
"UninstallPluginHeader": "Deinstalleer Plugin",
|
||||
"UninstallPluginConfirmation": "Weet u zeker dat u {0} wilt deinstalleren?",
|
||||
"NoPluginConfigurationMessage": "Deze plugin heeft niets in te stellen",
|
||||
"NoPluginsInstalledMessage": "U heeft geen plugins geinstalleerd",
|
||||
"BrowsePluginCatalogMessage": "Blader door de Plugincatalogus voor beschikbare plugins."
|
||||
}
|
||||
{"SettingsSaved":"Instellingen opgeslagen.","AddUser":"Gebruiker toevoegen","Users":"Gebruikers","Delete":"Verwijderen","Administrator":"Beheerder","Password":"Wachtwoord","DeleteImage":"Verwijder afbeelding","DeleteImageConfirmation":"Weet je zeker dat je deze afbeelding wilt verwijderen?","FileReadCancelled":"Het lezen van het bestand is geannuleerd","FileNotFound":"Bestand niet gevonden.","FileReadError":"Er is een fout opgetreden bij het lezen van het bestand.","DeleteUser":"Verwijder gebruiker","DeleteUserConfirmation":"Weet je zeker dat je {0} wilt verwijderen?","PasswordResetHeader":"Wachtwoord opnieuw instellen","PasswordResetComplete":"Het wachtwoord is opnieuw ingesteld.","PasswordResetConfirmation":"Weet je zeker dat je het wachtwoord opnieuw in wilt stellen?","PasswordSaved":"Wachtwoord opgeslagen.","PasswordMatchError":"Wachtwoord en wachtwoord bevestiging moeten hetzelfde zijn.","OptionOff":"Uit","OptionOn":"Aan","OptionRelease":"Release","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Deinstalleer Plugin","UninstallPluginConfirmation":"Weet u zeker dat u {0} wilt deinstalleren?","NoPluginConfigurationMessage":"Deze plugin heeft niets in te stellen","NoPluginsInstalledMessage":"U heeft geen plugins geinstalleerd","BrowsePluginCatalogMessage":"Blader door de Plugincatalogus voor beschikbare plugins."}
|
@ -1,31 +1 @@
|
||||
{
|
||||
"SettingsSaved": "Prefer\u00eancias salvas.",
|
||||
"AddUser": "Adicionar Usu\u00e1rio",
|
||||
"Users": "Usu\u00e1rios",
|
||||
"Delete": "Apagar",
|
||||
"Administrator": "Administrador",
|
||||
"Password": "Senha",
|
||||
"CreatePassword": "Criar Senha",
|
||||
"DeleteImage": "Apagar Imagem",
|
||||
"DeleteImageConfirmation": "Tem certeza que deseja apagar esta imagem?",
|
||||
"FileReadCancelled": "A leitura do arquivo foi cancelada.",
|
||||
"FileNotFound": "Arquivo n\u00e3o encontrado.",
|
||||
"FileReadError": "Ocorreu um erro ao ler o arquivo.",
|
||||
"DeleteUser": "Apagar Usu\u00e1rio",
|
||||
"DeleteUserConfirmation": "Tem certeza que deseja apagar {0}?",
|
||||
"PasswordResetHeader": "Redefinir Senha",
|
||||
"PasswordResetComplete": "A senha foi redefinida.",
|
||||
"PasswordResetConfirmation": "Deseja realmente redefinir a senha?",
|
||||
"PasswordSaved": "Senha salva.",
|
||||
"PasswordMatchError": "A senha e confirma\u00e7\u00e3o da senha devem conferir.",
|
||||
"OptionOff": "Off",
|
||||
"OptionOn": "On",
|
||||
"OptionRelease": "Release",
|
||||
"OptionBeta": "Beta",
|
||||
"OptionDev": "Dev",
|
||||
"UninstallPluginHeader": "Desintalar Plugin",
|
||||
"UninstallPluginConfirmation": "Deseja realmente desinstalar {0}?",
|
||||
"NoPluginConfigurationMessage": "Este plugin n\u00e3o necessita configurar.",
|
||||
"NoPluginsInstalledMessage": "N\u00e3o existem plugins instalados.",
|
||||
"BrowsePluginCatalogMessage": "Navegue pelo cat\u00e1logo de plugins para ver os dispon\u00edveis."
|
||||
}
|
||||
{"SettingsSaved":"Prefer\u00eancias salvas.","AddUser":"Adicionar Usu\u00e1rio","Users":"Usu\u00e1rios","Delete":"Apagar","Administrator":"Administrador","Password":"Senha","DeleteImage":"Apagar Imagem","DeleteImageConfirmation":"Tem certeza que deseja apagar esta imagem?","FileReadCancelled":"A leitura do arquivo foi cancelada.","FileNotFound":"Arquivo n\u00e3o encontrado.","FileReadError":"Ocorreu um erro ao ler o arquivo.","DeleteUser":"Apagar Usu\u00e1rio","DeleteUserConfirmation":"Tem certeza que deseja apagar {0}?","PasswordResetHeader":"Redefinir Senha","PasswordResetComplete":"A senha foi redefinida.","PasswordResetConfirmation":"Deseja realmente redefinir a senha?","PasswordSaved":"Senha salva.","PasswordMatchError":"A senha e confirma\u00e7\u00e3o da senha devem conferir.","OptionOff":"Off","OptionOn":"On","OptionRelease":"Release","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Desintalar Plugin","UninstallPluginConfirmation":"Deseja realmente desinstalar {0}?","NoPluginConfigurationMessage":"Este plugin n\u00e3o necessita configurar.","NoPluginsInstalledMessage":"N\u00e3o existem plugins instalados.","BrowsePluginCatalogMessage":"Navegue pelo cat\u00e1logo de plugins para ver os dispon\u00edveis."}
|
@ -1,31 +1 @@
|
||||
{
|
||||
"SettingsSaved": "Configura\u00e7\u00f5es guardadas.",
|
||||
"AddUser": "Adicionar Utilizador",
|
||||
"Users": "Utilizadores",
|
||||
"Delete": "Apagar",
|
||||
"Administrator": "Administrador",
|
||||
"Password": "Senha",
|
||||
"CreatePassword": "Criar Senha",
|
||||
"DeleteImage": "Apagar Imagem",
|
||||
"DeleteImageConfirmation": "Tem a certeza que pretende apagar a imagem?",
|
||||
"FileReadCancelled": "A leitura do ficheiro foi cancelada.",
|
||||
"FileNotFound": "Ficheiro n\u00e3o encontrado",
|
||||
"FileReadError": "Ocorreu um erro ao ler o ficheiro.",
|
||||
"DeleteUser": "Apagar Utilizador",
|
||||
"DeleteUserConfirmation": "Tem a certeza que pretende apagar {0}?",
|
||||
"PasswordResetHeader": "Redefinir Senha",
|
||||
"PasswordResetComplete": "A senha foi redefinida.",
|
||||
"PasswordResetConfirmation": "Tem a certeza que pretende redefinir a senha?",
|
||||
"PasswordSaved": "Senha guardada.",
|
||||
"PasswordMatchError": "A senha e a confirma\u00e7\u00e3o da senha devem coincidir.",
|
||||
"OptionOff": "Desligado",
|
||||
"OptionOn": "Ligado",
|
||||
"OptionRelease": "Final",
|
||||
"OptionBeta": "Beta",
|
||||
"OptionDev": "Dev",
|
||||
"UninstallPluginHeader": "Desinstalar extens\u00e3o",
|
||||
"UninstallPluginConfirmation": "Tem a certeza que pretende desinstalar {0}?",
|
||||
"NoPluginConfigurationMessage": "Esta extens\u00e3o n\u00e3o \u00e9 configur\u00e1vel.",
|
||||
"NoPluginsInstalledMessage": "N\u00e3o tem extens\u00f5es instaladas.",
|
||||
"BrowsePluginCatalogMessage": "Navegue o nosso cat\u00e1logo de extens\u00f5es para ver as extens\u00f5es dispon\u00edveis."
|
||||
}
|
||||
{"SettingsSaved":"Configura\u00e7\u00f5es guardadas.","AddUser":"Adicionar Utilizador","Users":"Utilizadores","Delete":"Apagar","Administrator":"Administrador","Password":"Senha","DeleteImage":"Apagar Imagem","DeleteImageConfirmation":"Tem a certeza que pretende apagar a imagem?","FileReadCancelled":"A leitura do ficheiro foi cancelada.","FileNotFound":"Ficheiro n\u00e3o encontrado","FileReadError":"Ocorreu um erro ao ler o ficheiro.","DeleteUser":"Apagar Utilizador","DeleteUserConfirmation":"Tem a certeza que pretende apagar {0}?","PasswordResetHeader":"Redefinir Senha","PasswordResetComplete":"A senha foi redefinida.","PasswordResetConfirmation":"Tem a certeza que pretende redefinir a senha?","PasswordSaved":"Senha guardada.","PasswordMatchError":"A senha e a confirma\u00e7\u00e3o da senha devem coincidir.","OptionOff":"Desligado","OptionOn":"Ligado","OptionRelease":"Final","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Desinstalar extens\u00e3o","UninstallPluginConfirmation":"Tem a certeza que pretende desinstalar {0}?","NoPluginConfigurationMessage":"Esta extens\u00e3o n\u00e3o \u00e9 configur\u00e1vel.","NoPluginsInstalledMessage":"N\u00e3o tem extens\u00f5es instaladas.","BrowsePluginCatalogMessage":"Navegue o nosso cat\u00e1logo de extens\u00f5es para ver as extens\u00f5es dispon\u00edveis."}
|
@ -1,31 +1 @@
|
||||
{
|
||||
"SettingsSaved": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b",
|
||||
"AddUser": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f",
|
||||
"Users": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438",
|
||||
"Delete": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c",
|
||||
"Administrator": "\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440",
|
||||
"Password": "\u041f\u0430\u0440\u043e\u043b\u044c",
|
||||
"CreatePassword": "\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c",
|
||||
"DeleteImage": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435",
|
||||
"DeleteImageConfirmation": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u044d\u0442\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435?",
|
||||
"FileReadCancelled": "\u0427\u0442\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0431\u044b\u043b\u043e \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043e",
|
||||
"FileNotFound": "\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d",
|
||||
"FileReadError": "\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430",
|
||||
"DeleteUser": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f",
|
||||
"DeleteUserConfirmation": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?",
|
||||
"PasswordResetHeader": "\u0421\u0431\u0440\u043e\u0441 \u043f\u0430\u0440\u043e\u043b\u044f",
|
||||
"PasswordResetComplete": "\u041f\u0430\u0440\u043e\u043b\u044c \u0431\u044b\u043b \u0441\u0431\u0440\u043e\u0448\u0435\u043d",
|
||||
"PasswordResetConfirmation": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c?",
|
||||
"PasswordSaved": "\u041f\u0430\u0440\u043e\u043b\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d",
|
||||
"PasswordMatchError": "\u041f\u043e\u043b\u044f \u041f\u0430\u0440\u043e\u043b\u044c \u0438 \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c",
|
||||
"OptionOff": "\u0412\u044b\u043a\u043b.",
|
||||
"OptionOn": "\u0412\u043a\u043b.",
|
||||
"OptionRelease": "\u0412\u044b\u043f\u0443\u0441\u043a",
|
||||
"OptionBeta": "\u0411\u0435\u0442\u0430",
|
||||
"OptionDev": "\u0420\u0430\u0437\u0440\u0430\u0431.",
|
||||
"UninstallPluginHeader": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d",
|
||||
"UninstallPluginConfirmation": "\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?",
|
||||
"NoPluginConfigurationMessage": "\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0435\u0442 \u043d\u0438\u043a\u0430\u043a\u0438\u0445 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a",
|
||||
"NoPluginsInstalledMessage": "\u0423 \u0412\u0430\u0441 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430.",
|
||||
"BrowsePluginCatalogMessage": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u043d\u0430\u0448\u0438\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432."
|
||||
}
|
||||
{"SettingsSaved":"\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u044b","AddUser":"\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f","Users":"\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0438","Delete":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c","Administrator":"\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0442\u043e\u0440","Password":"\u041f\u0430\u0440\u043e\u043b\u044c","DeleteImage":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435","DeleteImageConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u044d\u0442\u043e \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435?","FileReadCancelled":"\u0427\u0442\u0435\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0431\u044b\u043b\u043e \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u043e","FileNotFound":"\u0424\u0430\u0439\u043b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d","FileReadError":"\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0444\u0430\u0439\u043b\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430","DeleteUser":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f","DeleteUserConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?","PasswordResetHeader":"\u0421\u0431\u0440\u043e\u0441 \u043f\u0430\u0440\u043e\u043b\u044f","PasswordResetComplete":"\u041f\u0430\u0440\u043e\u043b\u044c \u0431\u044b\u043b \u0441\u0431\u0440\u043e\u0448\u0435\u043d","PasswordResetConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0441\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c?","PasswordSaved":"\u041f\u0430\u0440\u043e\u043b\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0451\u043d","PasswordMatchError":"\u041f\u043e\u043b\u044f \u041f\u0430\u0440\u043e\u043b\u044c \u0438 \u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u043e\u043b\u044f \u0434\u043e\u043b\u0436\u043d\u044b \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u0442\u044c","OptionOff":"\u0412\u044b\u043a\u043b.","OptionOn":"\u0412\u043a\u043b.","OptionRelease":"\u0412\u044b\u043f\u0443\u0441\u043a","OptionBeta":"\u0411\u0435\u0442\u0430","OptionDev":"\u0420\u0430\u0437\u0440\u0430\u0431.","UninstallPluginHeader":"\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u043f\u043b\u0430\u0433\u0438\u043d","UninstallPluginConfirmation":"\u0412\u044b \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0436\u0435\u043b\u0430\u0435\u0442\u0435 \u0443\u0434\u0430\u043b\u0438\u0442\u044c {0}?","NoPluginConfigurationMessage":"\u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430 \u043d\u0435\u0447\u0435\u0433\u043e \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c.","NoPluginsInstalledMessage":"\u0423 \u0412\u0430\u0441 \u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u0438 \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u043b\u0430\u0433\u0438\u043d\u0430.","BrowsePluginCatalogMessage":"\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u043d\u0430\u0448\u0438\u043c \u043a\u0430\u0442\u0430\u043b\u043e\u0433\u043e\u043c \u0434\u043b\u044f \u043f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043b\u0430\u0433\u0438\u043d\u043e\u0432."}
|
@ -1,31 +1 @@
|
||||
{
|
||||
"SettingsSaved": "\u8a2d\u7f6e\u5df2\u4fdd\u5b58",
|
||||
"AddUser": "Add User",
|
||||
"Users": "\u7528\u6236",
|
||||
"Delete": "\u522a\u9664",
|
||||
"Administrator": "\u7ba1\u7406\u54e1",
|
||||
"Password": "\u5bc6\u78bc",
|
||||
"CreatePassword": "\u5275\u5efa\u5bc6\u78bc",
|
||||
"DeleteImage": "\u522a\u9664\u5716\u50cf",
|
||||
"DeleteImageConfirmation": "\u4f60\u78ba\u5b9a\u8981\u522a\u9664\u9019\u5f35\u5716\u7247\uff1f",
|
||||
"FileReadCancelled": "The file read has been cancelled.",
|
||||
"FileNotFound": "File not found.",
|
||||
"FileReadError": "An error occurred while reading the file.",
|
||||
"DeleteUser": "\u522a\u9664\u7528\u6236",
|
||||
"DeleteUserConfirmation": "Are you sure you wish to delete {0}?",
|
||||
"PasswordResetHeader": "\u91cd\u8a2d\u5bc6\u78bc",
|
||||
"PasswordResetComplete": "\u5bc6\u78bc\u5df2\u91cd\u8a2d",
|
||||
"PasswordResetConfirmation": "\u4f60\u78ba\u5b9a\u8981\u91cd\u8a2d\u5bc6\u78bc\uff1f",
|
||||
"PasswordSaved": "\u5bc6\u78bc\u5df2\u4fdd\u5b58\u3002",
|
||||
"PasswordMatchError": "\u5bc6\u78bc\u548c\u78ba\u8a8d\u5bc6\u78bc\u5fc5\u9808\u4e00\u81f4\u3002",
|
||||
"OptionOff": "Off",
|
||||
"OptionOn": "On",
|
||||
"OptionRelease": "Release",
|
||||
"OptionBeta": "Beta",
|
||||
"OptionDev": "Dev",
|
||||
"UninstallPluginHeader": "Uninstall Plugin",
|
||||
"UninstallPluginConfirmation": "Are you sure you wish to uninstall {0}?",
|
||||
"NoPluginConfigurationMessage": "This plugin has nothing to configure.",
|
||||
"NoPluginsInstalledMessage": "You have no plugins installed.",
|
||||
"BrowsePluginCatalogMessage": "Browse our plugin catalog to view available plugins."
|
||||
}
|
||||
{"SettingsSaved":"\u8a2d\u7f6e\u5df2\u4fdd\u5b58","AddUser":"Add User","Users":"\u7528\u6236","Delete":"\u522a\u9664","Administrator":"\u7ba1\u7406\u54e1","Password":"\u5bc6\u78bc","DeleteImage":"\u522a\u9664\u5716\u50cf","DeleteImageConfirmation":"\u4f60\u78ba\u5b9a\u8981\u522a\u9664\u9019\u5f35\u5716\u7247\uff1f","FileReadCancelled":"The file read has been cancelled.","FileNotFound":"File not found.","FileReadError":"An error occurred while reading the file.","DeleteUser":"\u522a\u9664\u7528\u6236","DeleteUserConfirmation":"Are you sure you wish to delete {0}?","PasswordResetHeader":"\u91cd\u8a2d\u5bc6\u78bc","PasswordResetComplete":"\u5bc6\u78bc\u5df2\u91cd\u8a2d","PasswordResetConfirmation":"\u4f60\u78ba\u5b9a\u8981\u91cd\u8a2d\u5bc6\u78bc\uff1f","PasswordSaved":"\u5bc6\u78bc\u5df2\u4fdd\u5b58\u3002","PasswordMatchError":"\u5bc6\u78bc\u548c\u78ba\u8a8d\u5bc6\u78bc\u5fc5\u9808\u4e00\u81f4\u3002","OptionOff":"Off","OptionOn":"On","OptionRelease":"Release","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Uninstall Plugin","UninstallPluginConfirmation":"Are you sure you wish to uninstall {0}?","NoPluginConfigurationMessage":"This plugin has nothing to configure.","NoPluginsInstalledMessage":"You have no plugins installed.","BrowsePluginCatalogMessage":"Browse our plugin catalog to view available plugins."}
|
@ -338,6 +338,8 @@ namespace MediaBrowser.Server.Implementations.Localization
|
||||
new LocalizatonOption{ Name="Dutch", Value="nl"},
|
||||
new LocalizatonOption{ Name="French", Value="fr"},
|
||||
new LocalizatonOption{ Name="German", Value="de"},
|
||||
new LocalizatonOption{ Name="Hebrew", Value="he"},
|
||||
new LocalizatonOption{ Name="Italian", Value="it"},
|
||||
new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"},
|
||||
new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},
|
||||
new LocalizatonOption{ Name="Russian", Value="ru"},
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -15,7 +15,7 @@
|
||||
"LabelNext": "Next",
|
||||
"LabelYoureDone": "You're Done!",
|
||||
"WelcomeToMediaBrowser": "Welcome to Media Browser!",
|
||||
"LabelMediaBrowser": "Media Browser",
|
||||
"TitleMediaBrowser": "Media Browser",
|
||||
"ThisWizardWillGuideYou": "This wizard will help guide you through the setup process.",
|
||||
"TellUsAboutYourself": "Tell us about yourself",
|
||||
"LabelYourFirstName": "Your first name:",
|
||||
@ -46,5 +46,44 @@
|
||||
"LabelSaveLocalMetadata": "Save artwork and metadata into media folders",
|
||||
"LabelSaveLocalMetadataHelp": "Saving artwork and metadata directly into media folders will put them in a place where they can be easily edited.",
|
||||
"LabelDownloadInternetMetadata": "Download artwork and metadata from the internet",
|
||||
"LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations."
|
||||
"LabelDownloadInternetMetadataHelp": "Media Browser can download information about your media to enable rich presentations.",
|
||||
"TabPreferences": "Preferences",
|
||||
"TabPassword": "Password",
|
||||
"TabLibraryAccess": "Library Access",
|
||||
"TabImage": "Image",
|
||||
"TabProfile": "Profile",
|
||||
"LabelDisplayMissingEpisodesWithinSeasons": "Display missing episodes within seasons",
|
||||
"LabelUnairedMissingEpisodesWithinSeasons": "Display unaired episodes within seasons",
|
||||
"HeaderVideoPlaybackSettings": "Video Playback Settings",
|
||||
"LabelAudioLanguagePreference": "Audio language preference:",
|
||||
"LabelSubtitleLanguagePreference": "Subtitle language preference:",
|
||||
"LabelDisplayForcedSubtitlesOnly": "Display only forced subtitles",
|
||||
"TabProfiles": "Profiles",
|
||||
"TabSecurity": "Security",
|
||||
"ButtonAddUser": "Add User",
|
||||
"ButtonSave": "Save",
|
||||
"ButtonResetPassword": "Reset Password",
|
||||
"LabelNewPassword": "New password:",
|
||||
"LabelNewPasswordConfirm": "New password confirm:",
|
||||
"HeaderCreatePassword": "Create Password",
|
||||
"LabelCurrentPassword": "Current password:",
|
||||
"LabelMaxParentalRating": "Maximum allowed parental rating:",
|
||||
"MaxParentalRatingHelp": "Content with a higher rating will be hidden from this user.",
|
||||
"LibraryAccessHelp": "Select the media folders to share with this user. Administrators will be able to edit all folders using the metadata manager.",
|
||||
"ButtonDeleteImage": "Delete Image",
|
||||
"ButtonUpload": "Upload",
|
||||
"HeaderUploadNewImage": "Upload New Image",
|
||||
"LabelDropImageHere": "Drop Image Here",
|
||||
"ImageUploadAspectRatioHelp": "1:1 Aspect Ratio Recommended. JPG/PNG only.",
|
||||
"MessageNothingHere": "Nothing here.",
|
||||
"MessagePleaseEnsureInternetMetadata": "Please ensure downloading of internet metadata is enabled.",
|
||||
"TabSuggested": "Suggested",
|
||||
"TabLatest": "Latest",
|
||||
"TabUpcoming": "Upcoming",
|
||||
"TabShows": "Shows",
|
||||
"TabEpisodes": "Episodes",
|
||||
"TabGenres": "Genres",
|
||||
"TabPeople": "People",
|
||||
"TabNetworks": "Networks",
|
||||
"HeaderUsers": "Users"
|
||||
}
|
File diff suppressed because one or more lines are too long
@ -301,6 +301,10 @@
|
||||
<EmbeddedResource Include="Localization\JavaScript\es.json" />
|
||||
<EmbeddedResource Include="Localization\Server\es.json" />
|
||||
<EmbeddedResource Include="Localization\Server\pt_BR.json" />
|
||||
<EmbeddedResource Include="Localization\JavaScript\it.json" />
|
||||
<EmbeddedResource Include="Localization\JavaScript\pt_BR.json" />
|
||||
<EmbeddedResource Include="Localization\Server\he.json" />
|
||||
<EmbeddedResource Include="Localization\Server\it.json" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
Loading…
Reference in New Issue
Block a user