aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Model
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Model')
-rw-r--r--MediaBrowser.Model/Dlna/CodecProfile.cs4
-rw-r--r--MediaBrowser.Model/Dlna/ConditionProcessor.cs192
-rw-r--r--MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs120
-rw-r--r--MediaBrowser.Model/Dlna/DeviceProfile.cs74
-rw-r--r--MediaBrowser.Model/Dlna/DirectPlayProfile.cs9
-rw-r--r--MediaBrowser.Model/Dlna/DlnaMaps.cs13
-rw-r--r--MediaBrowser.Model/Dlna/MediaFormatProfile.cs2
-rw-r--r--MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs59
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs305
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs99
-rw-r--r--MediaBrowser.Model/Entities/MediaStream.cs19
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj1
12 files changed, 657 insertions, 240 deletions
diff --git a/MediaBrowser.Model/Dlna/CodecProfile.cs b/MediaBrowser.Model/Dlna/CodecProfile.cs
index b15dfc809e..2b04b7fdb7 100644
--- a/MediaBrowser.Model/Dlna/CodecProfile.cs
+++ b/MediaBrowser.Model/Dlna/CodecProfile.cs
@@ -76,10 +76,12 @@ namespace MediaBrowser.Model.Dlna
Width,
Height,
Has64BitOffsets,
+ PacketLength,
VideoBitDepth,
VideoBitrate,
VideoFramerate,
VideoLevel,
- VideoProfile
+ VideoProfile,
+ VideoTimestamp
}
}
diff --git a/MediaBrowser.Model/Dlna/ConditionProcessor.cs b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
new file mode 100644
index 0000000000..292ca21487
--- /dev/null
+++ b/MediaBrowser.Model/Dlna/ConditionProcessor.cs
@@ -0,0 +1,192 @@
+using System;
+using System.Globalization;
+
+namespace MediaBrowser.Model.Dlna
+{
+ public class ConditionProcessor
+ {
+ public bool IsVideoConditionSatisfied(ProfileCondition condition,
+ int? audioBitrate,
+ int? audioChannels,
+ int? width,
+ int? height,
+ int? bitDepth,
+ int? videoBitrate,
+ string videoProfile,
+ double? videoLevel,
+ double? videoFramerate,
+ int? packetLength,
+ TransportStreamTimestamp timestamp)
+ {
+ switch (condition.Property)
+ {
+ case ProfileConditionValue.AudioProfile:
+ // TODO: Implement
+ return true;
+ case ProfileConditionValue.Has64BitOffsets:
+ // TODO: Implement
+ return true;
+ case ProfileConditionValue.VideoFramerate:
+ return IsConditionSatisfied(condition, videoFramerate);
+ case ProfileConditionValue.VideoLevel:
+ return IsConditionSatisfied(condition, videoLevel);
+ case ProfileConditionValue.VideoProfile:
+ return IsConditionSatisfied(condition, videoProfile);
+ case ProfileConditionValue.PacketLength:
+ return IsConditionSatisfied(condition, packetLength);
+ case ProfileConditionValue.AudioBitrate:
+ return IsConditionSatisfied(condition, audioBitrate);
+ case ProfileConditionValue.AudioChannels:
+ return IsConditionSatisfied(condition, audioChannels);
+ case ProfileConditionValue.VideoBitDepth:
+ return IsConditionSatisfied(condition, bitDepth);
+ case ProfileConditionValue.VideoBitrate:
+ return IsConditionSatisfied(condition, videoBitrate);
+ case ProfileConditionValue.Height:
+ return IsConditionSatisfied(condition, height);
+ case ProfileConditionValue.Width:
+ return IsConditionSatisfied(condition, width);
+ case ProfileConditionValue.VideoTimestamp:
+ return IsConditionSatisfied(condition, timestamp);
+ default:
+ throw new ArgumentException("Unexpected condition on video file: " + condition.Property);
+ }
+ }
+
+ public bool IsImageConditionSatisfied(ProfileCondition condition, int? width, int? height)
+ {
+ switch (condition.Property)
+ {
+ case ProfileConditionValue.Height:
+ return IsConditionSatisfied(condition, height);
+ case ProfileConditionValue.Width:
+ return IsConditionSatisfied(condition, width);
+ default:
+ throw new ArgumentException("Unexpected condition on image file: " + condition.Property);
+ }
+ }
+
+ public bool IsAudioConditionSatisfied(ProfileCondition condition, int? audioChannels, int? audioBitrate)
+ {
+ switch (condition.Property)
+ {
+ case ProfileConditionValue.AudioBitrate:
+ return IsConditionSatisfied(condition, audioBitrate);
+ case ProfileConditionValue.AudioChannels:
+ return IsConditionSatisfied(condition, audioChannels);
+ default:
+ throw new ArgumentException("Unexpected condition on audio file: " + condition.Property);
+ }
+ }
+
+ public bool IsVideoAudioConditionSatisfied(ProfileCondition condition,
+ int? audioChannels,
+ int? audioBitrate)
+ {
+ switch (condition.Property)
+ {
+ case ProfileConditionValue.AudioBitrate:
+ return IsConditionSatisfied(condition, audioBitrate);
+ case ProfileConditionValue.AudioChannels:
+ return IsConditionSatisfied(condition, audioChannels);
+ default:
+ throw new ArgumentException("Unexpected condition on audio file: " + condition.Property);
+ }
+ }
+
+ private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+ private bool IsConditionSatisfied(ProfileCondition condition, int? currentValue)
+ {
+ if (!currentValue.HasValue)
+ {
+ // If the value is unknown, it satisfies if not marked as required
+ return !condition.IsRequired;
+ }
+
+ int expected;
+ if (int.TryParse(condition.Value, NumberStyles.Any, UsCulture, out expected))
+ {
+ switch (condition.Condition)
+ {
+ case ProfileConditionType.Equals:
+ return currentValue.Value.Equals(expected);
+ case ProfileConditionType.GreaterThanEqual:
+ return currentValue.Value >= expected;
+ case ProfileConditionType.LessThanEqual:
+ return currentValue.Value <= expected;
+ case ProfileConditionType.NotEquals:
+ return !currentValue.Value.Equals(expected);
+ default:
+ throw new InvalidOperationException("Unexpected ProfileConditionType");
+ }
+ }
+
+ return false;
+ }
+
+ private bool IsConditionSatisfied(ProfileCondition condition, string currentValue)
+ {
+ if (string.IsNullOrEmpty(currentValue))
+ {
+ // If the value is unknown, it satisfies if not marked as required
+ return !condition.IsRequired;
+ }
+
+ var expected = condition.Value;
+
+ switch (condition.Condition)
+ {
+ case ProfileConditionType.Equals:
+ return string.Equals(currentValue, expected, StringComparison.OrdinalIgnoreCase);
+ case ProfileConditionType.NotEquals:
+ return !string.Equals(currentValue, expected, StringComparison.OrdinalIgnoreCase);
+ default:
+ throw new InvalidOperationException("Unexpected ProfileConditionType");
+ }
+ }
+
+ private bool IsConditionSatisfied(ProfileCondition condition, double? currentValue)
+ {
+ if (!currentValue.HasValue)
+ {
+ // If the value is unknown, it satisfies if not marked as required
+ return !condition.IsRequired;
+ }
+
+ double expected;
+ if (double.TryParse(condition.Value, NumberStyles.Any, UsCulture, out expected))
+ {
+ switch (condition.Condition)
+ {
+ case ProfileConditionType.Equals:
+ return currentValue.Value.Equals(expected);
+ case ProfileConditionType.GreaterThanEqual:
+ return currentValue.Value >= expected;
+ case ProfileConditionType.LessThanEqual:
+ return currentValue.Value <= expected;
+ case ProfileConditionType.NotEquals:
+ return !currentValue.Value.Equals(expected);
+ default:
+ throw new InvalidOperationException("Unexpected ProfileConditionType");
+ }
+ }
+
+ return false;
+ }
+
+ private bool IsConditionSatisfied(ProfileCondition condition, TransportStreamTimestamp timestamp)
+ {
+ var expected = (TransportStreamTimestamp)Enum.Parse(typeof(TransportStreamTimestamp), condition.Value, true);
+
+ switch (condition.Condition)
+ {
+ case ProfileConditionType.Equals:
+ return timestamp == expected;
+ case ProfileConditionType.NotEquals:
+ return timestamp != expected;
+ default:
+ throw new InvalidOperationException("Unexpected ProfileConditionType");
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index 351eca5be0..b2bbe3b14c 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
namespace MediaBrowser.Model.Dlna
{
@@ -11,13 +13,45 @@ namespace MediaBrowser.Model.Dlna
_profile = profile;
}
- public string BuildAudioHeader(string container,
+ public string BuildImageHeader(string container,
+ int? width,
+ int? height)
+ {
+ var orgOp = ";DLNA.ORG_OP=" + DlnaMaps.GetImageOrgOpValue();
+
+ // 0 = native, 1 = transcoded
+ const string orgCi = ";DLNA.ORG_CI=0";
+
+ var flagValue = DlnaFlags.StreamingTransferMode |
+ DlnaFlags.BackgroundTransferMode |
+ DlnaFlags.DlnaV15;
+
+ var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}",
+ FlagsToString(flagValue));
+
+ var mediaProfile = _profile.GetImageMediaProfile(container,
+ width,
+ height);
+
+ var orgPn = mediaProfile == null ? null : mediaProfile.OrgPn;
+
+ if (string.IsNullOrEmpty(orgPn))
+ {
+ orgPn = GetImageOrgPnValue(container, width, height);
+ }
+
+ var contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn;
+
+ return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
+ }
+
+ public string BuildAudioHeader(string container,
string audioCodec,
- int? audioBitrate,
- int? audioSampleRate,
- int? audioChannels,
- bool isDirectStream,
- long? runtimeTicks,
+ int? audioBitrate,
+ int? audioSampleRate,
+ int? audioChannels,
+ bool isDirectStream,
+ long? runtimeTicks,
TranscodeSeekInfo transcodeSeekInfo)
{
// 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
@@ -42,7 +76,10 @@ namespace MediaBrowser.Model.Dlna
var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}",
FlagsToString(flagValue));
- var mediaProfile = _profile.GetAudioMediaProfile(container, audioCodec);
+ var mediaProfile = _profile.GetAudioMediaProfile(container,
+ audioCodec,
+ audioChannels,
+ audioBitrate);
var orgPn = mediaProfile == null ? null : mediaProfile.OrgPn;
@@ -62,15 +99,22 @@ namespace MediaBrowser.Model.Dlna
return string.Format("{0:X8}{1:D24}", (ulong)flags, 0);
}
- public string BuildVideoHeader(string container,
- string videoCodec,
- string audioCodec,
- int? width,
- int? height,
- int? bitrate,
- TransportStreamTimestamp timestamp,
- bool isDirectStream,
- long? runtimeTicks,
+ public string BuildVideoHeader(string container,
+ string videoCodec,
+ string audioCodec,
+ int? width,
+ int? height,
+ int? bitDepth,
+ int? videoBitrate,
+ int? audioChannels,
+ int? audioBitrate,
+ TransportStreamTimestamp timestamp,
+ bool isDirectStream,
+ long? runtimeTicks,
+ string videoProfile,
+ double? videoLevel,
+ double? videoFramerate,
+ int? packetLength,
TranscodeSeekInfo transcodeSeekInfo)
{
// 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
@@ -95,12 +139,30 @@ namespace MediaBrowser.Model.Dlna
var dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}000000000000000000000000",
Enum.Format(typeof(DlnaFlags), flagValue, "x"));
- var mediaProfile = _profile.GetVideoMediaProfile(container, audioCodec, videoCodec);
+ var mediaProfile = _profile.GetVideoMediaProfile(container,
+ audioCodec,
+ videoCodec,
+ audioBitrate,
+ audioChannels,
+ width,
+ height,
+ bitDepth,
+ videoBitrate,
+ videoProfile,
+ videoLevel,
+ videoFramerate,
+ packetLength,
+ timestamp);
+
var orgPn = mediaProfile == null ? null : mediaProfile.OrgPn;
-
+
if (string.IsNullOrEmpty(orgPn))
{
- orgPn = GetVideoOrgPnValue(container, videoCodec, audioCodec, width, height, bitrate, timestamp);
+ orgPn = GetVideoOrgPnValue(container, videoCodec, audioCodec, width, height, timestamp)
+ .FirstOrDefault();
+
+ // TODO: Support multiple values and return multiple headers?
+ orgPn = (orgPn ?? string.Empty).Split(',').FirstOrDefault();
}
var contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn;
@@ -108,6 +170,16 @@ namespace MediaBrowser.Model.Dlna
return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
}
+ private string GetImageOrgPnValue(string container, int? width, int? height)
+ {
+ var format = new MediaFormatProfileResolver()
+ .ResolveImageFormat(container,
+ width,
+ height);
+
+ return format.HasValue ? format.Value.ToString() : null;
+ }
+
private string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels)
{
var format = new MediaFormatProfileResolver()
@@ -119,18 +191,16 @@ namespace MediaBrowser.Model.Dlna
return format.HasValue ? format.Value.ToString() : null;
}
- private string GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestamp)
+ private IEnumerable<string> GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp)
{
- var videoFormat = new MediaFormatProfileResolver()
+ return new MediaFormatProfileResolver()
.ResolveVideoFormat(container,
videoCodec,
audioCodec,
width,
height,
- bitrate,
- timestamp);
-
- return videoFormat.HasValue ? videoFormat.Value.ToString() : null;
+ timestamp)
+ .Select(i => i.ToString());
}
}
}
diff --git a/MediaBrowser.Model/Dlna/DeviceProfile.cs b/MediaBrowser.Model/Dlna/DeviceProfile.cs
index 106ba75b08..96ff108962 100644
--- a/MediaBrowser.Model/Dlna/DeviceProfile.cs
+++ b/MediaBrowser.Model/Dlna/DeviceProfile.cs
@@ -1,5 +1,4 @@
-using MediaBrowser.Model.Entities;
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Serialization;
@@ -71,6 +70,8 @@ namespace MediaBrowser.Model.Dlna
public bool RequiresPlainVideoItems { get; set; }
public bool RequiresPlainFolders { get; set; }
+ public XmlAttribute[] ContentDirectoryRootAttributes { get; set; }
+
/// <summary>
/// Gets or sets the direct play profiles.
/// </summary>
@@ -96,6 +97,8 @@ namespace MediaBrowser.Model.Dlna
CodecProfiles = new CodecProfile[] { };
ContainerProfiles = new ContainerProfile[] { };
+ ContentDirectoryRootAttributes = new XmlAttribute[] { };
+
SupportedMediaTypes = "Audio,Photo,Video";
}
@@ -159,7 +162,7 @@ namespace MediaBrowser.Model.Dlna
});
}
- public ResponseProfile GetAudioMediaProfile(string container, string audioCodec)
+ public ResponseProfile GetAudioMediaProfile(string container, string audioCodec, int? audioChannels, int? audioBitrate)
{
container = (container ?? string.Empty).TrimStart('.');
@@ -182,11 +185,51 @@ namespace MediaBrowser.Model.Dlna
return false;
}
- return true;
+ var conditionProcessor = new ConditionProcessor();
+ return i.Conditions.All(c => conditionProcessor.IsAudioConditionSatisfied(c,
+ audioChannels,
+ audioBitrate));
});
}
- public ResponseProfile GetVideoMediaProfile(string container, string audioCodec, string videoCodec)
+ public ResponseProfile GetImageMediaProfile(string container, int? width, int? height)
+ {
+ container = (container ?? string.Empty).TrimStart('.');
+
+ return ResponseProfiles.FirstOrDefault(i =>
+ {
+ if (i.Type != DlnaProfileType.Photo)
+ {
+ return false;
+ }
+
+ var containers = i.GetContainers().ToList();
+ if (containers.Count > 0 && !containers.Contains(container))
+ {
+ return false;
+ }
+
+ var conditionProcessor = new ConditionProcessor();
+ return i.Conditions.All(c => conditionProcessor.IsImageConditionSatisfied(c,
+ width,
+ height));
+ });
+ }
+
+ public ResponseProfile GetVideoMediaProfile(string container,
+ string audioCodec,
+ string videoCodec,
+ int? audioBitrate,
+ int? audioChannels,
+ int? width,
+ int? height,
+ int? bitDepth,
+ int? videoBitrate,
+ string videoProfile,
+ double? videoLevel,
+ double? videoFramerate,
+ int? packetLength,
+ TransportStreamTimestamp timestamp)
{
container = (container ?? string.Empty).TrimStart('.');
@@ -215,11 +258,23 @@ namespace MediaBrowser.Model.Dlna
return false;
}
- return true;
+ var conditionProcessor = new ConditionProcessor();
+ return i.Conditions.All(c => conditionProcessor.IsVideoConditionSatisfied(c,
+ audioBitrate,
+ audioChannels,
+ width,
+ height,
+ bitDepth,
+ videoBitrate,
+ videoProfile,
+ videoLevel,
+ videoFramerate,
+ packetLength,
+ timestamp));
});
}
- public ResponseProfile GetPhotoMediaProfile(string container)
+ public ResponseProfile GetPhotoMediaProfile(string container, int? width, int? height)
{
container = (container ?? string.Empty).TrimStart('.');
@@ -236,7 +291,10 @@ namespace MediaBrowser.Model.Dlna
return false;
}
- return true;
+ var conditionProcessor = new ConditionProcessor();
+ return i.Conditions.All(c => conditionProcessor.IsImageConditionSatisfied(c,
+ width,
+ height));
});
}
}
diff --git a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
index c7ecdc908b..e195c94507 100644
--- a/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
+++ b/MediaBrowser.Model/Dlna/DirectPlayProfile.cs
@@ -34,6 +34,15 @@ namespace MediaBrowser.Model.Dlna
}
}
+ public class XmlAttribute
+ {
+ [XmlAttribute("name")]
+ public string Name { get; set; }
+
+ [XmlAttribute("value")]
+ public string Value { get; set; }
+ }
+
public enum DlnaProfileType
{
Audio = 0,
diff --git a/MediaBrowser.Model/Dlna/DlnaMaps.cs b/MediaBrowser.Model/Dlna/DlnaMaps.cs
index eb0c333154..d2871474ad 100644
--- a/MediaBrowser.Model/Dlna/DlnaMaps.cs
+++ b/MediaBrowser.Model/Dlna/DlnaMaps.cs
@@ -41,6 +41,19 @@ namespace MediaBrowser.Model.Dlna
// No seeking is available if we don't know the content runtime
return "00";
}
+
+ public static string GetImageOrgOpValue()
+ {
+ var orgOp = string.Empty;
+
+ // Time-based seeking currently only possible when transcoding
+ orgOp += "0";
+
+ // Byte-based seeking only possible when not transcoding
+ orgOp += "1";
+
+ return orgOp;
+ }
}
[Flags]
diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfile.cs b/MediaBrowser.Model/Dlna/MediaFormatProfile.cs
index e686fe932d..596985bdc0 100644
--- a/MediaBrowser.Model/Dlna/MediaFormatProfile.cs
+++ b/MediaBrowser.Model/Dlna/MediaFormatProfile.cs
@@ -1,6 +1,4 @@

-using System;
-
namespace MediaBrowser.Model.Dlna
{
public enum MediaFormatProfile
diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
index 02400a7ff3..9337a6c95f 100644
--- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
+++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
@@ -1,57 +1,62 @@
using System;
using System.Collections.Generic;
-using System.Linq;
namespace MediaBrowser.Model.Dlna
{
public class MediaFormatProfileResolver
{
- public MediaFormatProfile? ResolveVideoFormat(string container, string videoCodec, string audioCodec, int? width, int? height, int? bitrate, TransportStreamTimestamp timestampType)
+ public IEnumerable<MediaFormatProfile> ResolveVideoFormat(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType)
{
if (string.Equals(container, "asf", StringComparison.OrdinalIgnoreCase))
- return ResolveVideoASFFormat(videoCodec, audioCodec, width, height);
+ {
+ var val = ResolveVideoASFFormat(videoCodec, audioCodec, width, height);
+ return val.HasValue ? new List<MediaFormatProfile> { val.Value } : new List<MediaFormatProfile>();
+ }
if (string.Equals(container, "mp4", StringComparison.OrdinalIgnoreCase))
- return ResolveVideoMP4Format(videoCodec, audioCodec, width, height, bitrate);
+ {
+ var val = ResolveVideoMP4Format(videoCodec, audioCodec, width, height);
+ return val.HasValue ? new List<MediaFormatProfile> { val.Value } : new List<MediaFormatProfile>();
+ }
if (string.Equals(container, "avi", StringComparison.OrdinalIgnoreCase))
- return MediaFormatProfile.AVI;
+ return new[] { MediaFormatProfile.AVI };
if (string.Equals(container, "mkv", StringComparison.OrdinalIgnoreCase))
- return MediaFormatProfile.MATROSKA;
+ return new[] { MediaFormatProfile.MATROSKA };
if (string.Equals(container, "mpeg2ps", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "ts", StringComparison.OrdinalIgnoreCase))
- // MediaFormatProfile.MPEG_PS_PAL, MediaFormatProfile.MPEG_PS_NTSC
- return MediaFormatProfile.MPEG_PS_NTSC;
+
+ return new[] { MediaFormatProfile.MPEG_PS_NTSC, MediaFormatProfile.MPEG_PS_PAL };
if (string.Equals(container, "mpeg1video", StringComparison.OrdinalIgnoreCase))
- return MediaFormatProfile.MPEG1;
+ return new[] { MediaFormatProfile.MPEG1 };
if (string.Equals(container, "mpeg2ts", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
{
- var list = ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, timestampType)
- .ToList();
-
- return list.Count > 0 ? list[0] : (MediaFormatProfile?)null;
+ return ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, timestampType);
}
if (string.Equals(container, "flv", StringComparison.OrdinalIgnoreCase))
- return MediaFormatProfile.FLV;
+ return new[] { MediaFormatProfile.FLV };
if (string.Equals(container, "wtv", StringComparison.OrdinalIgnoreCase))
- return MediaFormatProfile.WTV;
+ return new[] { MediaFormatProfile.WTV };
if (string.Equals(container, "3gp", StringComparison.OrdinalIgnoreCase))
- return ResolveVideo3GPFormat(videoCodec, audioCodec);
+ {
+ var val = ResolveVideo3GPFormat(videoCodec, audioCodec);
+ return val.HasValue ? new List<MediaFormatProfile> { val.Value } : new List<MediaFormatProfile>();
+ }
if (string.Equals(container, "ogv", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "ogg", StringComparison.OrdinalIgnoreCase))
- return MediaFormatProfile.OGV;
+ return new[] { MediaFormatProfile.OGV };
- return null;
+ return new List<MediaFormatProfile>();
}
private IEnumerable<MediaFormatProfile> ResolveVideoMPEG2TSFormat(string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestampType)
@@ -132,11 +137,16 @@ namespace MediaBrowser.Model.Dlna
}
return new[] { MediaFormatProfile.VC1_TS_AP_L1_AC3_ISO };
}
- // if (audioCodec == AudioCodec.DTS) {
- // suffix = suffix.equals("_ISO") ? suffix : "_T";
- // return Collections.singletonList(MediaFormatProfile.valueOf(String.format("VC1_TS_HD_DTS%s", cast(Object[])[ suffix ])));
- // }
- //} else if ((videoCodec == VideoCodec.MPEG4) || (videoCodec == VideoCodec.MSMPEG4)) {
+ if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase))
+ {
+ suffix = string.Equals(suffix, "_ISO") ? suffix : "_T";
+
+ return new[] { ValueOf(string.Format("VC1_TS_HD_DTS{0}", suffix)) };
+ }
+
+ }
+ else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
+ {
// if (audioCodec == AudioCodec.AAC)
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_AAC%s", cast(Object[])[ suffix ])));
// if (audioCodec == AudioCodec.MP3)
@@ -145,7 +155,6 @@ namespace MediaBrowser.Model.Dlna
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_MPEG2_L2%s", cast(Object[])[ suffix ])));
// if ((audioCodec is null) || (audioCodec == AudioCodec.AC3)) {
// return Collections.singletonList(MediaFormatProfile.valueOf(String.format("MPEG4_P2_TS_ASP_AC3%s", cast(Object[])[ suffix ])));
- // }
}
return new List<MediaFormatProfile>();
@@ -156,7 +165,7 @@ namespace MediaBrowser.Model.Dlna
return (MediaFormatProfile)Enum.Parse(typeof(MediaFormatProfile), value, true);
}
- private MediaFormatProfile? ResolveVideoMP4Format(string videoCodec, string audioCodec, int? width, int? height, int? bitrate)
+ private MediaFormatProfile? ResolveVideoMP4Format(string videoCodec, string audioCodec, int? width, int? height)
{
if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index 2127af34ae..f3425d2027 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -100,13 +100,23 @@ namespace MediaBrowser.Model.Dlna
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.Audio && i.ContainsCodec(audioCodec))
- .All(i => AreConditionsSatisfied(i.Conditions, item.Path, null, audioStream)))
+ if (!string.IsNullOrEmpty(audioCodec))
{
- playlistItem.IsDirectStream = true;
- playlistItem.Container = item.Container;
+ var conditionProcessor = new ConditionProcessor();
- return playlistItem;
+ var conditions = options.Profile.CodecProfiles.Where(i => i.Type == CodecType.Audio && i.ContainsCodec(audioCodec))
+ .SelectMany(i => i.Conditions);
+
+ var audioChannels = audioStream == null ? null : audioStream.Channels;
+ var audioBitrate = audioStream == null ? null : audioStream.BitRate;
+
+ if (conditions.All(c => conditionProcessor.IsAudioConditionSatisfied(c, audioChannels, audioBitrate)))
+ {
+ playlistItem.IsDirectStream = true;
+ playlistItem.Container = item.Container;
+
+ return playlistItem;
+ }
}
}
}
@@ -168,29 +178,14 @@ namespace MediaBrowser.Model.Dlna
if (IsEligibleForDirectPlay(item, options, maxBitrateSetting))
{
// See if it can be direct played
- var directPlay = options.Profile.DirectPlayProfiles
- .FirstOrDefault(i => i.Type == playlistItem.MediaType && IsVideoDirectPlaySupported(i, item, videoStream, audioStream));
+ var directPlay = GetVideoDirectPlayProfile(options.Profile, 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;
+ playlistItem.IsDirectStream = true;
+ playlistItem.Container = item.Container;
- return playlistItem;
- }
- }
+ return playlistItem;
}
}
@@ -257,6 +252,110 @@ namespace MediaBrowser.Model.Dlna
return playlistItem;
}
+ private DirectPlayProfile GetVideoDirectPlayProfile(DeviceProfile profile,
+ MediaSourceInfo mediaSource,
+ MediaStream videoStream,
+ MediaStream audioStream)
+ {
+ // See if it can be direct played
+ var directPlay = profile.DirectPlayProfiles
+ .FirstOrDefault(i => i.Type == DlnaProfileType.Video && IsVideoDirectPlaySupported(i, mediaSource, videoStream, audioStream));
+
+ if (directPlay == null)
+ {
+ return null;
+ }
+
+ var container = mediaSource.Container;
+
+ var conditions = profile.ContainerProfiles
+ .Where(i => i.Type == DlnaProfileType.Video && i.GetContainers().Contains(container, StringComparer.OrdinalIgnoreCase))
+ .SelectMany(i => i.Conditions);
+
+ var conditionProcessor = new ConditionProcessor();
+
+ var width = videoStream == null ? null : videoStream.Width;
+ var height = videoStream == null ? null : videoStream.Height;
+ var bitDepth = videoStream == null ? null : videoStream.BitDepth;
+ var videoBitrate = videoStream == null ? null : videoStream.BitRate;
+ var videoLevel = videoStream == null ? null : videoStream.Level;
+ var videoProfile = videoStream == null ? null : videoStream.Profile;
+ var videoFramerate = videoStream == null ? null : videoStream.AverageFrameRate ?? videoStream.AverageFrameRate;
+
+ var audioBitrate = audioStream == null ? null : audioStream.BitRate;
+ var audioChannels = audioStream == null ? null : audioStream.Channels;
+
+ var timestamp = videoStream == null ? TransportStreamTimestamp.NONE : videoStream.Timestamp;
+ var packetLength = videoStream == null ? null : videoStream.PacketLength;
+
+ // Check container conditions
+ if (!conditions.All(i => conditionProcessor.IsVideoConditionSatisfied(i,
+ audioBitrate,
+ audioChannels,
+ width,
+ height,
+ bitDepth,
+ videoBitrate,
+ videoProfile,
+ videoLevel,
+ videoFramerate,
+ packetLength,
+ timestamp)))
+ {
+ return null;
+ }
+
+ var videoCodec = videoStream == null ? null : videoStream.Codec;
+
+ if (string.IsNullOrEmpty(videoCodec))
+ {
+ return null;
+ }
+
+ conditions = profile.CodecProfiles
+ .Where(i => i.Type == CodecType.Video && i.ContainsCodec(videoCodec))
+ .SelectMany(i => i.Conditions);
+
+ if (!conditions.All(i => conditionProcessor.IsVideoConditionSatisfied(i,
+ audioBitrate,
+ audioChannels,
+ width,
+ height,
+ bitDepth,
+ videoBitrate,
+ videoProfile,
+ videoLevel,
+ videoFramerate,
+ packetLength,
+ timestamp)))
+ {
+ return null;
+ }
+
+ if (audioStream != null)
+ {
+ var audioCodec = audioStream.Codec;
+
+ if (string.IsNullOrEmpty(audioCodec))
+ {
+ return null;
+ }
+
+ conditions = profile.CodecProfiles
+ .Where(i => i.Type == CodecType.VideoAudio && i.ContainsCodec(audioCodec))
+ .SelectMany(i => i.Conditions);
+
+ if (!conditions.All(i => conditionProcessor.IsVideoAudioConditionSatisfied(i,
+ audioChannels,
+ audioBitrate)))
+ {
+ return null;
+ }
+ }
+
+ return directPlay;
+ }
+
private bool IsEligibleForDirectPlay(MediaSourceInfo item, VideoOptions options, int? maxBitrate)
{
if (options.SubtitleStreamIndex.HasValue)
@@ -343,12 +442,18 @@ namespace MediaBrowser.Model.Dlna
}
case ProfileConditionValue.AudioProfile:
case ProfileConditionValue.Has64BitOffsets:
+ case ProfileConditionValue.PacketLength:
+ case ProfileConditionValue.VideoTimestamp:
case ProfileConditionValue.VideoBitDepth:
- case ProfileConditionValue.VideoProfile:
{
// Not supported yet
break;
}
+ case ProfileConditionValue.VideoProfile:
+ {
+ item.VideoProfile = value;
+ break;
+ }
case ProfileConditionValue.Height:
{
int num;
@@ -457,155 +562,5 @@ namespace MediaBrowser.Model.Dlna
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)
- {
- double expected;
- if (double.TryParse(condition.Value, NumberStyles.Any, _usCulture, out expected))
- {
- switch (condition.Condition)
- {
- case ProfileConditionType.Equals:
- return actualValue.Value.Equals(expected);
- case ProfileConditionType.GreaterThanEqual:
- return actualValue.Value >= expected;
- case ProfileConditionType.LessThanEqual:
- return actualValue.Value <= expected;
- case ProfileConditionType.NotEquals:
- return !actualValue.Value.Equals(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 double? 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 : (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 : videoStream.Level;
- case ProfileConditionValue.VideoBitDepth:
- return videoStream == null ? null : GetBitDepth(videoStream);
- default:
- throw new InvalidOperationException("Unexpected Property");
- }
- }
-
- private int? GetBitDepth(MediaStream videoStream)
- {
- var eightBit = new List<string>
- {
- "yuv420p",
- "yuv411p",
- "yuvj420p",
- "uyyvyy411",
- "nv12",
- "nv21",
- "rgb444le",
- "rgb444be",
- "bgr444le",
- "bgr444be",
- "yuvj411p"
- };
-
- if (!string.IsNullOrEmpty(videoStream.PixelFormat))
- {
- if (eightBit.Contains(videoStream.PixelFormat, StringComparer.OrdinalIgnoreCase))
- {
- return 8;
- }
- }
-
- return null;
- }
}
-
}
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index c5589322d8..0ab6805e3b 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -26,6 +26,7 @@ namespace MediaBrowser.Model.Dlna
public long StartPositionTicks { get; set; }
public string VideoCodec { get; set; }
+ public string VideoProfile { get; set; }
public string AudioCodec { get; set; }
@@ -57,8 +58,6 @@ namespace MediaBrowser.Model.Dlna
public MediaSourceInfo MediaSource { get; set; }
- public TransportStreamTimestamp TargetTimestamp { get; set; }
-
public string MediaSourceId
{
get
@@ -178,6 +177,74 @@ namespace MediaBrowser.Model.Dlna
}
/// <summary>
+ /// Predicts the audio sample rate that will be in the output stream
+ /// </summary>
+ public int? TargetVideoBitDepth
+ {
+ get
+ {
+ var stream = TargetVideoStream;
+ return stream == null || !IsDirectStream ? null : stream.BitDepth;
+ }
+ }
+
+ /// <summary>
+ /// Predicts the audio sample rate that will be in the output stream
+ /// </summary>
+ public double? TargetFramerate
+ {
+ get
+ {
+ var stream = TargetVideoStream;
+ return MaxFramerate.HasValue && !IsDirectStream
+ ? MaxFramerate
+ : stream == null ? null : stream.AverageFrameRate ?? stream.RealFrameRate;
+ }
+ }
+
+ /// <summary>
+ /// Predicts the audio sample rate that will be in the output stream
+ /// </summary>
+ public double? TargetVideoLevel
+ {
+ get
+ {
+ var stream = TargetVideoStream;
+ return VideoLevel.HasValue && !IsDirectStream
+ ? VideoLevel
+ : stream == null ? null : stream.Level;
+ }
+ }
+
+ /// <summary>
+ /// Predicts the audio sample rate that will be in the output stream
+ /// </summary>
+ public int? TargetPacketLength
+ {
+ get
+ {
+ var stream = TargetVideoStream;
+ return !IsDirectStream
+ ? null
+ : stream == null ? null : stream.PacketLength;
+ }
+ }
+
+ /// <summary>
+ /// Predicts the audio sample rate that will be in the output stream
+ /// </summary>
+ public string TargetVideoProfile
+ {
+ get
+ {
+ var stream = TargetVideoStream;
+ return !string.IsNullOrEmpty(VideoProfile) && !IsDirectStream
+ ? VideoProfile
+ : stream == null ? null : stream.Profile;
+ }
+ }
+
+ /// <summary>
/// Predicts the audio bitrate that will be in the output stream
/// </summary>
public int? TargetAudioBitrate
@@ -256,11 +323,35 @@ namespace MediaBrowser.Model.Dlna
}
}
- public int? TotalOutputBitrate
+ public int? TargetVideoBitrate
+ {
+ get
+ {
+ var stream = TargetVideoStream;
+
+ return VideoBitrate.HasValue && !IsDirectStream
+ ? VideoBitrate
+ : stream == null ? null : stream.BitRate;
+ }
+ }
+
+ public TransportStreamTimestamp TargetTimestamp
+ {
+ get
+ {
+ var stream = TargetVideoStream;
+
+ return !IsDirectStream
+ ? TransportStreamTimestamp.VALID
+ : stream == null ? TransportStreamTimestamp.VALID : stream.Timestamp;
+ }
+ }
+
+ public int? TargetTotalBitrate
{
get
{
- return (TargetAudioBitrate ?? 0) + (VideoBitrate ?? 0);
+ return (TargetAudioBitrate ?? 0) + (TargetVideoBitrate ?? 0);
}
}
diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs
index cdfdd19c84..920112d02e 100644
--- a/MediaBrowser.Model/Entities/MediaStream.cs
+++ b/MediaBrowser.Model/Entities/MediaStream.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics;
+using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Model.Entities
{
@@ -40,6 +41,24 @@ namespace MediaBrowser.Model.Entities
public int? BitRate { get; set; }
/// <summary>
+ /// Gets or sets the bit depth.
+ /// </summary>
+ /// <value>The bit depth.</value>
+ public int? BitDepth { get; set; }
+
+ /// <summary>
+ /// Gets or sets the length of the packet.
+ /// </summary>
+ /// <value>The length of the packet.</value>
+ public int? PacketLength { get; set; }
+
+ /// <summary>
+ /// Gets or sets the timestamp.
+ /// </summary>
+ /// <value>The timestamp.</value>
+ public TransportStreamTimestamp Timestamp { get; set; }
+
+ /// <summary>
/// Gets or sets the channels.
/// </summary>
/// <value>The channels.</value>
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 4d0214eb86..1ae0833fb7 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -67,6 +67,7 @@
<Compile Include="Configuration\MetadataOptions.cs" />
<Compile Include="Configuration\ServerConfiguration.cs" />
<Compile Include="Dlna\CodecProfile.cs" />
+ <Compile Include="Dlna\ConditionProcessor.cs" />
<Compile Include="Dlna\ContainerProfile.cs" />
<Compile Include="Dlna\ContentFeatureBuilder.cs" />
<Compile Include="Dlna\DeviceIdentification.cs" />