From 0ab3a1bf8e437591e8f949ed198f33b95522c703 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Wed, 23 Apr 2014 12:29:21 -0400 Subject: [PATCH] unify didl generation --- MediaBrowser.Dlna/Didl/DidlBuilder.cs | 736 +++++++++++++++--- MediaBrowser.Dlna/PlayTo/Device.cs | 17 +- MediaBrowser.Dlna/PlayTo/DlnaController.cs | 14 +- MediaBrowser.Dlna/PlayTo/PlayToManager.cs | 7 +- .../PlayTo/PlayToServerEntryPoint.cs | 8 +- MediaBrowser.Dlna/Server/ControlHandler.cs | 661 +--------------- 6 files changed, 667 insertions(+), 776 deletions(-) diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 5c7992a065..27aae7d9c1 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -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.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; +using System.Xml; namespace MediaBrowser.Dlna.Didl { public class DidlBuilder { - const string CRLF = "\r\n"; - const string UNKNOWN = "Unknown"; + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + 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 = @"" + CRLF; - const string DIDL_TITLE = @" {0}" + CRLF; - const string DIDL_ARTIST = @"{0}" + CRLF; - const string DIDL_ALBUM = @"{0}" + CRLF; - const string DIDL_TRACKNUM = @"{0}" + CRLF; - const string DIDL_VIDEOCLASS = @" object.item.videoItem" + CRLF; - const string DIDL_AUDIOCLASS = @" object.item.audioItem.musicTrack" + CRLF; - const string DIDL_IMAGE = @" {0}" + CRLF + - @" {0}" + CRLF; - const string DIDL_RELEASEDATE = @" {0}" + CRLF; - const string DIDL_GENRE = @" {0}" + CRLF; - const string DESCRIPTION = @" {0}" + CRLF; - const string DIDL_VIDEO_RES = @" {4}" + CRLF; - const string DIDL_AUDIO_RES = @" {3}" + CRLF; - const string DIDL_IMAGE_RES = @" {0}" + CRLF; - const string DIDL_ALBUMIMAGE_RES = @" {0}" + CRLF; - const string DIDL_RATING = @" {0}" + CRLF; - const string DIDL_END = ""; + private readonly DeviceProfile _profile; + private readonly IImageProcessor _imageProcessor; + private readonly string _serverAddress; + private readonly IDtoService _dtoService; + + public DidlBuilder(DeviceProfile profile, IImageProcessor imageProcessor, string serverAddress, IDtoService dtoService) + { + _profile = profile; + _imageProcessor = imageProcessor; + _serverAddress = serverAddress; + _dtoService = dtoService; + } + + public string GetItemDidl(BaseItem item, string deviceId, Filter filter) + { + var result = new XmlDocument(); + + 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); + // } + //} /// - /// Builds a Didl MetaData object for the specified dto. + /// Adds fields used by both items and folders /// - /// The dto. - /// The user identifier. - /// The server address. - /// The stream URL. - /// The streams. - /// if set to true [include image resource]. - /// System.String. - public static string Build(BaseItem dto, string userId, string serverAddress, string streamUrl, IEnumerable streams, bool includeImageRes) + /// The item. + /// The element. + /// The filter. + private void AddCommonFields(BaseItem item, XmlElement element, Filter filter) { - string response = string.Format(DIDL_START, dto.Id, userId); - 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)) + if (filter.Contains("dc:title")) { - 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; - response += string.Format(DIDL_GENRE, UNKNOWN); + element.AppendChild(CreateObjectClass(element.OwnerDocument, item)); - if (IsVideo(dto)) + if (filter.Contains("dc:date")) { - response += string.Format(DESCRIPTION, UNKNOWN); - response += GetVideoDIDL(dto, streamUrl, streams); - - if (includeImageRes && !string.IsNullOrWhiteSpace(imageUrl)) + if (item.PremiereDate.HasValue) { - 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 { - 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); - response += string.Format(DIDL_ALBUM, audio.Album); - - response += string.Format(DIDL_TRACKNUM, audio.IndexNumber ?? 0); + AddValue(element, "upnp", "artist", artist, NS_UPNP); } - response += GetAudioDIDL(dto, streamUrl, streams); - - if (includeImageRes && !string.IsNullOrWhiteSpace(imageUrl)) + if (!string.IsNullOrEmpty(audio.Album)) { - 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; - } - - private static string GetVideoDIDL(BaseItem dto, string streamUrl, IEnumerable streams) - { - var videostream = streams.Where(stream => stream.Type == MediaStreamType.Video).OrderBy(s => s.IsDefault ? 0 : 1).FirstOrDefault(); - - if (videostream == null) + if (album != null) { - // TOOD: ??? - return string.Empty; + if (!string.IsNullOrEmpty(album.AlbumArtist)) + { + AddValue(element, "upnp", "artist", album.AlbumArtist, NS_UPNP); + AddValue(element, "upnp", "albumArtist", album.AlbumArtist, NS_UPNP); + } } - return string.Format(DIDL_VIDEO_RES, - videostream.BitRate.HasValue ? videostream.BitRate.Value / 10 : 0, - GetDurationString(dto), - videostream.Width ?? 0, - videostream.Height ?? 0, - streamUrl); - } + var musicVideo = item as MusicVideo; - private static string GetAudioDIDL(BaseItem dto, string streamUrl, IEnumerable streams) - { - var audiostream = streams.Where(stream => stream.Type == MediaStreamType.Audio).OrderBy(s => s.IsDefault ? 0 : 1).FirstOrDefault(); - - if (audiostream == null) + if (musicVideo != null) { - // TOOD: ??? - return string.Empty; + 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); + } } - return string.Format(DIDL_AUDIO_RES, - audiostream.BitRate.HasValue ? audiostream.BitRate.Value / 10 : 16000, - GetDurationString(dto), - audiostream.SampleRate ?? 0, - streamUrl); + if (item.IndexNumber.HasValue) + { + AddValue(element, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP); + } } - 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; - - if (!dto.HasImage(imageType)) + try { - 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? - return string.Format("{0}:{1:00}:2{00}.000", duration.Hours, duration.Minutes, duration.Seconds); + 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 static string GetDateString(DateTime? date) + private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type) { - if (!date.HasValue) - return UNKNOWN; + var imageInfo = item.GetImageInfo(type, 0); + 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 + }; } } } diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 6897c9e1aa..c7ab3e477a 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.Net; +using System.Security; +using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Dlna.Common; using MediaBrowser.Model.Logging; @@ -299,16 +300,12 @@ namespace MediaBrowser.Dlna.PlayTo private string CreateDidlMeta(string value) { - if (value == null) + if (string.IsNullOrEmpty(value)) return String.Empty; - var escapedData = value.Replace("<", "<").Replace(">", ">"); - - return String.Format(BaseDidl, escapedData.Replace("\r\n", "")); + return SecurityElement.Escape(value); } - private const string BaseDidl = "<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/\">{0}</DIDL-Lite>"; - public async Task SetNextAvTransport(string value, string header, string metaData) { 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)) { - return new Tuple(true, null); + return new Tuple(false, null); } XElement uPnpResponse; @@ -624,9 +621,9 @@ namespace MediaBrowser.Dlna.PlayTo { 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(true, null); } diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index b037113d5f..28fc4474e9 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Net; using MediaBrowser.Controller; using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -35,6 +36,7 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IUserManager _userManager; private readonly IServerApplicationHost _appHost; private readonly IDtoService _dtoService; + private readonly IImageProcessor _imageProcessor; 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; _itemRepository = itemRepository; @@ -63,6 +65,7 @@ namespace MediaBrowser.Dlna.PlayTo _userManager = userManager; _appHost = appHost; _dtoService = dtoService; + _imageProcessor = imageProcessor; _logger = logger; } @@ -390,12 +393,11 @@ namespace MediaBrowser.Dlna.PlayTo playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress); - var mediaStreams = mediaSources - .Where(i => string.Equals(i.Id, playlistItem.StreamInfo.MediaSourceId)) - .SelectMany(i => i.MediaStreams) - .ToList(); + var itemXml = + new DidlBuilder(profile, _imageProcessor, serverAddress, _dtoService).GetItemDidl(item, _session.DeviceId, + new Filter()); - playlistItem.Didl = DidlBuilder.Build(item, _session.UserId.ToString(), serverAddress, playlistItem.StreamUrl, mediaStreams, profile.EnableAlbumArtInDidl); + playlistItem.Didl = itemXml; return playlistItem; } diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index a7a04038c4..10e82a2276 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; @@ -38,8 +39,9 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IServerConfigurationManager _config; private readonly IServerApplicationHost _appHost; 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(); _tokenSource = new CancellationTokenSource(); @@ -54,6 +56,7 @@ namespace MediaBrowser.Dlna.PlayTo _dlnaManager = dlnaManager; _appHost = appHost; _dtoService = dtoService; + _imageProcessor = imageProcessor; _config = config; } @@ -238,7 +241,7 @@ namespace MediaBrowser.Dlna.PlayTo 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); diff --git a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs index c6fbcb90f8..15bebd9962 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; @@ -26,8 +27,9 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IDlnaManager _dlnaManager; private readonly IServerApplicationHost _appHost; 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; _sessionManager = sessionManager; @@ -39,6 +41,7 @@ namespace MediaBrowser.Dlna.PlayTo _dlnaManager = dlnaManager; _appHost = appHost; _dtoService = dtoService; + _imageProcessor = imageProcessor; _logger = logManager.GetLogger("PlayTo"); } @@ -85,7 +88,8 @@ namespace MediaBrowser.Dlna.PlayTo _userManager, _dlnaManager, _appHost, - _dtoService); + _dtoService, + _imageProcessor); _manager.Start(); } diff --git a/MediaBrowser.Dlna/Server/ControlHandler.cs b/MediaBrowser.Dlna/Server/ControlHandler.cs index 8a0b0a3d5e..734adf7952 100644 --- a/MediaBrowser.Dlna/Server/ControlHandler.cs +++ b/MediaBrowser.Dlna/Server/ControlHandler.cs @@ -1,5 +1,4 @@ using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -8,8 +7,8 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Dlna.Didl; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; @@ -27,35 +26,29 @@ namespace MediaBrowser.Dlna.Server { private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; - private readonly DeviceProfile _profile; - private readonly IDtoService _dtoService; - private readonly IImageProcessor _imageProcessor; private readonly IUserDataManager _userDataManager; private readonly User _user; - private readonly string _serverAddress; - 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_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_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/"; private readonly int _systemUpdateId; 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) { _logger = logger; _libraryManager = libraryManager; - _profile = profile; - _serverAddress = serverAddress; - _dtoService = dtoService; - _imageProcessor = imageProcessor; _userDataManager = userDataManager; _user = user; _systemUpdateId = systemUpdateId; + + _didlBuilder = new DidlBuilder(profile, imageProcessor, serverAddress, dtoService); } public ControlResponse ProcessControlRequest(ControlRequest request) @@ -91,7 +84,7 @@ namespace MediaBrowser.Dlna.Server sparams.Add(e.LocalName, e.InnerText.Trim()); } - var deviceId = "fgd"; + var deviceId = "test"; IEnumerable> result; @@ -270,7 +263,7 @@ namespace MediaBrowser.Dlna.Server if (string.Equals(flag, "BrowseMetadata")) { - Browse_AddFolder(result, folder, children.Count, filter); + result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, children.Count, filter)); provided++; } else @@ -293,11 +286,11 @@ namespace MediaBrowser.Dlna.Server var f = (Folder)i; var childCount = GetChildrenSorted(f, user, sortCriteria).Count(); - Browse_AddFolder(result, f, childCount, filter); + result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter)); } 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 childCount = GetChildrenSorted(f, user, searchCriteria, sortCriteria).Count(); - Browse_AddFolder(result, f, childCount, filter); + result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter)); } 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 : _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); - } - } - - /// - /// Adds fields used by both items and folders - /// - /// The item. - /// The element. - /// The filter. - 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 - }; - } } }