This commit is contained in:
tikuf 2014-04-02 16:53:53 +11:00
commit 9ff262bc2a
73 changed files with 1260 additions and 877 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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" />

View File

@ -1,4 +1,5 @@
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Controller.MediaEncoding
{

View File

@ -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" />

View File

@ -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"));

View 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);
}
}
}

View File

@ -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

View File

@ -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;

View File

@ -1,4 +1,4 @@
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.PlayTo
{

View File

@ -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:

View File

@ -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);
}
}
}

View File

@ -1,5 +1,6 @@
using MediaBrowser.Controller.Dlna;
using System.Xml.Serialization;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using MediaBrowser.Controller.Dlna;
using System.Xml.Serialization;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using MediaBrowser.Controller.Dlna;
using System.Xml.Serialization;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View 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"
}
}
}
};
}
}
}

View File

@ -1,5 +1,5 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
using System.Xml.Serialization;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -1,5 +1,6 @@
using System.Xml.Serialization;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Dlna.Profiles
{

View File

@ -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.");
}

View File

@ -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>

View File

@ -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>

View File

@ -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,

View File

@ -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();
}
}
}

View File

@ -1,7 +1,6 @@

using System.Xml.Serialization;
using System.Xml.Serialization;
namespace MediaBrowser.Controller.Dlna
namespace MediaBrowser.Model.Dlna
{
public class DeviceIdentification
{

View File

@ -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)

View File

@ -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();
}
}

View File

@ -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();
}
}
}

View 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;
}
}
}

View 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; }
}
}

View File

@ -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();
}
}

View File

@ -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; }

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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();
}

View File

@ -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."}

View File

@ -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."}

View File

@ -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."}

View File

@ -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."}

View File

@ -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."}

View File

@ -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.",

View File

@ -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."}

View File

@ -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."}

View File

@ -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."}

View File

@ -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."}

View File

@ -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."}

View File

@ -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

View File

@ -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

View File

@ -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>