unify didl generation

This commit is contained in:
Luke Pulverenti 2014-04-23 12:29:21 -04:00
parent 73b771f9c5
commit 0ab3a1bf8e
6 changed files with 667 additions and 776 deletions

View File

@ -1,167 +1,691 @@
using MediaBrowser.Controller.Entities; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Xml;
namespace MediaBrowser.Dlna.Didl namespace MediaBrowser.Dlna.Didl
{ {
public class DidlBuilder public class DidlBuilder
{ {
const string CRLF = "\r\n"; private readonly CultureInfo _usCulture = new CultureInfo("en-US");
const string UNKNOWN = "Unknown";
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
const string DIDL_START = @"<item id=""{0}"" parentID=""{1}"" restricted=""1"" xmlns=""urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"">" + CRLF; private readonly DeviceProfile _profile;
const string DIDL_TITLE = @" <dc:title xmlns:dc=""http://purl.org/dc/elements/1.1/"">{0}</dc:title>" + CRLF; private readonly IImageProcessor _imageProcessor;
const string DIDL_ARTIST = @"<upnp:artist xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:artist>" + CRLF; private readonly string _serverAddress;
const string DIDL_ALBUM = @"<upnp:album xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:album>" + CRLF; private readonly IDtoService _dtoService;
const string DIDL_TRACKNUM = @"<upnp:originalTrackNumber xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:originalTrackNumber>" + CRLF;
const string DIDL_VIDEOCLASS = @" <upnp:class xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">object.item.videoItem</upnp:class>" + CRLF; public DidlBuilder(DeviceProfile profile, IImageProcessor imageProcessor, string serverAddress, IDtoService dtoService)
const string DIDL_AUDIOCLASS = @" <upnp:class xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">object.item.audioItem.musicTrack</upnp:class>" + CRLF; {
const string DIDL_IMAGE = @" <upnp:albumArtURI dlna:profileID=""JPEG_TN"" xmlns:dlna=""urn:schemas-dlna-org:metadata-1-0/"" xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:albumArtURI>" + CRLF + _profile = profile;
@" <upnp:icon xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:icon>" + CRLF; _imageProcessor = imageProcessor;
const string DIDL_RELEASEDATE = @" <dc:date xmlns:dc=""http://purl.org/dc/elements/1.1/"">{0}</dc:date>" + CRLF; _serverAddress = serverAddress;
const string DIDL_GENRE = @" <upnp:genre xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:genre>" + CRLF; _dtoService = dtoService;
const string DESCRIPTION = @" <dc:description xmlns:dc=""http://purl.org/dc/elements/1.1/"">{0}</dc:description>" + CRLF; }
const string DIDL_VIDEO_RES = @" <res bitrate=""{0}"" duration=""{1}"" protocolInfo=""http-get:*:video/x-msvideo:DLNA.ORG_PN=AVI;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000"" resolution=""{2}x{3}"">{4}</res>" + CRLF;
const string DIDL_AUDIO_RES = @" <res bitrate=""{0}"" duration=""{1}"" nrAudioChannels=""2"" protocolInfo=""http-get:*:audio/mp3:DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01500000000000000000000000000000"" sampleFrequency=""{2}"">{3}</res>" + CRLF; public string GetItemDidl(BaseItem item, string deviceId, Filter filter)
const string DIDL_IMAGE_RES = @" <res protocolInfo=""http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=00D00000000000000000000000000000"" resolution=""212x320"">{0}</res>" + CRLF; {
const string DIDL_ALBUMIMAGE_RES = @" <res protocolInfo=""http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN;DLNA.ORG_OP=00;DLNA.ORG_CI=1;DLNA.ORG_FLAGS=00D00000000000000000000000000000"" resolution=""320x320"">{0}</res>" + CRLF; var result = new XmlDocument();
const string DIDL_RATING = @" <upnp:rating xmlns:upnp=""urn:schemas-upnp-org:metadata-1-0/upnp/"">{0}</upnp:rating>" + CRLF;
const string DIDL_END = "</item>"; var didl = result.CreateElement(string.Empty, "DIDL-Lite", NS_DIDL);
didl.SetAttribute("xmlns:dc", NS_DC);
didl.SetAttribute("xmlns:dlna", NS_DLNA);
didl.SetAttribute("xmlns:upnp", NS_UPNP);
//didl.SetAttribute("xmlns:sec", NS_SEC);
result.AppendChild(didl);
result.DocumentElement.AppendChild(GetItemElement(result, item, deviceId, filter));
return result.DocumentElement.OuterXml;
}
public XmlElement GetItemElement(XmlDocument doc, BaseItem item, string deviceId, Filter filter)
{
var element = doc.CreateElement(string.Empty, "item", NS_DIDL);
element.SetAttribute("restricted", "1");
element.SetAttribute("id", item.Id.ToString("N"));
if (item.Parent != null)
{
element.SetAttribute("parentID", item.Parent.Id.ToString("N"));
}
//AddBookmarkInfo(item, user, element);
AddGeneralProperties(item, element, filter);
// refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
var audio = item as Audio;
if (audio != null)
{
AddAudioResource(element, audio, deviceId, filter);
}
var video = item as Video;
if (video != null)
{
AddVideoResource(element, video, deviceId, filter);
}
AddCover(item, element);
return element;
}
private void AddVideoResource(XmlElement container, Video video, string deviceId, Filter filter)
{
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
var sources = _dtoService.GetMediaSources(video);
int? maxBitrateSetting = null;
var streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
{
ItemId = video.Id.ToString("N"),
MediaSources = sources,
Profile = _profile,
DeviceId = deviceId,
MaxBitrate = maxBitrateSetting
});
var url = streamInfo.ToDlnaUrl(_serverAddress);
//res.AppendChild(container.OwnerDocument.CreateCDataSection(url));
res.InnerText = url;
var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId));
if (mediaSource.RunTimeTicks.HasValue)
{
res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
}
if (filter.Contains("res@size"))
{
if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
{
var size = streamInfo.TargetSize;
if (size.HasValue)
{
res.SetAttribute("size", size.Value.ToString(_usCulture));
}
}
}
var totalBitrate = streamInfo.TotalOutputBitrate;
var targetSampleRate = streamInfo.TargetAudioSampleRate;
var targetChannels = streamInfo.TargetAudioChannels;
var targetWidth = streamInfo.TargetWidth;
var targetHeight = streamInfo.TargetHeight;
if (targetChannels.HasValue)
{
res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
}
if (filter.Contains("res@resolution"))
{
if (targetWidth.HasValue && targetHeight.HasValue)
{
res.SetAttribute("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
}
}
if (targetSampleRate.HasValue)
{
res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
}
if (totalBitrate.HasValue)
{
res.SetAttribute("bitrate", totalBitrate.Value.ToString(_usCulture));
}
var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
streamInfo.AudioCodec,
streamInfo.VideoCodec);
var filename = url.Substring(0, url.IndexOf('?'));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
var contentFeatures = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
streamInfo.VideoCodec,
streamInfo.AudioCodec,
targetWidth,
targetHeight,
totalBitrate,
streamInfo.TargetTimestamp,
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks,
streamInfo.TranscodeSeekInfo);
res.SetAttribute("protocolInfo", String.Format(
"http-get:*:{0}:{1}",
mimeType,
contentFeatures
));
container.AppendChild(res);
}
private void AddAudioResource(XmlElement container, Audio audio, string deviceId, Filter filter)
{
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
var sources = _dtoService.GetMediaSources(audio);
var streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
{
ItemId = audio.Id.ToString("N"),
MediaSources = sources,
Profile = _profile,
DeviceId = deviceId
});
var url = streamInfo.ToDlnaUrl(_serverAddress);
//res.AppendChild(container.OwnerDocument.CreateCDataSection(url));
res.InnerText = url;
var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId));
if (mediaSource.RunTimeTicks.HasValue)
{
res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
}
if (filter.Contains("res@size"))
{
if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
{
var size = streamInfo.TargetSize;
if (size.HasValue)
{
res.SetAttribute("size", size.Value.ToString(_usCulture));
}
}
}
var targetAudioBitrate = streamInfo.TargetAudioBitrate;
var targetSampleRate = streamInfo.TargetAudioSampleRate;
var targetChannels = streamInfo.TargetAudioChannels;
if (targetChannels.HasValue)
{
res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
}
if (targetSampleRate.HasValue)
{
res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
}
if (targetAudioBitrate.HasValue)
{
res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
}
var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
streamInfo.AudioCodec);
var filename = url.Substring(0, url.IndexOf('?'));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
streamInfo.TargetAudioCodec,
targetAudioBitrate,
targetSampleRate,
targetChannels,
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks,
streamInfo.TranscodeSeekInfo);
res.SetAttribute("protocolInfo", String.Format(
"http-get:*:{0}:{1}",
mimeType,
contentFeatures
));
container.AppendChild(res);
}
public XmlElement GetFolderElement(XmlDocument doc, Folder folder, int childCount, Filter filter)
{
var container = doc.CreateElement(string.Empty, "container", NS_DIDL);
container.SetAttribute("restricted", "0");
container.SetAttribute("searchable", "1");
container.SetAttribute("childCount", childCount.ToString(_usCulture));
container.SetAttribute("id", folder.Id.ToString("N"));
var parent = folder.Parent;
if (parent == null)
{
container.SetAttribute("parentID", "0");
}
else
{
container.SetAttribute("parentID", parent.Id.ToString("N"));
}
AddCommonFields(folder, container, filter);
AddCover(folder, container);
return container;
}
//private void AddBookmarkInfo(BaseItem item, User user, XmlElement element)
//{
// var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
// if (userdata.PlaybackPositionTicks > 0)
// {
// var dcmInfo = element.OwnerDocument.CreateElement("sec", "dcmInfo", NS_SEC);
// dcmInfo.InnerText = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture));
// element.AppendChild(dcmInfo);
// }
//}
/// <summary> /// <summary>
/// Builds a Didl MetaData object for the specified dto. /// Adds fields used by both items and folders
/// </summary> /// </summary>
/// <param name="dto">The dto.</param> /// <param name="item">The item.</param>
/// <param name="userId">The user identifier.</param> /// <param name="element">The element.</param>
/// <param name="serverAddress">The server address.</param> /// <param name="filter">The filter.</param>
/// <param name="streamUrl">The stream URL.</param> private void AddCommonFields(BaseItem item, XmlElement element, Filter filter)
/// <param name="streams">The streams.</param>
/// <param name="includeImageRes">if set to <c>true</c> [include image resource].</param>
/// <returns>System.String.</returns>
public static string Build(BaseItem dto, string userId, string serverAddress, string streamUrl, IEnumerable<MediaStream> streams, bool includeImageRes)
{ {
string response = string.Format(DIDL_START, dto.Id, userId); if (filter.Contains("dc:title"))
response += string.Format(DIDL_TITLE, dto.Name.Replace("&", "and"));
if (IsVideo(dto))
response += DIDL_VIDEOCLASS;
else
response += DIDL_AUDIOCLASS;
var imageUrl = GetImageUrl(dto, serverAddress);
if (!string.IsNullOrWhiteSpace(imageUrl))
{ {
response += string.Format(DIDL_IMAGE, imageUrl); AddValue(element, "dc", "title", item.Name, NS_DC);
} }
response += string.Format(DIDL_RELEASEDATE, GetDateString(dto.PremiereDate));
//TODO Add genres to didl; element.AppendChild(CreateObjectClass(element.OwnerDocument, item));
response += string.Format(DIDL_GENRE, UNKNOWN);
if (IsVideo(dto)) if (filter.Contains("dc:date"))
{ {
response += string.Format(DESCRIPTION, UNKNOWN); if (item.PremiereDate.HasValue)
response += GetVideoDIDL(dto, streamUrl, streams);
if (includeImageRes && !string.IsNullOrWhiteSpace(imageUrl))
{ {
response += string.Format(DIDL_IMAGE_RES, imageUrl); AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
}
}
foreach (var genre in item.Genres)
{
AddValue(element, "upnp", "genre", genre, NS_UPNP);
}
foreach (var studio in item.Studios)
{
AddValue(element, "upnp", "publisher", studio, NS_UPNP);
}
if (filter.Contains("dc:description"))
{
if (!string.IsNullOrWhiteSpace(item.Overview))
{
AddValue(element, "dc", "description", item.Overview, NS_DC);
}
}
if (filter.Contains("upnp:longDescription"))
{
if (!string.IsNullOrWhiteSpace(item.Overview))
{
AddValue(element, "upnp", "longDescription", item.Overview, NS_UPNP);
}
}
if (!string.IsNullOrEmpty(item.OfficialRating))
{
if (filter.Contains("dc:rating"))
{
AddValue(element, "dc", "rating", item.OfficialRating, NS_DC);
}
if (filter.Contains("upnp:rating"))
{
AddValue(element, "upnp", "rating", item.OfficialRating, NS_UPNP);
}
}
AddPeople(item, element);
}
private XmlElement CreateObjectClass(XmlDocument result, BaseItem item)
{
var objectClass = result.CreateElement("upnp", "class", NS_UPNP);
if (item.IsFolder)
{
string classType = null;
if (!_profile.RequiresPlainFolders)
{
if (item is MusicAlbum)
{
classType = "object.container.album.musicAlbum";
}
if (item is MusicArtist)
{
classType = "object.container.person.musicArtist";
}
}
objectClass.InnerText = classType ?? "object.container.storageFolder";
}
else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
objectClass.InnerText = "object.item.audioItem.musicTrack";
}
else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
{
objectClass.InnerText = "object.item.imageItem.photo";
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
if (!_profile.RequiresPlainVideoItems && item is Movie)
{
objectClass.InnerText = "object.item.videoItem.movie";
}
else
{
objectClass.InnerText = "object.item.videoItem";
} }
} }
else else
{ {
var audio = dto as Audio; throw new NotSupportedException();
}
if (audio != null) return objectClass;
}
private void AddPeople(BaseItem item, XmlElement element)
{
foreach (var actor in item.People)
{
AddValue(element, "upnp", (actor.Type ?? PersonType.Actor).ToLower(), actor.Name, NS_UPNP);
}
}
private void AddGeneralProperties(BaseItem item, XmlElement element, Filter filter)
{
AddCommonFields(item, element, filter);
var audio = item as Audio;
if (audio != null)
{
foreach (var artist in audio.Artists)
{ {
response += string.Format(DIDL_ARTIST, audio.Artists.FirstOrDefault() ?? UNKNOWN); AddValue(element, "upnp", "artist", artist, NS_UPNP);
response += string.Format(DIDL_ALBUM, audio.Album);
response += string.Format(DIDL_TRACKNUM, audio.IndexNumber ?? 0);
} }
response += GetAudioDIDL(dto, streamUrl, streams); if (!string.IsNullOrEmpty(audio.Album))
if (includeImageRes && !string.IsNullOrWhiteSpace(imageUrl))
{ {
response += string.Format(DIDL_ALBUMIMAGE_RES, imageUrl); AddValue(element, "upnp", "album", audio.Album, NS_UPNP);
}
if (!string.IsNullOrEmpty(audio.AlbumArtist))
{
AddValue(element, "upnp", "albumArtist", audio.AlbumArtist, NS_UPNP);
} }
} }
response += DIDL_END; var album = item as MusicAlbum;
return response; if (album != null)
}
private static string GetVideoDIDL(BaseItem dto, string streamUrl, IEnumerable<MediaStream> streams)
{
var videostream = streams.Where(stream => stream.Type == MediaStreamType.Video).OrderBy(s => s.IsDefault ? 0 : 1).FirstOrDefault();
if (videostream == null)
{ {
// TOOD: ??? if (!string.IsNullOrEmpty(album.AlbumArtist))
return string.Empty; {
AddValue(element, "upnp", "artist", album.AlbumArtist, NS_UPNP);
AddValue(element, "upnp", "albumArtist", album.AlbumArtist, NS_UPNP);
}
} }
return string.Format(DIDL_VIDEO_RES, var musicVideo = item as MusicVideo;
videostream.BitRate.HasValue ? videostream.BitRate.Value / 10 : 0,
GetDurationString(dto),
videostream.Width ?? 0,
videostream.Height ?? 0,
streamUrl);
}
private static string GetAudioDIDL(BaseItem dto, string streamUrl, IEnumerable<MediaStream> streams) if (musicVideo != null)
{
var audiostream = streams.Where(stream => stream.Type == MediaStreamType.Audio).OrderBy(s => s.IsDefault ? 0 : 1).FirstOrDefault();
if (audiostream == null)
{ {
// TOOD: ??? if (!string.IsNullOrEmpty(musicVideo.Artist))
return string.Empty; {
AddValue(element, "upnp", "artist", musicVideo.Artist, NS_UPNP);
}
if (!string.IsNullOrEmpty(musicVideo.Album))
{
AddValue(element, "upnp", "album", musicVideo.Album, NS_UPNP);
}
} }
return string.Format(DIDL_AUDIO_RES, if (item.IndexNumber.HasValue)
audiostream.BitRate.HasValue ? audiostream.BitRate.Value / 10 : 16000, {
GetDurationString(dto), AddValue(element, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
audiostream.SampleRate ?? 0, }
streamUrl);
} }
private static string GetImageUrl(BaseItem dto, string serverAddress) private void AddValue(XmlElement elem, string prefix, string name, string value, string namespaceUri)
{ {
const ImageType imageType = ImageType.Primary; try
if (!dto.HasImage(imageType))
{ {
dto = dto.Parents.FirstOrDefault(i => i.HasImage(imageType)); var date = elem.OwnerDocument.CreateElement(prefix, name, namespaceUri);
date.InnerText = value;
elem.AppendChild(date);
}
catch (XmlException)
{
//_logger.Error("Error adding xml value: " + value);
}
}
private void AddCover(BaseItem item, XmlElement element)
{
var imageInfo = GetImageInfo(item);
if (imageInfo == null)
{
return;
} }
return dto == null ? null : string.Format("{0}/Items/{1}/Images/{2}", serverAddress, dto.Id, imageType); var result = element.OwnerDocument;
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight);
var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
profile.InnerText = _profile.AlbumArtPn;
icon.SetAttributeNode(profile);
icon.InnerText = albumartUrlInfo.Url;
element.AppendChild(icon);
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth, _profile.MaxIconHeight);
icon = result.CreateElement("upnp", "icon", NS_UPNP);
profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
profile.InnerText = _profile.AlbumArtPn;
icon.SetAttributeNode(profile);
icon.InnerText = iconUrlInfo.Url;
element.AppendChild(icon);
if (!_profile.EnableAlbumArtInDidl)
{
return;
}
var res = result.CreateElement(string.Empty, "res", NS_DIDL);
res.InnerText = albumartUrlInfo.Url;
var width = albumartUrlInfo.Width;
var height = albumartUrlInfo.Height;
var mediaProfile = new MediaFormatProfileResolver().ResolveImageFormat("jpg", width, height);
var orgPn = mediaProfile.HasValue ? "DLNA.ORG_PN=:" + mediaProfile.Value + ";" : string.Empty;
res.SetAttribute("protocolInfo", string.Format(
"http-get:*:{1}:{0}DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
orgPn,
"image/jpeg",
DlnaMaps.DefaultStreaming
));
if (width.HasValue && height.HasValue)
{
res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
}
element.AppendChild(res);
} }
private static string GetDurationString(BaseItem dto) private ImageDownloadInfo GetImageInfo(BaseItem item)
{ {
var duration = TimeSpan.FromTicks(dto.RunTimeTicks.HasValue ? dto.RunTimeTicks.Value : 0); if (item.HasImage(ImageType.Primary))
{
return GetImageInfo(item, ImageType.Primary);
}
if (item.HasImage(ImageType.Thumb))
{
return GetImageInfo(item, ImageType.Thumb);
}
// TODO: Bad format string? if (item is Audio || item is Episode)
return string.Format("{0}:{1:00}:2{00}.000", duration.Hours, duration.Minutes, duration.Seconds); {
item = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
if (item != null)
{
return GetImageInfo(item, ImageType.Primary);
}
}
return null;
} }
private static string GetDateString(DateTime? date) private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
{ {
if (!date.HasValue) var imageInfo = item.GetImageInfo(type, 0);
return UNKNOWN; string tag = null;
return string.Format("{0}-{1:00}-{2:00}", date.Value.Year, date.Value.Month, date.Value.Day); try
{
var guid = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
tag = guid.HasValue ? guid.Value.ToString("N") : null;
}
catch
{
}
int? width = null;
int? height = null;
try
{
var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
width = Convert.ToInt32(size.Width);
height = Convert.ToInt32(size.Height);
}
catch
{
}
return new ImageDownloadInfo
{
ItemId = item.Id.ToString("N"),
Type = ImageType.Primary,
ImageTag = tag,
Width = width,
Height = height
};
} }
private static bool IsVideo(BaseItem item) class ImageDownloadInfo
{ {
return string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); internal string ItemId;
internal string ImageTag;
internal ImageType Type;
internal int? Width;
internal int? Height;
}
class ImageUrlInfo
{
internal string Url;
internal int? Width;
internal int? Height;
}
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int? maxWidth, int? maxHeight)
{
var url = string.Format("{0}/Items/{1}/Images/{2}?tag={3}&format=jpg",
_serverAddress,
info.ItemId,
info.Type,
info.ImageTag);
if (maxWidth.HasValue)
{
url += "&maxWidth=" + maxWidth.Value.ToString(_usCulture);
}
if (maxHeight.HasValue)
{
url += "&maxHeight=" + maxHeight.Value.ToString(_usCulture);
}
var width = info.Width;
var height = info.Height;
if (width.HasValue && height.HasValue)
{
if (maxWidth.HasValue || maxHeight.HasValue)
{
var newSize = DrawingUtils.Resize(new ImageSize
{
Height = height.Value,
Width = width.Value
}, maxWidth: maxWidth, maxHeight: maxHeight);
width = Convert.ToInt32(newSize.Width);
height = Convert.ToInt32(newSize.Height);
}
}
return new ImageUrlInfo
{
Url = url,
Width = width,
Height = height
};
} }
} }
} }

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.Net; using System.Security;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Dlna.Common; using MediaBrowser.Dlna.Common;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
@ -299,16 +300,12 @@ namespace MediaBrowser.Dlna.PlayTo
private string CreateDidlMeta(string value) private string CreateDidlMeta(string value)
{ {
if (value == null) if (string.IsNullOrEmpty(value))
return String.Empty; return String.Empty;
var escapedData = value.Replace("<", "&lt;").Replace(">", "&gt;"); return SecurityElement.Escape(value);
return String.Format(BaseDidl, escapedData.Replace("\r\n", ""));
} }
private const string BaseDidl = "&lt;DIDL-Lite xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\"&gt;{0}&lt;/DIDL-Lite&gt;";
public async Task SetNextAvTransport(string value, string header, string metaData) public async Task SetNextAvTransport(string value, string header, string metaData)
{ {
var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetNextAVTransportURI"); var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetNextAVTransportURI");
@ -615,7 +612,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
{ {
return new Tuple<bool, uBaseObject>(true, null); return new Tuple<bool, uBaseObject>(false, null);
} }
XElement uPnpResponse; XElement uPnpResponse;
@ -624,9 +621,9 @@ namespace MediaBrowser.Dlna.PlayTo
{ {
uPnpResponse = XElement.Parse(trackString); uPnpResponse = XElement.Parse(trackString);
} }
catch catch (Exception ex)
{ {
_logger.Error("Unable to parse xml {0}", trackString); _logger.ErrorException("Unable to parse xml {0}", ex, trackString);
return new Tuple<bool, uBaseObject>(true, null); return new Tuple<bool, uBaseObject>(true, null);
} }

View File

@ -1,6 +1,7 @@
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -35,6 +36,7 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
public bool SupportsMediaRemoteControl public bool SupportsMediaRemoteControl
{ {
@ -52,7 +54,7 @@ namespace MediaBrowser.Dlna.PlayTo
} }
} }
public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost, IDtoService dtoService) public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, INetworkManager networkManager, IDlnaManager dlnaManager, IUserManager userManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor)
{ {
_session = session; _session = session;
_itemRepository = itemRepository; _itemRepository = itemRepository;
@ -63,6 +65,7 @@ namespace MediaBrowser.Dlna.PlayTo
_userManager = userManager; _userManager = userManager;
_appHost = appHost; _appHost = appHost;
_dtoService = dtoService; _dtoService = dtoService;
_imageProcessor = imageProcessor;
_logger = logger; _logger = logger;
} }
@ -390,12 +393,11 @@ namespace MediaBrowser.Dlna.PlayTo
playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress); playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress);
var mediaStreams = mediaSources var itemXml =
.Where(i => string.Equals(i.Id, playlistItem.StreamInfo.MediaSourceId)) new DidlBuilder(profile, _imageProcessor, serverAddress, _dtoService).GetItemDidl(item, _session.DeviceId,
.SelectMany(i => i.MediaStreams) new Filter());
.ToList();
playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, mediaStreams, profile.EnableAlbumArtInDidl); playlistItem.Didl = itemXml;
return playlistItem; return playlistItem;
} }

View File

@ -2,6 +2,7 @@
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
@ -38,8 +39,9 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService) public PlayToManager(ILogger logger, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepository, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor)
{ {
_locations = new ConcurrentDictionary<string, DateTime>(); _locations = new ConcurrentDictionary<string, DateTime>();
_tokenSource = new CancellationTokenSource(); _tokenSource = new CancellationTokenSource();
@ -54,6 +56,7 @@ namespace MediaBrowser.Dlna.PlayTo
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
_appHost = appHost; _appHost = appHost;
_dtoService = dtoService; _dtoService = dtoService;
_imageProcessor = imageProcessor;
_config = config; _config = config;
} }
@ -238,7 +241,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (controller == null) if (controller == null)
{ {
sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost, _dtoService); sessionInfo.SessionController = controller = new PlayToController(sessionInfo, _sessionManager, _itemRepository, _libraryManager, _logger, _networkManager, _dlnaManager, _userManager, _appHost, _dtoService, _imageProcessor);
controller.Init(device); controller.Init(device);

View File

@ -2,6 +2,7 @@
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
@ -26,8 +27,9 @@ namespace MediaBrowser.Dlna.PlayTo
private readonly IDlnaManager _dlnaManager; private readonly IDlnaManager _dlnaManager;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService) public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IDtoService dtoService, IImageProcessor imageProcessor)
{ {
_config = config; _config = config;
_sessionManager = sessionManager; _sessionManager = sessionManager;
@ -39,6 +41,7 @@ namespace MediaBrowser.Dlna.PlayTo
_dlnaManager = dlnaManager; _dlnaManager = dlnaManager;
_appHost = appHost; _appHost = appHost;
_dtoService = dtoService; _dtoService = dtoService;
_imageProcessor = imageProcessor;
_logger = logManager.GetLogger("PlayTo"); _logger = logManager.GetLogger("PlayTo");
} }
@ -85,7 +88,8 @@ namespace MediaBrowser.Dlna.PlayTo
_userManager, _userManager,
_dlnaManager, _dlnaManager,
_appHost, _appHost,
_dtoService); _dtoService,
_imageProcessor);
_manager.Start(); _manager.Start();
} }

View File

@ -1,5 +1,4 @@
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
@ -8,8 +7,8 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Dlna.Didl;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging; using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
@ -27,35 +26,29 @@ namespace MediaBrowser.Dlna.Server
{ {
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly DeviceProfile _profile;
private readonly IDtoService _dtoService;
private readonly IImageProcessor _imageProcessor;
private readonly IUserDataManager _userDataManager; private readonly IUserDataManager _userDataManager;
private readonly User _user; private readonly User _user;
private readonly string _serverAddress;
private const string NS_DC = "http://purl.org/dc/elements/1.1/"; private const string NS_DC = "http://purl.org/dc/elements/1.1/";
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"; private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/"; private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
private const string NS_SEC = "http://www.sec.co.kr/";
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/"; private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/"; private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private readonly int _systemUpdateId; private readonly int _systemUpdateId;
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly DidlBuilder _didlBuilder;
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId) public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IDtoService dtoService, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId)
{ {
_logger = logger; _logger = logger;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_profile = profile;
_serverAddress = serverAddress;
_dtoService = dtoService;
_imageProcessor = imageProcessor;
_userDataManager = userDataManager; _userDataManager = userDataManager;
_user = user; _user = user;
_systemUpdateId = systemUpdateId; _systemUpdateId = systemUpdateId;
_didlBuilder = new DidlBuilder(profile, imageProcessor, serverAddress, dtoService);
} }
public ControlResponse ProcessControlRequest(ControlRequest request) public ControlResponse ProcessControlRequest(ControlRequest request)
@ -91,7 +84,7 @@ namespace MediaBrowser.Dlna.Server
sparams.Add(e.LocalName, e.InnerText.Trim()); sparams.Add(e.LocalName, e.InnerText.Trim());
} }
var deviceId = "fgd"; var deviceId = "test";
IEnumerable<KeyValuePair<string, string>> result; IEnumerable<KeyValuePair<string, string>> result;
@ -270,7 +263,7 @@ namespace MediaBrowser.Dlna.Server
if (string.Equals(flag, "BrowseMetadata")) if (string.Equals(flag, "BrowseMetadata"))
{ {
Browse_AddFolder(result, folder, children.Count, filter); result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, children.Count, filter));
provided++; provided++;
} }
else else
@ -293,11 +286,11 @@ namespace MediaBrowser.Dlna.Server
var f = (Folder)i; var f = (Folder)i;
var childCount = GetChildrenSorted(f, user, sortCriteria).Count(); var childCount = GetChildrenSorted(f, user, sortCriteria).Count();
Browse_AddFolder(result, f, childCount, filter); result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter));
} }
else else
{ {
Browse_AddItem(result, i, deviceId, filter); result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, i, deviceId, filter));
} }
} }
} }
@ -368,11 +361,11 @@ namespace MediaBrowser.Dlna.Server
var f = (Folder)i; var f = (Folder)i;
var childCount = GetChildrenSorted(f, user, searchCriteria, sortCriteria).Count(); var childCount = GetChildrenSorted(f, user, searchCriteria, sortCriteria).Count();
Browse_AddFolder(result, f, childCount, filter); result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter));
} }
else else
{ {
Browse_AddItem(result, i, deviceId, filter); result.DocumentElement.AppendChild(_didlBuilder.GetItemElement(result, i, deviceId, filter));
} }
} }
@ -475,637 +468,5 @@ namespace MediaBrowser.Dlna.Server
? user.RootFolder ? user.RootFolder
: _libraryManager.GetItemById(new Guid(id)); : _libraryManager.GetItemById(new Guid(id));
} }
private void Browse_AddFolder(XmlDocument result, Folder f, int childCount, Filter filter)
{
var container = result.CreateElement(string.Empty, "container", NS_DIDL);
container.SetAttribute("restricted", "0");
container.SetAttribute("searchable", "1");
container.SetAttribute("childCount", childCount.ToString(_usCulture));
container.SetAttribute("id", f.Id.ToString("N"));
var parent = f.Parent;
if (parent == null)
{
container.SetAttribute("parentID", "0");
}
else
{
container.SetAttribute("parentID", parent.Id.ToString("N"));
}
AddCommonFields(f, container, filter);
AddCover(f, container);
result.DocumentElement.AppendChild(container);
}
private void AddValue(XmlElement elem, string prefix, string name, string value, string namespaceUri)
{
try
{
var date = elem.OwnerDocument.CreateElement(prefix, name, namespaceUri);
date.InnerText = value;
elem.AppendChild(date);
}
catch (XmlException)
{
//_logger.Error("Error adding xml value: " + value);
}
}
private void Browse_AddItem(XmlDocument result, BaseItem item, string deviceId, Filter filter)
{
var element = result.CreateElement(string.Empty, "item", NS_DIDL);
element.SetAttribute("restricted", "1");
element.SetAttribute("id", item.Id.ToString("N"));
if (item.Parent != null)
{
element.SetAttribute("parentID", item.Parent.Id.ToString("N"));
}
//AddBookmarkInfo(item, user, element);
AddGeneralProperties(item, element, filter);
// refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
var audio = item as Audio;
if (audio != null)
{
AddAudioResource(element, audio, deviceId, filter);
}
var video = item as Video;
if (video != null)
{
AddVideoResource(element, video, deviceId, filter);
}
AddCover(item, element);
result.DocumentElement.AppendChild(element);
}
private void AddVideoResource(XmlElement container, Video video, string deviceId, Filter filter)
{
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
var sources = _dtoService.GetMediaSources(video);
int? maxBitrateSetting = null;
var streamInfo = new StreamBuilder().BuildVideoItem(new VideoOptions
{
ItemId = video.Id.ToString("N"),
MediaSources = sources,
Profile = _profile,
DeviceId = deviceId,
MaxBitrate = maxBitrateSetting
});
var url = streamInfo.ToDlnaUrl(_serverAddress);
res.InnerText = url;
var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId));
if (mediaSource.RunTimeTicks.HasValue)
{
res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
}
if (filter.Contains("res@size"))
{
if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
{
var size = streamInfo.TargetSize;
if (size.HasValue)
{
res.SetAttribute("size", size.Value.ToString(_usCulture));
}
}
}
var totalBitrate = streamInfo.TotalOutputBitrate;
var targetSampleRate = streamInfo.TargetAudioSampleRate;
var targetChannels = streamInfo.TargetAudioChannels;
var targetWidth = streamInfo.TargetWidth;
var targetHeight = streamInfo.TargetHeight;
if (targetChannels.HasValue)
{
res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
}
if (filter.Contains("res@resolution"))
{
if (targetWidth.HasValue && targetHeight.HasValue)
{
res.SetAttribute("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
}
}
if (targetSampleRate.HasValue)
{
res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
}
if (totalBitrate.HasValue)
{
res.SetAttribute("bitrate", totalBitrate.Value.ToString(_usCulture));
}
var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
streamInfo.AudioCodec,
streamInfo.VideoCodec);
var filename = url.Substring(0, url.IndexOf('?'));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
var contentFeatures = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
streamInfo.VideoCodec,
streamInfo.AudioCodec,
targetWidth,
targetHeight,
totalBitrate,
streamInfo.TargetTimestamp,
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks,
streamInfo.TranscodeSeekInfo);
res.SetAttribute("protocolInfo", String.Format(
"http-get:*:{0}:{1}",
mimeType,
contentFeatures
));
container.AppendChild(res);
}
private void AddAudioResource(XmlElement container, Audio audio, string deviceId, Filter filter)
{
var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL);
var sources = _dtoService.GetMediaSources(audio);
var streamInfo = new StreamBuilder().BuildAudioItem(new AudioOptions
{
ItemId = audio.Id.ToString("N"),
MediaSources = sources,
Profile = _profile,
DeviceId = deviceId
});
var url = streamInfo.ToDlnaUrl(_serverAddress);
res.InnerText = url;
var mediaSource = sources.First(i => string.Equals(i.Id, streamInfo.MediaSourceId));
if (mediaSource.RunTimeTicks.HasValue)
{
res.SetAttribute("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
}
if (filter.Contains("res@size"))
{
if (streamInfo.IsDirectStream || streamInfo.EstimateContentLength)
{
var size = streamInfo.TargetSize;
if (size.HasValue)
{
res.SetAttribute("size", size.Value.ToString(_usCulture));
}
}
}
var targetAudioBitrate = streamInfo.TargetAudioBitrate;
var targetSampleRate = streamInfo.TargetAudioSampleRate;
var targetChannels = streamInfo.TargetAudioChannels;
if (targetChannels.HasValue)
{
res.SetAttribute("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
}
if (targetSampleRate.HasValue)
{
res.SetAttribute("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
}
if (targetAudioBitrate.HasValue)
{
res.SetAttribute("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
}
var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
streamInfo.AudioCodec);
var filename = url.Substring(0, url.IndexOf('?'));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
streamInfo.TargetAudioCodec,
targetAudioBitrate,
targetSampleRate,
targetChannels,
streamInfo.IsDirectStream,
streamInfo.RunTimeTicks,
streamInfo.TranscodeSeekInfo);
res.SetAttribute("protocolInfo", String.Format(
"http-get:*:{0}:{1}",
mimeType,
contentFeatures
));
container.AppendChild(res);
}
private XmlElement CreateObjectClass(XmlDocument result, BaseItem item)
{
var objectClass = result.CreateElement("upnp", "class", NS_UPNP);
if (item.IsFolder)
{
string classType = null;
if (!_profile.RequiresPlainFolders)
{
if (item is MusicAlbum)
{
classType = "object.container.album.musicAlbum";
}
if (item is MusicArtist)
{
classType = "object.container.person.musicArtist";
}
}
objectClass.InnerText = classType ?? "object.container.storageFolder";
}
else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
objectClass.InnerText = "object.item.audioItem.musicTrack";
}
else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
{
objectClass.InnerText = "object.item.imageItem.photo";
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
if (!_profile.RequiresPlainVideoItems && item is Movie)
{
objectClass.InnerText = "object.item.videoItem.movie";
}
else
{
objectClass.InnerText = "object.item.videoItem";
}
}
else
{
throw new NotSupportedException();
}
return objectClass;
}
private void AddPeople(BaseItem item, XmlElement element)
{
foreach (var actor in item.People)
{
AddValue(element, "upnp", (actor.Type ?? PersonType.Actor).ToLower(), actor.Name, NS_UPNP);
}
}
private void AddBookmarkInfo(BaseItem item, User user, XmlElement element)
{
var userdata = _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
if (userdata.PlaybackPositionTicks > 0)
{
var dcmInfo = element.OwnerDocument.CreateElement("sec", "dcmInfo", NS_SEC);
dcmInfo.InnerText = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture));
element.AppendChild(dcmInfo);
}
}
/// <summary>
/// Adds fields used by both items and folders
/// </summary>
/// <param name="item">The item.</param>
/// <param name="element">The element.</param>
/// <param name="filter">The filter.</param>
private void AddCommonFields(BaseItem item, XmlElement element, Filter filter)
{
if (filter.Contains("dc:title"))
{
AddValue(element, "dc", "title", item.Name, NS_DC);
}
element.AppendChild(CreateObjectClass(element.OwnerDocument, item));
if (filter.Contains("dc:date"))
{
if (item.PremiereDate.HasValue)
{
AddValue(element, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
}
}
foreach (var genre in item.Genres)
{
AddValue(element, "upnp", "genre", genre, NS_UPNP);
}
foreach (var studio in item.Studios)
{
AddValue(element, "upnp", "publisher", studio, NS_UPNP);
}
if (filter.Contains("dc:description"))
{
if (!string.IsNullOrWhiteSpace(item.Overview))
{
AddValue(element, "dc", "description", item.Overview, NS_DC);
}
}
if (filter.Contains("upnp:longDescription"))
{
if (!string.IsNullOrWhiteSpace(item.Overview))
{
AddValue(element, "upnp", "longDescription", item.Overview, NS_UPNP);
}
}
if (!string.IsNullOrEmpty(item.OfficialRating))
{
if (filter.Contains("dc:rating"))
{
AddValue(element, "dc", "rating", item.OfficialRating, NS_DC);
}
if (filter.Contains("upnp:rating"))
{
AddValue(element, "upnp", "rating", item.OfficialRating, NS_UPNP);
}
}
AddPeople(item, element);
}
private void AddGeneralProperties(BaseItem item, XmlElement element, Filter filter)
{
AddCommonFields(item, element, filter);
var audio = item as Audio;
if (audio != null)
{
foreach (var artist in audio.Artists)
{
AddValue(element, "upnp", "artist", artist, NS_UPNP);
}
if (!string.IsNullOrEmpty(audio.Album))
{
AddValue(element, "upnp", "album", audio.Album, NS_UPNP);
}
if (!string.IsNullOrEmpty(audio.AlbumArtist))
{
AddValue(element, "upnp", "albumArtist", audio.AlbumArtist, NS_UPNP);
}
}
var album = item as MusicAlbum;
if (album != null)
{
if (!string.IsNullOrEmpty(album.AlbumArtist))
{
AddValue(element, "upnp", "artist", album.AlbumArtist, NS_UPNP);
AddValue(element, "upnp", "albumArtist", album.AlbumArtist, NS_UPNP);
}
}
var musicVideo = item as MusicVideo;
if (musicVideo != null)
{
if (!string.IsNullOrEmpty(musicVideo.Artist))
{
AddValue(element, "upnp", "artist", musicVideo.Artist, NS_UPNP);
}
if (!string.IsNullOrEmpty(musicVideo.Album))
{
AddValue(element, "upnp", "album", musicVideo.Album, NS_UPNP);
}
}
if (item.IndexNumber.HasValue)
{
AddValue(element, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
}
}
private void AddCover(BaseItem item, XmlElement element)
{
var imageInfo = GetImageInfo(item);
if (imageInfo == null)
{
return;
}
var result = element.OwnerDocument;
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight);
var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP);
var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
profile.InnerText = _profile.AlbumArtPn;
icon.SetAttributeNode(profile);
icon.InnerText = albumartUrlInfo.Url;
element.AppendChild(icon);
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth, _profile.MaxIconHeight);
icon = result.CreateElement("upnp", "icon", NS_UPNP);
profile = result.CreateAttribute("dlna", "profileID", NS_DLNA);
profile.InnerText = _profile.AlbumArtPn;
icon.SetAttributeNode(profile);
icon.InnerText = iconUrlInfo.Url;
element.AppendChild(icon);
if (!_profile.EnableAlbumArtInDidl)
{
return;
}
var res = result.CreateElement(string.Empty, "res", NS_DIDL);
res.InnerText = albumartUrlInfo.Url;
var width = albumartUrlInfo.Width;
var height = albumartUrlInfo.Height;
var mediaProfile = new MediaFormatProfileResolver().ResolveImageFormat("jpg", width, height);
var orgPn = mediaProfile.HasValue ? "DLNA.ORG_PN=:" + mediaProfile.Value + ";" : string.Empty;
res.SetAttribute("protocolInfo", string.Format(
"http-get:*:{1}:{0}DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS={2}",
orgPn,
"image/jpeg",
DlnaMaps.DefaultStreaming
));
if (width.HasValue && height.HasValue)
{
res.SetAttribute("resolution", string.Format("{0}x{1}", width.Value, height.Value));
}
element.AppendChild(res);
}
private ImageDownloadInfo GetImageInfo(BaseItem item)
{
if (item.HasImage(ImageType.Primary))
{
return GetImageInfo(item, ImageType.Primary);
}
if (item.HasImage(ImageType.Thumb))
{
return GetImageInfo(item, ImageType.Thumb);
}
if (item is Audio || item is Episode)
{
item = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
if (item != null)
{
return GetImageInfo(item, ImageType.Primary);
}
}
return null;
}
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
{
var imageInfo = item.GetImageInfo(type, 0);
string tag = null;
try
{
var guid = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
tag = guid.HasValue ? guid.Value.ToString("N") : null;
}
catch
{
}
int? width = null;
int? height = null;
try
{
var size = _imageProcessor.GetImageSize(imageInfo.Path, imageInfo.DateModified);
width = Convert.ToInt32(size.Width);
height = Convert.ToInt32(size.Height);
}
catch
{
}
return new ImageDownloadInfo
{
ItemId = item.Id.ToString("N"),
Type = ImageType.Primary,
ImageTag = tag,
Width = width,
Height = height
};
}
class ImageDownloadInfo
{
internal string ItemId;
internal string ImageTag;
internal ImageType Type;
internal int? Width;
internal int? Height;
}
class ImageUrlInfo
{
internal string Url;
internal int? Width;
internal int? Height;
}
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int? maxWidth, int? maxHeight)
{
var url = string.Format("{0}/Items/{1}/Images/{2}?tag={3}&format=jpg",
_serverAddress,
info.ItemId,
info.Type,
info.ImageTag);
if (maxWidth.HasValue)
{
url += "&maxWidth=" + maxWidth.Value.ToString(_usCulture);
}
if (maxHeight.HasValue)
{
url += "&maxHeight=" + maxHeight.Value.ToString(_usCulture);
}
var width = info.Width;
var height = info.Height;
if (width.HasValue && height.HasValue)
{
if (maxWidth.HasValue || maxHeight.HasValue)
{
var newSize = DrawingUtils.Resize(new ImageSize
{
Height = height.Value,
Width = width.Value
}, maxWidth: maxWidth, maxHeight: maxHeight);
width = Convert.ToInt32(newSize.Width);
height = Convert.ToInt32(newSize.Height);
}
}
return new ImageUrlInfo
{
Url = url,
Width = width,
Height = height
};
}
} }
} }