Browse Source

Do better to make sure hls files are cleaned up

Luke Pulverenti 11 năm trước cách đây
mục cha
commit
1a323767be

+ 5 - 31
MediaBrowser.Api/ApiEntryPoint.cs

@@ -71,8 +71,7 @@ namespace MediaBrowser.Api
         /// </summary>
         private void DeleteEncodedMediaCache()
         {
-            foreach (var file in Directory.EnumerateFiles(_appPaths.TranscodingTempPath)
-                .Where(i => EntityResolutionHelper.VideoFileExtensions.Contains(Path.GetExtension(i)))
+            foreach (var file in Directory.EnumerateFiles(_appPaths.TranscodingTempPath, "*", SearchOption.AllDirectories)
                 .ToList())
             {
                 File.Delete(file);
@@ -116,11 +115,10 @@ namespace MediaBrowser.Api
         /// <param name="path">The path.</param>
         /// <param name="type">The type.</param>
         /// <param name="process">The process.</param>
-        /// <param name="isVideo">if set to <c>true</c> [is video].</param>
         /// <param name="startTimeTicks">The start time ticks.</param>
         /// <param name="sourcePath">The source path.</param>
         /// <param name="deviceId">The device id.</param>
-        public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, bool isVideo, long? startTimeTicks, string sourcePath, string deviceId)
+        public void OnTranscodeBeginning(string path, TranscodingJobType type, Process process, long? startTimeTicks, string sourcePath, string deviceId)
         {
             lock (_activeTranscodingJobs)
             {
@@ -130,7 +128,6 @@ namespace MediaBrowser.Api
                     Path = path,
                     Process = process,
                     ActiveRequestCount = 1,
-                    IsVideo = isVideo,
                     StartTimeTicks = startTimeTicks,
                     SourcePath = sourcePath,
                     DeviceId = deviceId
@@ -261,7 +258,7 @@ namespace MediaBrowser.Api
             {
                 // This is really only needed for HLS. 
                 // Progressive streams can stop on their own reliably
-                jobs.AddRange(_activeTranscodingJobs.Where(i => isVideo == i.IsVideo && string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase)));
+                jobs.AddRange(_activeTranscodingJobs.Where(i => string.Equals(deviceId, i.DeviceId, StringComparison.OrdinalIgnoreCase)));
             }
 
             foreach (var job in jobs)
@@ -325,37 +322,15 @@ namespace MediaBrowser.Api
                 }
             }
 
-            // Determine if it exited successfully
-            var hasExitedSuccessfully = false;
-
-            try
-            {
-                hasExitedSuccessfully = process.ExitCode == 0;
-            }
-            catch (InvalidOperationException)
-            {
-
-            }
-            catch (NotSupportedException)
-            {
-
-            }
-
             // Dispose the process
             process.Dispose();
 
-            // If it didn't complete successfully cleanup the partial files
-            // Also don't cache output from resume points
-            // Also don't cache video
-            if (!hasExitedSuccessfully || job.StartTimeTicks.HasValue || job.IsVideo)
-            {
-                DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
-            }
+            DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
         }
 
         private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
         {
-            if (retryCount >= 5)
+            if (retryCount >= 10)
             {
                 return;
             }
@@ -455,7 +430,6 @@ namespace MediaBrowser.Api
         /// <value>The kill timer.</value>
         public Timer KillTimer { get; set; }
 
-        public bool IsVideo { get; set; }
         public long? StartTimeTicks { get; set; }
         public string SourcePath { get; set; }
         public string DeviceId { get; set; }

+ 10 - 3
MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs

@@ -24,6 +24,8 @@ namespace MediaBrowser.Api.DefaultTheme
 
         [ApiMember(Name = "RecentlyPlayedGamesLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int RecentlyPlayedGamesLimit { get; set; }
+
+        public string ParentId { get; set; }
     }
 
     [Route("/MBT/DefaultTheme/TV", "GET")]
@@ -49,6 +51,8 @@ namespace MediaBrowser.Api.DefaultTheme
 
         [ApiMember(Name = "LatestEpisodeLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int LatestEpisodeLimit { get; set; }
+
+        public string ParentId { get; set; }
     }
 
     [Route("/MBT/DefaultTheme/Movies", "GET")]
@@ -71,6 +75,8 @@ namespace MediaBrowser.Api.DefaultTheme
 
         [ApiMember(Name = "LatestTrailersLimit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
         public int LatestTrailersLimit { get; set; }
+
+        public string ParentId { get; set; }
     }
 
     [Route("/MBT/DefaultTheme/Favorites", "GET")]
@@ -224,7 +230,7 @@ namespace MediaBrowser.Api.DefaultTheme
         {
             var user = _userManager.GetUserById(request.UserId);
 
-            var items = user.RootFolder.GetRecursiveChildren(user, i => i is Game || i is GameSystem)
+            var items = GetAllLibraryItems(user.Id, _userManager, _libraryManager, request.ParentId).Where(i => i is Game || i is GameSystem)
                 .ToList();
 
             var gamesWithImages = items.OfType<Game>().Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath)).ToList();
@@ -280,7 +286,7 @@ namespace MediaBrowser.Api.DefaultTheme
 
             var user = _userManager.GetUserById(request.UserId);
 
-            var series = user.RootFolder.GetRecursiveChildren(user)
+            var series = GetAllLibraryItems(user.Id, _userManager, _libraryManager, request.ParentId)
                 .OfType<Series>()
                 .ToList();
 
@@ -403,7 +409,8 @@ namespace MediaBrowser.Api.DefaultTheme
         {
             var user = _userManager.GetUserById(request.UserId);
 
-            var items = user.RootFolder.GetRecursiveChildren(user, i => i is Movie || i is Trailer || i is BoxSet)
+            var items = GetAllLibraryItems(user.Id, _userManager, _libraryManager, request.ParentId)
+                .Where(i => i is Movie || i is Trailer || i is BoxSet)
                 .ToList();
 
             var view = new MoviesView();

+ 1 - 1
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -903,7 +903,7 @@ namespace MediaBrowser.Api.Playback
                 EnableRaisingEvents = true
             };
 
-            ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.IsInputVideo, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId);
+            ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath, TranscodingJobType, process, state.Request.StartTimeTicks, state.MediaPath, state.Request.DeviceId);
 
             var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
             Logger.Info(commandLineLogMessage);

+ 1 - 3
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -273,9 +273,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";
 
-            var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal &&
-                                 (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
-                                  state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1);
+            var hasGraphicalSubs = state.SubtitleStream != null && state.SubtitleStream.IsGraphicalSubtitleStream;
 
             var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
 

+ 1 - 3
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -168,9 +168,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+1))" : 
                 " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";
 
-            var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal &&
-                                 (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
-                                  state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1);
+            var hasGraphicalSubs = state.SubtitleStream != null && state.SubtitleStream.IsGraphicalSubtitleStream;
 
             var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
 

+ 1 - 3
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -146,9 +146,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             args += keyFrameArg;
 
-            var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsExternal &&
-                                   (state.SubtitleStream.Codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
-                                    state.SubtitleStream.Codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1);
+            var hasGraphicalSubs = state.SubtitleStream != null && state.SubtitleStream.IsGraphicalSubtitleStream;
 
             var request = state.VideoRequest;
 

+ 1 - 7
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -305,13 +305,7 @@ namespace MediaBrowser.Controller.Entities.TV
 
                 if (!ParentIndexNumber.HasValue && !string.IsNullOrEmpty(Path))
                 {
-                    ParentIndexNumber = TVUtils.GetSeasonNumberFromPath(Path);
-
-                    // If a change was made record it
-                    if (ParentIndexNumber.HasValue)
-                    {
-                        hasChanges = true;
-                    }
+                    ParentIndexNumber = TVUtils.GetSeasonNumberFromEpisodeFile(Path);
                 }
 
                 // If a change was made record it

+ 18 - 2
MediaBrowser.Model/Configuration/MetadataOptions.cs

@@ -52,14 +52,30 @@ namespace MediaBrowser.Model.Configuration
 
         public int GetLimit(ImageType type)
         {
-            ImageOption option = ImageOptions.FirstOrDefault(i => i.Type == type);
+            ImageOption option = null;
+            foreach (ImageOption i in ImageOptions)
+            {
+                if (i.Type == type)
+                {
+                    option = i;
+                    break;
+                }
+            }
 
             return option == null ? 1 : option.Limit;
         }
 
         public int GetMinWidth(ImageType type)
         {
-            ImageOption option = ImageOptions.FirstOrDefault(i => i.Type == type);
+            ImageOption option = null;
+            foreach (ImageOption i in ImageOptions)
+            {
+                if (i.Type == type)
+                {
+                    option = i;
+                    break;
+                }
+            }
 
             return option == null ? 0 : option.MinWidth;
         }

+ 5 - 1
MediaBrowser.Model/Configuration/NotificationOptions.cs

@@ -70,7 +70,11 @@ namespace MediaBrowser.Model.Configuration
 
         public NotificationOption GetOptions(string type)
         {
-            return Options.FirstOrDefault(i => string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase));
+            foreach (NotificationOption i in Options)
+            {
+                if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase)) return i;
+            }
+            return null;
         }
 
         public bool IsEnabled(string type)

+ 1 - 0
MediaBrowser.Model/Configuration/NotificationType.cs

@@ -12,6 +12,7 @@ namespace MediaBrowser.Model.Configuration
         PluginUpdateInstalled,
         PluginUninstalled,
         NewLibraryContent,
+        NewLibraryContentMultiple,
         ServerRestartRequired,
         TaskFailed,
         VideoPlayback

+ 18 - 13
MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Model.MediaInfo;
 using System.Collections.Generic;
-using System.Linq;
 
 namespace MediaBrowser.Model.Dlna
 {
@@ -158,11 +157,21 @@ namespace MediaBrowser.Model.Dlna
 
             if (string.IsNullOrEmpty(orgPn))
             {
-                orgPn = GetVideoOrgPnValue(container, videoCodec, audioCodec, width, height, timestamp)
-                    .FirstOrDefault();
+                foreach (string s in GetVideoOrgPnValue(container, videoCodec, audioCodec, width, height, timestamp))
+                {
+                    orgPn = s;
+                    break;
+                }
+            }
 
+            if (string.IsNullOrEmpty(orgPn))
+            {
                 // TODO: Support multiple values and return multiple headers?
-                orgPn = (orgPn ?? string.Empty).Split(',').FirstOrDefault();
+                foreach (string s in (orgPn ?? string.Empty).Split(','))
+                {
+                    orgPn = s;
+                    break;
+                }
             }
 
             string contentFeatures = string.IsNullOrEmpty(orgPn) ? string.Empty : "DLNA.ORG_PN=" + orgPn;
@@ -191,16 +200,12 @@ namespace MediaBrowser.Model.Dlna
             return format.HasValue ? format.Value.ToString() : null;
         }
 
-        private IEnumerable<string> GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp)
+        private List<string> GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp)
         {
-            return new MediaFormatProfileResolver()
-                .ResolveVideoFormat(container,
-                    videoCodec,
-                    audioCodec,
-                    width,
-                    height,
-                    timestamp)
-                    .Select(i => i.ToString());
+            List<string> list = new List<string>();
+            foreach (MediaFormatProfile i in new MediaFormatProfileResolver().ResolveVideoFormat(container, videoCodec, audioCodec, width, height, timestamp))
+                list.Add(i.ToString());
+            return list;
         }
     }
 }

+ 4 - 3
MediaBrowser.Model/Dlna/Filter.cs

@@ -19,9 +19,10 @@ namespace MediaBrowser.Model.Dlna
         {
             _all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
 
-            _fields = (filter ?? string.Empty)
-                .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
-                .ToList();
+            List<string> list = new List<string>();
+            foreach (string s in (filter ?? string.Empty).Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries))
+                list.Add(s);
+            _fields = list;
         }
 
         public bool Contains(string field)

+ 0 - 1
MediaBrowser.Model/Dlna/TranscodingProfile.cs

@@ -1,5 +1,4 @@
 using System.Collections.Generic;
-using System.Linq;
 using System.Xml.Serialization;
 
 namespace MediaBrowser.Model.Dlna

+ 17 - 1
MediaBrowser.Model/Entities/MediaStream.cs

@@ -1,4 +1,6 @@
-using System.Diagnostics;
+using System;
+using System.Diagnostics;
+using System.Runtime.Serialization;
 
 namespace MediaBrowser.Model.Entities
 {
@@ -128,6 +130,20 @@ namespace MediaBrowser.Model.Entities
         /// <value><c>true</c> if this instance is external; otherwise, <c>false</c>.</value>
         public bool IsExternal { get; set; }
 
+        [IgnoreDataMember]
+        public bool IsGraphicalSubtitleStream
+        {
+            get
+            {
+                if (IsExternal) return false;
+
+                var codec = Codec ?? string.Empty;
+
+                return codec.IndexOf("pgs", StringComparison.OrdinalIgnoreCase) != -1 ||
+                       codec.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1;
+            }
+        }
+
         /// <summary>
         /// Gets or sets the filename.
         /// </summary>

+ 6 - 5
MediaBrowser.Model/Web/QueryStringDictionary.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 
 namespace MediaBrowser.Model.Web
@@ -24,7 +25,7 @@ namespace MediaBrowser.Model.Web
         /// <param name="value">The value.</param>
         public void Add(string name, int value)
         {
-            Add(name, value.ToString());
+            Add(name, value.ToString(CultureInfo.InvariantCulture));
         }
 
         /// <summary>
@@ -34,7 +35,7 @@ namespace MediaBrowser.Model.Web
         /// <param name="value">The value.</param>
         public void Add(string name, long value)
         {
-            Add(name, value.ToString());
+            Add(name, value.ToString(CultureInfo.InvariantCulture));
         }
 
         /// <summary>
@@ -44,7 +45,7 @@ namespace MediaBrowser.Model.Web
         /// <param name="value">The value.</param>
         public void Add(string name, double value)
         {
-            Add(name, value.ToString());
+            Add(name, value.ToString(CultureInfo.InvariantCulture));
         }
 
         /// <summary>
@@ -135,7 +136,7 @@ namespace MediaBrowser.Model.Web
                 throw new ArgumentNullException("value");
             }
 
-            Add(name, string.Join(",", value.Select(v => v.ToString()).ToArray()));
+            Add(name, string.Join(",", value.Select(v => v.ToString(CultureInfo.InvariantCulture)).ToArray()));
         }
 
         /// <summary>
@@ -188,7 +189,7 @@ namespace MediaBrowser.Model.Web
         /// <param name="name">The name.</param>
         /// <param name="value">The value.</param>
         /// <param name="delimiter">The delimiter.</param>
-        /// <exception cref="System.ArgumentNullException">value</exception>
+        /// <exception cref="ArgumentNullException">value</exception>
         public void Add(string name, IEnumerable<string> value, string delimiter)
         {
             if (value == null)

+ 14 - 7
MediaBrowser.Server.Implementations/EntryPoints/Notifications/Notifications.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Events;
+using System.Globalization;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Common.Updates;
@@ -247,10 +248,10 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
                 DisposeLibraryUpdateTimer();
             }
 
-            var item = items.FirstOrDefault();
-
-            if (item != null)
+            if (items.Count == 1)
             {
+                var item = items.First();
+
                 var notification = new NotificationRequest
                 {
                     NotificationType = NotificationType.NewLibraryContent.ToString()
@@ -258,10 +259,16 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
 
                 notification.Variables["Name"] = item.Name;
 
-                if (items.Count > 1)
+                await SendNotification(notification).ConfigureAwait(false);
+            }
+            else
+            {
+                var notification = new NotificationRequest
                 {
-                    notification.Name = items.Count + " new library items.";
-                }
+                    NotificationType = NotificationType.NewLibraryContentMultiple.ToString()
+                };
+
+                notification.Variables["ItemCount"] = items.Count.ToString(CultureInfo.InvariantCulture);
 
                 await SendNotification(notification).ConfigureAwait(false);
             }

+ 6 - 1
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -568,6 +568,7 @@
 	"NotificationOptionTaskFailed": "Scheduled task failure",
 	"NotificationOptionInstallationFailed": "Installation failure",
 	"NotificationOptionNewLibraryContent": "New content added",
+	"NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
 	"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
 	"NotificationOptionServerRestartRequired": "Server restart required",
 	"LabelNotificationEnabled": "Enable this notification",
@@ -716,5 +717,9 @@
 	"LabelDownloadLanguages": "Download languages:",
 	"ButtonRegister": "Register",
 	"LabelSkipIfAudioTrackPresent": "Skip if the default audio track matches the download language",
-	"LabelSkipIfAudioTrackPresentHelp": "Uncheck this to ensure all videos have subtitles, regardless of audio language."
+	"LabelSkipIfAudioTrackPresentHelp": "Uncheck this to ensure all videos have subtitles, regardless of audio language.",
+	"HeaderSendMessage": "Send Message",
+	"ButtonSend": "Send",
+	"LabelMessageText": "Message text:",
+	"LabelMessageTitle": "Message title:"
 }

+ 7 - 0
MediaBrowser.Server.Implementations/Notifications/CoreNotificationTypes.cs

@@ -90,6 +90,13 @@ namespace MediaBrowser.Server.Implementations.Notifications
                      Variables = new List<string>{"Name"}
                 },
 
+                new NotificationTypeInfo
+                {
+                     Type = NotificationType.NewLibraryContentMultiple.ToString(),
+                     DefaultTitle = "{ItemCount} new items have been added to your media library.",
+                     Variables = new List<string>{"ItemCount"}
+                },
+
                 new NotificationTypeInfo
                 {
                      Type = NotificationType.AudioPlayback.ToString(),

+ 181 - 0
OpenSubtitlesHandler/OpenSubtitles.cs

@@ -440,6 +440,187 @@ namespace OpenSubtitlesHandler
             }
             return new MethodResponseError("Fail", "Search Subtitles call failed !");
         }
+
+        public static async Task<IMethodResponse> SearchSubtitlesAsync(SubtitleSearchParameters[] parameters, CancellationToken cancellationToken)
+        {
+            if (TOKEN == "")
+            {
+                OSHConsole.WriteLine("Can't do this call, 'token' value not set. Please use Log In method first.", DebugCode.Error);
+                return new MethodResponseError("Fail", "Can't do this call, 'token' value not set. Please use Log In method first.");
+            }
+            if (parameters == null)
+            {
+                OSHConsole.UpdateLine("No subtitle search parameter passed !!", DebugCode.Error);
+                return new MethodResponseError("Fail", "No subtitle search parameter passed"); ;
+            }
+            if (parameters.Length == 0)
+            {
+                OSHConsole.UpdateLine("No subtitle search parameter passed !!", DebugCode.Error);
+                return new MethodResponseError("Fail", "No subtitle search parameter passed"); ;
+            }
+            // Method call ..
+            List<IXmlRpcValue> parms = new List<IXmlRpcValue>();
+            // Add token param
+            parms.Add(new XmlRpcValueBasic(TOKEN, XmlRpcBasicValueType.String));
+            // Add subtitle search parameters. Each one will be like 'array' of structs.
+            XmlRpcValueArray array = new XmlRpcValueArray();
+            foreach (SubtitleSearchParameters param in parameters)
+            {
+                XmlRpcValueStruct strct = new XmlRpcValueStruct(new List<XmlRpcStructMember>());
+                // sublanguageid member
+                XmlRpcStructMember member = new XmlRpcStructMember("sublanguageid",
+                    new XmlRpcValueBasic(param.SubLangaugeID, XmlRpcBasicValueType.String));
+                strct.Members.Add(member);
+                // moviehash member
+                if (param.MovieHash.Length > 0 && param.MovieByteSize > 0)
+                {
+                    member = new XmlRpcStructMember("moviehash",
+                        new XmlRpcValueBasic(param.MovieHash, XmlRpcBasicValueType.String));
+                    strct.Members.Add(member);
+                    // moviehash member
+                    member = new XmlRpcStructMember("moviebytesize",
+                        new XmlRpcValueBasic(param.MovieByteSize, XmlRpcBasicValueType.Int));
+                    strct.Members.Add(member);
+                }
+                if (param.Query.Length > 0)
+                {
+                    member = new XmlRpcStructMember("query",
+                        new XmlRpcValueBasic(param.Query, XmlRpcBasicValueType.String));
+                    strct.Members.Add(member);
+                }
+
+                if (param.Episode.Length > 0 && param.Season.Length > 0)
+                {
+                    member = new XmlRpcStructMember("season",
+                        new XmlRpcValueBasic(param.Season, XmlRpcBasicValueType.String));
+                    strct.Members.Add(member);
+                    member = new XmlRpcStructMember("episode",
+                      new XmlRpcValueBasic(param.Episode, XmlRpcBasicValueType.String));
+                    strct.Members.Add(member);
+                }
+
+                // imdbid member
+                if (param.IMDbID.Length > 0)
+                {
+                    member = new XmlRpcStructMember("imdbid",
+                        new XmlRpcValueBasic(param.IMDbID, XmlRpcBasicValueType.String));
+                    strct.Members.Add(member);
+                }
+                // Add the struct to the array
+                array.Values.Add(strct);
+            }
+            // Add the array to the parameters
+            parms.Add(array);
+            // Call !
+            XmlRpcMethodCall call = new XmlRpcMethodCall("SearchSubtitles", parms);
+            OSHConsole.WriteLine("Sending SearchSubtitles request to the server ...", DebugCode.Good);
+            // Send the request to the server
+            string response = Utilities.GetStreamString(await Utilities.SendRequestAsync(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT, cancellationToken).ConfigureAwait(false));
+
+            if (!response.Contains("ERROR:"))
+            {
+                // No error occur, get and decode the response. 
+                XmlRpcMethodCall[] calls = XmlRpcGenerator.DecodeMethodResponse(response);
+                if (calls.Length > 0)
+                {
+                    if (calls[0].Parameters.Count > 0)
+                    {
+                        // We expect Struct of 3 members:
+                        //* the first is status
+                        //* the second is [array of structs, each one includes subtitle file].
+                        //* the third is [double basic value] represent seconds token by server.
+                        XmlRpcValueStruct mainStruct = (XmlRpcValueStruct)calls[0].Parameters[0];
+                        // Create the response, we'll need it later
+                        MethodResponseSubtitleSearch R = new MethodResponseSubtitleSearch();
+                        // To make sure response is not currepted by server, do it in loop
+                        foreach (XmlRpcStructMember MEMBER in mainStruct.Members)
+                        {
+                            if (MEMBER.Name == "status")
+                            {
+                                R.Status = (string)MEMBER.Data.Data;
+                                OSHConsole.WriteLine("Status= " + R.Status);
+                            }
+                            else if (MEMBER.Name == "seconds")
+                            {
+                                R.Seconds = (double)MEMBER.Data.Data;
+                                OSHConsole.WriteLine("Seconds= " + R.Seconds);
+                            }
+                            else if (MEMBER.Name == "data")
+                            {
+                                if (MEMBER.Data is XmlRpcValueArray)
+                                {
+                                    OSHConsole.WriteLine("Search results: ");
+
+                                    XmlRpcValueArray rarray = (XmlRpcValueArray)MEMBER.Data;
+                                    foreach (IXmlRpcValue subStruct in rarray.Values)
+                                    {
+                                        if (subStruct == null) continue;
+                                        if (!(subStruct is XmlRpcValueStruct)) continue;
+
+                                        SubtitleSearchResult result = new SubtitleSearchResult();
+                                        foreach (XmlRpcStructMember submember in ((XmlRpcValueStruct)subStruct).Members)
+                                        {
+                                            // To avoid errors of arranged info or missing ones, let's do it with switch..
+                                            switch (submember.Name)
+                                            {
+                                                case "IDMovie": result.IDMovie = submember.Data.Data.ToString(); break;
+                                                case "IDMovieImdb": result.IDMovieImdb = submember.Data.Data.ToString(); break;
+                                                case "IDSubMovieFile": result.IDSubMovieFile = submember.Data.Data.ToString(); break;
+                                                case "IDSubtitle": result.IDSubtitle = submember.Data.Data.ToString(); break;
+                                                case "IDSubtitleFile": result.IDSubtitleFile = submember.Data.Data.ToString(); break;
+                                                case "ISO639": result.ISO639 = submember.Data.Data.ToString(); break;
+                                                case "LanguageName": result.LanguageName = submember.Data.Data.ToString(); break;
+                                                case "MovieByteSize": result.MovieByteSize = submember.Data.Data.ToString(); break;
+                                                case "MovieHash": result.MovieHash = submember.Data.Data.ToString(); break;
+                                                case "MovieImdbRating": result.MovieImdbRating = submember.Data.Data.ToString(); break;
+                                                case "MovieName": result.MovieName = submember.Data.Data.ToString(); break;
+                                                case "MovieNameEng": result.MovieNameEng = submember.Data.Data.ToString(); break;
+                                                case "MovieReleaseName": result.MovieReleaseName = submember.Data.Data.ToString(); break;
+                                                case "MovieTimeMS": result.MovieTimeMS = submember.Data.Data.ToString(); break;
+                                                case "MovieYear": result.MovieYear = submember.Data.Data.ToString(); break;
+                                                case "SubActualCD": result.SubActualCD = submember.Data.Data.ToString(); break;
+                                                case "SubAddDate": result.SubAddDate = submember.Data.Data.ToString(); break;
+                                                case "SubAuthorComment": result.SubAuthorComment = submember.Data.Data.ToString(); break;
+                                                case "SubBad": result.SubBad = submember.Data.Data.ToString(); break;
+                                                case "SubDownloadLink": result.SubDownloadLink = submember.Data.Data.ToString(); break;
+                                                case "SubDownloadsCnt": result.SubDownloadsCnt = submember.Data.Data.ToString(); break;
+                                                case "SeriesEpisode": result.SeriesEpisode = submember.Data.Data.ToString(); break;
+                                                case "SeriesSeason": result.SeriesSeason = submember.Data.Data.ToString(); break;
+                                                case "SubFileName": result.SubFileName = submember.Data.Data.ToString(); break;
+                                                case "SubFormat": result.SubFormat = submember.Data.Data.ToString(); break;
+                                                case "SubHash": result.SubHash = submember.Data.Data.ToString(); break;
+                                                case "SubLanguageID": result.SubLanguageID = submember.Data.Data.ToString(); break;
+                                                case "SubRating": result.SubRating = submember.Data.Data.ToString(); break;
+                                                case "SubSize": result.SubSize = submember.Data.Data.ToString(); break;
+                                                case "SubSumCD": result.SubSumCD = submember.Data.Data.ToString(); break;
+                                                case "UserID": result.UserID = submember.Data.Data.ToString(); break;
+                                                case "UserNickName": result.UserNickName = submember.Data.Data.ToString(); break;
+                                                case "ZipDownloadLink": result.ZipDownloadLink = submember.Data.Data.ToString(); break;
+                                            }
+                                        }
+                                        R.Results.Add(result);
+                                        OSHConsole.WriteLine(">" + result.ToString());
+                                    }
+                                }
+                                else// Unknown data ?
+                                {
+                                    OSHConsole.WriteLine("Data= " + MEMBER.Data.Data.ToString(), DebugCode.Warning);
+                                }
+                            }
+                        }
+                        // Return the response to user !!
+                        return R;
+                    }
+                }
+            }
+            else
+            {
+                OSHConsole.WriteLine(response, DebugCode.Error);
+                return new MethodResponseError("Fail", response);
+            }
+            return new MethodResponseError("Fail", "Search Subtitles call failed !");
+        }
+        
         /// <summary>
         /// Download subtitle file(s)
         /// </summary>