diff options
Diffstat (limited to 'MediaBrowser.Plugins.Dlna/Plugin.cs')
| -rw-r--r-- | MediaBrowser.Plugins.Dlna/Plugin.cs | 1027 |
1 files changed, 0 insertions, 1027 deletions
diff --git a/MediaBrowser.Plugins.Dlna/Plugin.cs b/MediaBrowser.Plugins.Dlna/Plugin.cs deleted file mode 100644 index 9cca31d34..000000000 --- a/MediaBrowser.Plugins.Dlna/Plugin.cs +++ /dev/null @@ -1,1027 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.IO; -using System.Linq; -using System.Reflection; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Common.Plugins; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Model.Entities; -using MediaBrowser.Plugins.Dlna.Configuration; - -namespace MediaBrowser.Plugins.Dlna -{ - /// <summary> - /// Class Plugin - /// </summary> - [Export(typeof(IPlugin))] - public class Plugin : BasePlugin<PluginConfiguration> - { - //these are Neptune values, they probably belong in the managed wrapper somewhere, but they aren't - //techincally theres 50 to 100 of these values, but these 3 seem to be the most useful - private const int NEP_Failure = -1; - private const int NEP_NotImplemented = -2012; - private const int NEP_Success = 0; - - private Platinum.UPnP _Upnp; - private Platinum.MediaConnect _PlatinumServer; - private User _CurrentUser; - - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public override string Name - { - get { return "DLNA Server"; } - } - - /// <summary> - /// Gets the description. - /// </summary> - /// <value>The description.</value> - public override string Description - { - get { return "DLNA Server"; } - } - - /// <summary> - /// Gets the instance. - /// </summary> - /// <value>The instance.</value> - public static Plugin Instance { get; private set; } - - /// <summary> - /// Initializes a new instance of the <see cref="Plugin" /> class. - /// </summary> - public Plugin() - : base() - { - Instance = this; - } - - /// <summary> - /// Initializes the on server. - /// </summary> - /// <param name="isFirstRun">if set to <c>true</c> [is first run].</param> - protected override void InitializeOnServer(bool isFirstRun) - { - base.InitializeOnServer(isFirstRun); - - Kernel.ReloadCompleted += Kernel_ReloadCompleted; - AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; - - } - - /// <summary> - /// Handles the AssemblyResolve event of the CurrentDomain control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="args">The <see cref="ResolveEventArgs" /> instance containing the event data.</param> - /// <returns>Assembly.</returns> - Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) - { - var askedAssembly = new AssemblyName(args.Name); - - var resourcePath = "MediaBrowser.Plugins.Dlna.Assemblies." + askedAssembly.Name + ".dll"; - - using (var stream = GetType().Assembly.GetManifestResourceStream(resourcePath)) - { - if (stream != null) - { - Logger.Info("Loading assembly from resource {0}", resourcePath); - - using (var memoryStream = new MemoryStream()) - { - stream.CopyTo(memoryStream); - - memoryStream.Position = 0; - - return Assembly.Load(memoryStream.ToArray()); - } - } - } - - return null; - } - - /// <summary> - /// Disposes the on server. - /// </summary> - /// <param name="dispose">if set to <c>true</c> [dispose].</param> - protected override void DisposeOnServer(bool dispose) - { - if (dispose) - { - Kernel.ReloadCompleted -= Kernel_ReloadCompleted; - AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve; - DisposeDlnaServer(); - } - - base.DisposeOnServer(dispose); - } - - /// <summary> - /// Handles the ReloadCompleted event of the Kernel control. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> - void Kernel_ReloadCompleted(object sender, EventArgs e) - { - InitializeDlnaServer(); - } - - /// <summary> - /// Initializes the dlna server. - /// </summary> - private void InitializeDlnaServer() - { - this.SetupUPnPServer(); - } - - /// <summary> - /// Disposes the dlna server. - /// </summary> - private void DisposeDlnaServer() - { - this.CleanupUPnPServer(); - } - - private void SetupUPnPServer() - { - - this._Upnp = new Platinum.UPnP(); - // Will need a config setting to set the friendly name of the upnp server - //this._PlatinumServer = new Platinum.MediaConnect("MB3 UPnP", "MB3UPnP", 1901); - if (this.Configuration.DlnaPortNumber.HasValue) - this._PlatinumServer = new Platinum.MediaConnect(this.Configuration.FriendlyDlnaName, "MB3UPnP", this.Configuration.DlnaPortNumber.Value); - else - this._PlatinumServer = new Platinum.MediaConnect(this.Configuration.FriendlyDlnaName); - - this._PlatinumServer.BrowseMetadata += new Platinum.MediaConnect.BrowseMetadataDelegate(server_BrowseMetadata); - this._PlatinumServer.BrowseDirectChildren += new Platinum.MediaConnect.BrowseDirectChildrenDelegate(server_BrowseDirectChildren); - this._PlatinumServer.ProcessFileRequest += new Platinum.MediaConnect.ProcessFileRequestDelegate(server_ProcessFileRequest); - this._PlatinumServer.SearchContainer += new Platinum.MediaConnect.SearchContainerDelegate(server_SearchContainer); - - this._Upnp.AddDeviceHost(this._PlatinumServer); - this._Upnp.Start(); - } - - private void CleanupUPnPServer() - { - if (this._Upnp != null && this._Upnp.Running) - this._Upnp.Stop(); - - if (this._PlatinumServer != null) - { - this._PlatinumServer.BrowseMetadata -= new Platinum.MediaConnect.BrowseMetadataDelegate(server_BrowseMetadata); - this._PlatinumServer.BrowseDirectChildren -= new Platinum.MediaConnect.BrowseDirectChildrenDelegate(server_BrowseDirectChildren); - this._PlatinumServer.ProcessFileRequest -= new Platinum.MediaConnect.ProcessFileRequestDelegate(server_ProcessFileRequest); - this._PlatinumServer.SearchContainer -= new Platinum.MediaConnect.SearchContainerDelegate(server_SearchContainer); - - this._PlatinumServer.Dispose(); - this._PlatinumServer = null; - } - - if (this._Upnp != null) - { - this._Upnp.Dispose(); - this._Upnp = null; - } - } - - private int server_BrowseMetadata(Platinum.Action action, string object_id, string filter, int starting_index, int requested_count, string sort_criteria, Platinum.HttpRequestContext context) - { - Logger.Info("BrowseMetadata Entered - Parameters: action:{0} object_id:{1} filter:{2} starting_index:{3} requested_count:{4} sort_criteria:{5} context:{6}", - action.ToLogString(), object_id, filter, starting_index, requested_count, sort_criteria, context.ToLogString()); - - //nothing much seems to call BrowseMetadata so far - //but perhaps that is because we aren't handing out the correct info for the client to call this... I don't know - - //PS3 calls it - //Parameters: action:Action Name:Browse Description:Platinum.ActionDescription Arguments: object_id:0 - //filter:@id,upnp:class,res,res@protocolInfo,res@av:authenticationUri,res@size,dc:title,upnp:albumArtURI,res@dlna:ifoFileURI,res@protection,res@bitrate,res@duration,res@sampleFrequency,res@bitsPerSample,res@nrAudioChannels,res@resolution,res@colorDepth,dc:date,av:dateTime,upnp:artist,upnp:album,upnp:genre,dc:contributer,upnp:storageFree,upnp:storageUsed,upnp:originalTrackNumber,dc:publisher,dc:language,dc:region,dc:description,upnp:toc,@childCount,upnp:albumArtURI@dlna:profileID,res@dlna:cleartextSize - //starting_index:0 requested_count:1 sort_criteria: context:HttpRequestContext LocalAddress:HttpRequestContext.SocketAddress IP:192.168.1.56 Port:1845 RemoteAddress:HttpRequestContext.SocketAddress IP:192.168.1.40 Port:49277 Request:http://192.168.1.56:1845/ContentDirectory/7c6b1b90-872b-2cda-3c5c-21a0e430ce5e/control.xml Signature:PS3 - - - - if (object_id == "0") - { - var root = new Platinum.MediaContainer(); - root.Title = "Root"; - root.ObjectID = "0"; - root.ParentID = "-1"; - root.Class = new Platinum.ObjectClass("object.container.storageFolder", ""); - - var didl = Platinum.Didl.header + root.ToDidl(filter) + Platinum.Didl.footer; - action.SetArgumentValue("Result", didl); - action.SetArgumentValue("NumberReturned", "1"); - action.SetArgumentValue("TotalMatches", "1"); - - // update ID may be wrong here, it should be the one of the container? - action.SetArgumentValue("UpdateId", "1"); - - return NEP_Success; - } - else - { - return NEP_Failure; - } - } - private int server_BrowseDirectChildren(Platinum.Action action, String object_id, String filter, Int32 starting_index, Int32 requested_count, String sort_criteria, Platinum.HttpRequestContext context) - { - Logger.Info("BrowseDirectChildren Entered - Parameters: action:{0} object_id:{1} filter:{2} starting_index:{3} requested_count:{4} sort_criteria:{5} context:{6}", - action.ToLogString(), object_id, filter, starting_index, requested_count, sort_criteria, context.ToLogString()); - - //WMP doesn't care how many results we return and what type they are - //Xbox360 Music App is unknown, it calls SearchContainer and stops, not sure what will happen once we return it results - //XBox360 Video App has fairly specific filter string and it need it - if you serve it music (mp3), it'll put music in the video list, so we have to do our own filtering - - //XBox360 Video App - // action: "Browse" - // object_id: "15" - // filter: "dc:title,res,res@protection,res@duration,res@bitrate,upnp:genre,upnp:actor,res@microsoft:codec" - // starting_index: 0 - // requested_count: 100 - // sort_criteria: "+upnp:class,+dc:title" - // - //the wierd thing about the filter is that there isn't much in it that says "only give me video"... except... - //if we look at the doc available here: http://msdn.microsoft.com/en-us/library/windows/hardware/gg487545.aspx which describes the way WMP does its DLNA serving (not clienting) - //doc is also a search cached google docs here: https://docs.google.com/viewer?a=v&q=cache:tnrdpTFCc84J:download.microsoft.com/download/0/0/b/00bba048-35e6-4e5b-a3dc-36da83cbb0d1/NetCompat_WMP11.docx+&hl=en&gl=au&pid=bl&srcid=ADGEESiBSKE1ZJeWmgYVOkKmRJuYaSL3_50KL1o6Ugp28JL1Ytq-2QbEeu6xFD8rbWcX35ZG4d7qPQnzqURGR5vig79S2Arj5umQNPnLeJo1k5_iWYbqPejeMHwwAv0ATmq2ynoZCBNL&sig=AHIEtbQ2qZJ8xMXLZYBWHHerezzXShKoVg - //it describes object 15 as beeing Root/Video/Folders which can contain object.storageFolder - //so perhaps thats what is saying 'only give me video' - //I'm just not sure if those folders listed with object IDs are all well known across clients or if these ones are WMP specific - //if they are device specific but also significant, then that might explain why Plex goes to the trouble of having configurable client device profiles for its DLNA server - - var didl = Platinum.Didl.header; - - IEnumerable<BaseItem> children = null; - - // I need to ask someone on the MB team if there's a better way to do this, it seems like it - //could get pretty expensive to get ALL children all the time - //if it's our only option perhaps we should cache results locally or something similar - children = this.CurrentUser.RootFolder.GetRecursiveChildren(this.CurrentUser); - //children = children.Filter(Extensions.FilterType.Music | Extensions.FilterType.Video).Page(starting_index, requested_count); - - int itemCount = 0; - - if (children != null) - { - foreach (var child in children) - { - - using (var item = BaseItemToMediaItem(child, context)) - { - if (item != null) - { - string test; - test = item.ToDidl(filter); - didl += item.ToDidl(filter); - itemCount++; - } - } - } - - didl += Platinum.Didl.footer; - - action.SetArgumentValue("Result", didl); - action.SetArgumentValue("NumberReturned", itemCount.ToString()); - action.SetArgumentValue("TotalMatches", itemCount.ToString()); - - // update ID may be wrong here, it should be the one of the container? - action.SetArgumentValue("UpdateId", "1"); - - return NEP_Success; - } - return NEP_Failure; - } - private int server_ProcessFileRequest(Platinum.HttpRequestContext context, Platinum.HttpResponse response) - { - Logger.Info("ProcessFileRequest Entered - Parameters: context:{0} response:{1}", - context.ToLogString(), response); - - Uri uri = context.Request.URI; - var id = uri.AbsolutePath.TrimStart('/'); - Guid itemID; - if (Guid.TryParseExact(id, "D", out itemID)) - { - var item = this.CurrentUser.RootFolder.FindItemById(itemID, this.CurrentUser); - - if (item != null) - { - //this is how the Xbox 360 Video app asks for artwork, it tacks this query string onto its request - //?albumArt=true - if (uri.Query == "?albumArt=true") - { - if (!string.IsNullOrWhiteSpace(item.PrimaryImagePath)) - //let see if we can serve artwork like this to the Xbox 360 Video App - Platinum.MediaServer.SetResponseFilePath(context, response, Kernel.HttpServerUrlPrefix.Replace("+", context.LocalAddress.ip) + "/api/image?id=" + item.Id.ToString() + "&type=primary"); - //Platinum.MediaServer.SetResponseFilePath(context, response, item.PrimaryImagePath); - } - else - Platinum.MediaServer.SetResponseFilePath(context, response, item.Path); - //this does not work for WMP - //Platinum.MediaServer.SetResponseFilePath(context, response, Kernel.HttpServerUrlPrefix.Replace("+", context.LocalAddress.ip) + "/api/video.ts?id=" + item.Id.ToString()); - - return NEP_Success; - } - } - return NEP_Failure; - } - private int server_SearchContainer(Platinum.Action action, string object_id, string searchCriteria, string filter, int starting_index, int requested_count, string sort_criteria, Platinum.HttpRequestContext context) - { - Logger.Info("SearchContainer Entered - Parameters: action:{0} object_id:{1} searchCriteria:{7} filter:{2} starting_index:{3} requested_count:{4} sort_criteria:{5} context:{6}", - action.ToLogString(), object_id, filter, starting_index, requested_count, sort_criteria, context.ToLogString(), searchCriteria); - - //Doesn't call search at all: - // XBox360 Video App - - //Calls search but does not require it to be implemented: - // WMP, probably uses it just for its "images" section - - //Calls search Seems to require it: - // XBox360 Music App - - //WMP - // action: "Search" - // object_id: "0" - // searchCriteria: "upnp:class derivedfrom \"object.item.imageItem\" and @refID exists false" - // filter: "*" - // starting_index: 0 - // requested_count: 200 - // sort_criteria: "-dc:date" - - //XBox360 Music App - // action: "Search" - // object_id: "7" - // searchCriteria: "(upnp:class = \"object.container.album.musicAlbum\")" - // filter: "dc:title,upnp:artist" - // starting_index: 0 - // requested_count: 1000 - // sort_criteria: "+dc:title" - // - //XBox360 Music App seems to work souly using SearchContainer and ProcessFileRequest - //I think the current resource Uri's aren't going to work because it seems to require an extension like .mp3 to work, but this requires further testing - //When hitting the Album tab of the app it's searching criteria is object.container.album.musicAlbum - //this means it wants albums put into containers, I thought Platinum might do this for us, but it doesn't - - - var didl = Platinum.Didl.header; - - IEnumerable<BaseItem> children = null; - - // I need to ask someone on the MB team if there's a better way to do this, it seems like it - //could get pretty expensive to get ALL children all the time - //if it's our only option perhaps we should cache results locally or something similar - children = this.CurrentUser.RootFolder.GetRecursiveChildren(this.CurrentUser); - //children = children.Filter(Extensions.FilterType.Music | Extensions.FilterType.Video).Page(starting_index, requested_count); - - //var test = GetFilterFromCriteria(searchCriteria); - children = children.Where(GetBaseItemMatchFromCriteria(searchCriteria)); - - - int itemCount = 0; - - if (children != null) - { - Platinum.MediaItem item = null; - foreach (var child in children) - { - item = BaseItemToMediaItem(child, context); - - if (item != null) - { - item.ParentID = string.Empty; - - didl += item.ToDidl(filter); - itemCount++; - } - } - - didl += Platinum.Didl.footer; - - action.SetArgumentValue("Result", didl); - action.SetArgumentValue("NumberReturned", itemCount.ToString()); - action.SetArgumentValue("TotalMatches", itemCount.ToString()); - - // update ID may be wrong here, it should be the one of the container? - action.SetArgumentValue("UpdateId", "1"); - - return NEP_Success; - } - return NEP_Failure; - } - - private Platinum.MediaItem BaseItemToMediaItem(BaseItem child, Platinum.HttpRequestContext context) - { - Platinum.MediaItem result = null; - Platinum.MediaResource resource = null; - - if (child.IsFolder) - { - //DLNA is a fairly flat system, there doesn't appear to be much room in the system for folders so far - //I haven't tested too many DLNA clients yet tho - result = null; - //item = new Platinum.MediaItem(); - //item.Class = new Platinum.ObjectClass("object.container.storageFolder", ""); - } - else if (child is Episode) - { - result = MediaItemHelper.GetMediaItem((Episode)child); - resource = MediaItemHelper.GetMediaResource((Episode)child); - } - else if (child is Video) - { - result = MediaItemHelper.GetMediaItem((Video)child); - resource = MediaItemHelper.GetMediaResource((Video)child); - } - else if (child is Audio) - { - result = MediaItemHelper.GetMediaItem((Audio)child); - resource = MediaItemHelper.GetMediaResource((Audio)child); - } - - if (result != null) - { - //have a go at finding the mime type - var mimeType = string.Empty; - if (child.Path != null && Path.HasExtension(child.Path)) - mimeType = MimeTypes.GetMimeType(child.Path); - - resource.ProtoInfo = Platinum.ProtocolInfo.GetProtocolInfoFromMimeType(mimeType, true, context); - - // get list of ips and make sure the ip the request came from is used for the first resource returned - // this ensures that clients which look only at the first resource will be able to reach the item - IEnumerable<String> ips = GetUPnPIPAddresses(context); //.Distinct(); - - // iterate through all ips and create a resource for each - // I think we need extensions (".mp3" type extensions) on these for Xbox360 Video and Music apps to work - - - //resource.URI = new Uri(Kernel.HttpServerUrlPrefix + "/api/video.ts?id=" + child.Id.ToString("D")).ToString(); - //result.AddResource(resource); - - foreach (String ip in ips) - { - //doesn't work for WMP - //resource.URI = new Uri(Kernel.HttpServerUrlPrefix.Replace("+", ip) + "/api/video.ts?id=" + child.Id.ToString()).ToString(); - resource.URI = new Uri("http://" + ip + ":" + context.LocalAddress.port + "/" + child.Id.ToString("D")).ToString(); - - result.AddResource(resource); - } - MediaItemHelper.AddAlbumArtInfoToMediaItem(result, child, Kernel.HttpServerUrlPrefix, ips); - } - - return result; - } - - private void AddResourcesToMediaItem(Platinum.MediaItem item, BaseItem child, Platinum.HttpRequestContext context) - { - Platinum.MediaResource resource = null; - - if (child is Video) - { - var videoChild = (Video)child; - resource = new Platinum.MediaResource(); - - if (videoChild.DefaultVideoStream != null) - { - //Bitrate is Bytes per second - if (videoChild.DefaultVideoStream.BitRate.HasValue) - resource.Bitrate = (uint)videoChild.DefaultVideoStream.BitRate; - - //not sure if we know Colour Depth - //resource.ColorDepth - if (videoChild.DefaultVideoStream.Channels.HasValue) - resource.NbAudioChannels = (uint)videoChild.DefaultVideoStream.Channels.Value; - - //resource.Protection - //resource.ProtoInfo - - //we must know resolution, I'm just not sure how to get it - //resource.Resolution - - //I'm not sure what this actually means, is it Sample Rate - if (videoChild.DefaultVideoStream.SampleRate.HasValue) - resource.SampleFrequency = (uint)videoChild.DefaultVideoStream.SampleRate.Value; - //file size? - //resource.Size - - } - } - else if (child is Audio) - { - - } - - // get list of ips and make sure the ip the request came from is used for the first resource returned - // this ensures that clients which look only at the first resource will be able to reach the item - List<String> ips = GetUPnPIPAddresses(context); - - // iterate through all ips and create a resource for each - // I think we need extensions (".mp3" type extensions) on these for Xbox360 Video and Music apps to work - - foreach (String ip in ips) - { - resource.URI = new Uri("http://" + ip + ":" + context.LocalAddress.port + "/" + child.Id.ToString("D")).ToString(); - item.AddResource(resource); - } - } - - /// <summary> - /// Gets a list of valid IP Addresses that the UPnP server is using - /// </summary> - /// <param name="context"></param> - /// <returns></returns> - private List<String> GetUPnPIPAddresses(Platinum.HttpRequestContext context) - { - // get list of ips and make sure the ip the request came from is used for the first resource returned - // this ensures that clients which look only at the first resource will be able to reach the item - List<String> result = Platinum.UPnP.GetIpAddresses(true); //if this call is expensive we could cache the results - String localIP = context.LocalAddress.ip; - if (localIP != "0.0.0.0") - { - result.Remove(localIP); - result.Insert(0, localIP); - } - return result; - } - - /// <summary> - /// Gets the MB User with a user name that matches the user name configured in the plugin config - /// </summary> - /// <returns>MediaBrowser.Controller.Entities.User</returns> - private User CurrentUser - { - get - { - if (this._CurrentUser == null) - { - //this looks like a lot of processing but it really isn't - //its mostly gaurding against no users or no matching user existing - var serverKernel = Controller.Kernel.Instance; - if (serverKernel.Users.Any()) - { - if (string.IsNullOrWhiteSpace(this.Configuration.UserName)) - this._CurrentUser = serverKernel.Users.First(); - else - { - this._CurrentUser = serverKernel.Users.FirstOrDefault(i => string.Equals(i.Name, this.Configuration.UserName, StringComparison.OrdinalIgnoreCase)); - if (this._CurrentUser == null) - { - //log and return first user - this._CurrentUser = serverKernel.Users.First(); - Logger.Error("Configured user: \"{0}\" not found. Using first user found: \"{1}\" instead", this.Configuration.UserName, this._CurrentUser.Name); - } - } - } - else - { - Logger.Fatal("No users in the system"); - this._CurrentUser = null; - } - } - return this._CurrentUser; - } - } - - #region "A Search Idea" - //this is just an idea of how we might do some search - //it's a bit lackluster in places and might be overkill in others - //all in all it might not be a good idea, but I thought I'd see how it felt - - private Func<BaseItem, bool> GetBaseItemMatchFromCriteria(string searchCriteria) - { - //WMP Search when clicking Music: - //"upnp:class derivedfrom \"object.item.audioItem\" and @refID exists false" - //WMP Search when clicking Videos: - //"upnp:class derivedfrom \"object.item.videoItem\" and @refID exists false" - //WMP Search when clicking Pictures: - //"upnp:class derivedfrom \"object.item.imageItem\" and @refID exists false" - //WMP Search when clicking Recorded TV: - //"upnp:class derivedfrom \"object.item.videoItem\" and @refID exists false" - - //we really need a syntax tree parser here - //but the requests never seem to get more complex than "'Condition One' And 'Condition Two'" - //something like Rosylin would be fun but it'd be serious overkill - //the syntax seems to be very clear and there are only a handful of valid constructs - //so this very basic parsing will provide some support for now - - Queue<string> criteriaQueue = new Queue<string>(searchCriteria.Split(' ')); - - Func<BaseItem, bool> result = null; - var currentMainOperatorIsAnd = false; - - //loop through in order and process - do not parallelise, order is important - while (criteriaQueue.Any()) - { - Func<BaseItem, bool> currentFilter = null; - - var metadataElement = criteriaQueue.Dequeue(); - var criteriaOperator = criteriaQueue.Dequeue(); - var value = criteriaQueue.Dequeue(); - if (value.StartsWith("\"") || value.StartsWith("\\\"")) - while (!value.EndsWith("\"")) - { - value += criteriaQueue.Dequeue(); - } - value = value.Trim(); - - - if (string.Equals(metadataElement, "upnp:class", StringComparison.OrdinalIgnoreCase)) - currentFilter = GetUpnpClassFilter(criteriaOperator, value); - else if (string.Equals(metadataElement, "@refID", StringComparison.OrdinalIgnoreCase)) - { - //not entirely sure what refID is for - //Platinum has ReferenceID which I assume is the same thing, but we're not using it yet - - } - else - { - //fail?? - } - - - if (currentFilter != null) - { - if (result == null) - result = currentFilter; - else - if (currentMainOperatorIsAnd) - result = (i) => result(i) && currentFilter(i); - else - result = (i) => result(i) || currentFilter(i); - } - if (criteriaQueue.Any()) - { - var op = criteriaQueue.Dequeue(); - if (string.Equals(op, "and", StringComparison.OrdinalIgnoreCase)) - currentMainOperatorIsAnd = true; - else - currentMainOperatorIsAnd = false; - } - } - return result; - } - - private Func<BaseItem, bool> GetUpnpClassFilter(string criteriaOperator, string value) - { - //"upnp:class derivedfrom \"object.item.videoItem\" " - //"(upnp:class = \"object.container.album.musicAlbum\")" - - //only two options are valid for criteria - // =, derivedfrom - - //there are only a few values we care about - //object.item.videoItem - //object.item.audioItem - //object.container.storageFolder - - if (string.Equals(criteriaOperator, "=", StringComparison.OrdinalIgnoreCase)) - { - if (value.Contains("object.item.videoItem")) - return (i) => (i is Video); - else if (value.Contains("object.item.audioItem")) - return (i) => (i is Audio); - else if (value.Contains("object.container.storageFolder")) - return (i) => (i is Folder); - else - //something has gone wrong, don't filter anything - return (i) => true; - } - else if (string.Equals(criteriaOperator, "derivedfrom", StringComparison.OrdinalIgnoreCase)) - { - if (value.Contains("object.item.videoItem")) - return (i) => (i is Video); - else if (value.Contains("object.item.audioItem")) - return (i) => (i is Audio); - else if (value.Contains("object.container.storageFolder")) - return (i) => (i is Folder); - else - //something has gone wrong, don't filter anything - return (i) => true; - } - else - { - //something has gone wrong, don't filter anything - return (i) => true; - } - } - #endregion - - public override void UpdateConfiguration(Model.Plugins.BasePluginConfiguration configuration) - { - base.UpdateConfiguration(configuration); - var config = (PluginConfiguration)configuration; - - this.CleanupUPnPServer(); - this._CurrentUser = null; - this.SetupUPnPServer(); - } - } - - internal static class MediaItemHelper - { - internal static Platinum.MediaResource GetMediaResource(Video item) - { - var result = GetMediaResource((BaseItem)item); - - if (item.DefaultVideoStream != null) - { - //Bitrate is Bytes per second - if (item.DefaultVideoStream.BitRate.HasValue) - result.Bitrate = (uint)item.DefaultVideoStream.BitRate; - - //not sure if we know Colour Depth - //resource.ColorDepth - if (item.DefaultVideoStream.Channels.HasValue) - result.NbAudioChannels = (uint)item.DefaultVideoStream.Channels.Value; - - //resource.Protection - //resource.ProtoInfo - - //we must know resolution, I'm just not sure how to get it - //resource.Resolution - - //I'm not sure what this actually means, is it Sample Rate - if (item.DefaultVideoStream.SampleRate.HasValue) - result.SampleFrequency = (uint)item.DefaultVideoStream.SampleRate.Value; - //file size? - //resource.Size - - ////to do subtitles for clients that can deal with external subtitles (like srt) - ////we will have to do something like this - //IEnumerable<String> ips = GetUPnPIPAddresses(context); - //foreach (var st in videoChild.MediaStreams) - //{ - // if (st.Type == MediaStreamType.Subtitle) - // { - // Platinum.MediaResource subtitleResource = new Platinum.MediaResource(); - // subtitleResource.ProtoInfo = Platinum.ProtocolInfo.GetProtocolInfo(st.Path, with_dlna_extension: false); - // foreach (String ip in ips) - // { - // //we'll need to figure out which of these options works for whick players - // //either serve them ourselves - // resource.URI = new Uri("http://" + ip + ":" + context.LocalAddress.port + "/" + child.Id.ToString("D")).ToString(); - // //or get the web api to serve them directly - // resource.URI = new Uri(Kernel.HttpServerUrlPrefix.Replace("+", ip) + "/api/video?id=" + child.Id.ToString() + "&type=Subtitle").ToString(); - // result.AddResource(resource); - // } - // } - //} - } - return result; - } - internal static Platinum.MediaItem GetMediaItem(Video item) - { - var result = GetMediaItem((BaseItem)item); - result.Title = GetTitle(item); - return result; - } - - internal static Platinum.MediaResource GetMediaResource(Episode item) - { - //there's nothing specific about an episode that requires extra Resources - return GetMediaResource((Video)item); - } - internal static Platinum.MediaItem GetMediaItem(Episode item) - { - var result = GetMediaItem((Video)item); - - if (item.IndexNumber.HasValue) - result.Recorded.EpisodeNumber = (uint)item.IndexNumber.Value; - if (item.Series != null && item.Series.Name != null) - result.Recorded.SeriesTitle = item.Series.Name; - result.Recorded.ProgramTitle = item.Name == null ? string.Empty : item.Name; - - return result; - } - - internal static Platinum.MediaResource GetMediaResource(Audio item) - { - //there's nothing specific about an audio item that requires extra Resources - return GetMediaResource((BaseItem)item); - } - internal static Platinum.MediaItem GetMediaItem(Audio item) - { - var result = GetMediaItem((BaseItem)item); - result.Title = GetTitle(item); - result.People.AddArtist(new Platinum.PersonRole(item.Artist)); - result.People.Contributor = item.AlbumArtist; - result.Affiliation.Album = item.Album; - return result; - } - - internal static Platinum.MediaResource GetMediaResource(BaseItem item) - { - var result = new Platinum.MediaResource(); - //duration is in seconds - if (item.RunTimeTicks.HasValue) - result.Duration = (uint)TimeSpan.FromTicks(item.RunTimeTicks.Value).TotalSeconds; - - return result; - } - internal static Platinum.MediaItem GetMediaItem(BaseItem item) - { - var result = new Platinum.MediaItem(); - - result.ObjectID = item.Id.ToString(); - - //if (child.Parent != null) - // result.ParentID = child.Parent.Id.ToString(); - result.Class = item.GetPlatinumClassObject(); - - result.Description.Date = item.PremiereDate.HasValue ? item.PremiereDate.Value.ToString() : string.Empty; - result.Description.Language = item.Language == null ? string.Empty : item.Language; - result.Description.DescriptionText = "this is DescriptionText"; - result.Description.LongDescriptionText = item.Overview == null ? string.Empty : item.Overview; - result.Description.Rating = item.CommunityRating.ToString(); - - if (item.Genres != null) - { - foreach (var genre in item.Genres) - { - result.Affiliation.AddGenre(genre); - } - } - if (item.People != null) - { - foreach (var person in item.People) - { - if (string.Equals(person.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase)) - result.People.AddActor(new Platinum.PersonRole(person.Name, person.Role == null ? string.Empty : person.Role)); - else if (string.Equals(person.Type, PersonType.MusicArtist, StringComparison.OrdinalIgnoreCase)) - { - result.People.AddArtist(new Platinum.PersonRole(person.Name, "MusicArtist")); - result.People.AddArtist(new Platinum.PersonRole(person.Name, "Performer")); - } - else if (string.Equals(person.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase)) - result.People.AddAuthors(new Platinum.PersonRole(person.Name, "Composer")); - else if (string.Equals(person.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase)) - result.People.AddAuthors(new Platinum.PersonRole(person.Name, "Writer")); - else if (string.Equals(person.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase)) - { - result.People.AddAuthors(new Platinum.PersonRole(person.Name, "Director")); - result.People.Director = result.People.Director + " " + person.Name; - } - else - result.People.AddArtist(new Platinum.PersonRole(person.Name, person.Type == null ? string.Empty : person.Type)); - } - } - return result; - } - - internal static void AddAlbumArtInfoToMediaItem(Platinum.MediaItem item, BaseItem child, string httpServerUrlPrefix, IEnumerable<String> ips) - { - foreach (var ip in ips) - { - AddAlbumArtInfoToMediaItem(item, child, httpServerUrlPrefix, ip); - } - } - private static void AddAlbumArtInfoToMediaItem(Platinum.MediaItem item, BaseItem child, string httpServerUrlPrefix, string ip) - { - //making the artwork a direct hit to the MediaBrowser server instead of via the DLNA plugin works for WMP - item.Extra.AddAlbumArtInfo(new Platinum.AlbumArtInfo(httpServerUrlPrefix.Replace("+", ip) + "/api/image?id=" + child.Id.ToString() + "&type=primary")); - } - - - /// <summary> - /// Gets the title. - /// </summary> - /// <param name="video">The video.</param> - /// <returns>System.String.</returns> - private static string GetTitle(Video video) - { - //we have to be extremely careful with all string handling - //if we set a null reference to a Platinum string it will not marshall to native correctly and things got very bad very quickly - var title = video.Name == null ? string.Empty : video.Name; - - var episode = video as Episode; - - if (episode != null) - { - if (episode.Season != null) - { - title = string.Format("{0}-{1}", episode.Season.Name, title); - } - if (episode.Series != null) - { - title = string.Format("{0}-{1}", episode.Series.Name, title); - } - } - - return title; - } - - /// <summary> - /// Gets the title. - /// </summary> - /// <param name="audio">The audio.</param> - /// <returns>System.String.</returns> - private static string GetTitle(Audio audio) - { - return audio.Name == null ? string.Empty : audio.Name; - } - - } - - internal static class Extensions - { - [Flags()] - internal enum FilterType - { - Folder = 1, - Music = 2, - Video = 4 - } - internal static IEnumerable<BaseItem> Filter(this IEnumerable<BaseItem> en, FilterType filter) - { - return en.Where(i => ( - (((filter & FilterType.Folder) == FilterType.Folder) && (i is Folder)) || - (((filter & FilterType.Music) == FilterType.Music) && (i is Audio)) || - (((filter & FilterType.Video) == FilterType.Video) && (i is Video))) - ); - } - - internal static IEnumerable<BaseItem> Page(this IEnumerable<BaseItem> en, int starting_index, int requested_count) - { - return en.Skip(starting_index).Take(requested_count); - } - - - internal static Platinum.ObjectClass GetPlatinumClassObject(this BaseItem item) - { - if (item is Video) - return new Platinum.ObjectClass("object.item.videoItem", ""); - else if (item is Audio) - return new Platinum.ObjectClass("object.item.audioItem.musicTrack", ""); - else if (item is Folder) - return new Platinum.ObjectClass("object.container.storageFolder", ""); - else - return null; - } - internal static Platinum.ObjectClass GetPlatinumClassObject(this Folder item) - { - return new Platinum.ObjectClass("object.container.storageFolder", ""); - } - internal static Platinum.ObjectClass GetPlatinumClassObject(this Audio item) - { - return new Platinum.ObjectClass("object.item.audioItem.musicTrack", ""); - } - internal static Platinum.ObjectClass GetPlatinumClassObject(this Video item) - { - return new Platinum.ObjectClass("object.item.videoItem", ""); - } - } - internal static class LoggingExtensions - { - //provide some json-esque string that can be used for Verbose logging purposed - internal static string ToLogString(this Platinum.Action item) - { - return string.Format(" {{ Name:\"{0}\", Description:\"{1}\", Arguments:{2} }} ", - item.Name, item.Description.ToLogString(), item.Arguments.ToLogString()); - - } - internal static string ToLogString(this IEnumerable<Platinum.ActionArgumentDescription> items) - { - var result = "["; - foreach (var arg in items) - { - result += (" " + arg.ToLogString()); - } - result += " ]"; - return result; - } - internal static string ToLogString(this Platinum.ActionArgumentDescription item) - { - return string.Format(" {{ Name:\"{0}\", Direction:{1}, HasReturnValue:{2}, RelatedStateVariable:{3} }} ", - item.Name, item.Direction, item.HasReturnValue, item.RelatedStateVariable.ToLogString()); - - } - internal static string ToLogString(this Platinum.StateVariable item) - { - return string.Format(" {{ Name:\"{0}\", DataType:{1}, DataTypeString:\"{2}\", Value:{3}, ValueString:\"{4}\" }} ", - item.Name, item.DataType, item.DataTypeString, item.Value, item.ValueString); - } - internal static string ToLogString(this Platinum.ActionDescription item) - { - return string.Format(" {{ Name:\"{0}\", Arguments:{1} }} ", - item.Name, item.Arguments.ToLogString()); - } - internal static string ToLogString(this Platinum.HttpRequestContext item) - { - return string.Format(" {{ LocalAddress:{0}, RemoteAddress:{1}, Request:\"{2}\", Signature:{3} }}", - item.LocalAddress.ToLogString(), item.RemoteAddress.ToLogString(), item.Request.URI.ToString(), item.Signature); - } - internal static string ToLogString(this Platinum.HttpRequestContext.SocketAddress item) - { - return string.Format("{{ IP:{0}, Port:{1} }}", - item.ip, item.port); - } - } -} |
