Explorar el Código

add basic open subtitle configuration

Luke Pulverenti hace 11 años
padre
commit
9e4b34a4b1

+ 5 - 5
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -131,15 +131,15 @@ namespace MediaBrowser.Api.Playback.Progressive
         {
             var args = "-vcodec " + codec;
 
-            // See if we can save come cpu cycles by avoiding encoding
-            if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
+            if (state.EnableMpegtsM2TsMode)
             {
-                return state.VideoStream != null && IsH264(state.VideoStream) ? args + " -bsf h264_mp4toannexb" : args;
+                args += " -mpegts_m2ts_mode 1";
             }
 
-            if (state.EnableMpegtsM2TsMode)
+            // See if we can save come cpu cycles by avoiding encoding
+            if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
             {
-                args += " -mpegts_m2ts_mode 1";
+                return state.VideoStream != null && IsH264(state.VideoStream) ? args + " -bsf h264_mp4toannexb" : args;
             }
 
             const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";

+ 7 - 0
MediaBrowser.Controller/Configuration/IServerConfigurationManager.cs

@@ -1,5 +1,7 @@
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Model.Configuration;
+using System;
 
 namespace MediaBrowser.Controller.Configuration
 {
@@ -8,6 +10,11 @@ namespace MediaBrowser.Controller.Configuration
     /// </summary>
     public interface IServerConfigurationManager : IConfigurationManager
     {
+        /// <summary>
+        /// Occurs when [configuration updating].
+        /// </summary>
+        event EventHandler<GenericEventArgs<ServerConfiguration>> ConfigurationUpdating;
+        
         /// <summary>
         /// Gets the application paths.
         /// </summary>

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -191,6 +191,7 @@
     <Compile Include="Providers\IMetadataProvider.cs" />
     <Compile Include="Providers\IMetadataService.cs" />
     <Compile Include="Providers\IRemoteMetadataProvider.cs" />
+    <Compile Include="Security\IEncryptionManager.cs" />
     <Compile Include="Subtitles\ISubtitleManager.cs" />
     <Compile Include="Subtitles\ISubtitleProvider.cs" />
     <Compile Include="Providers\ItemLookupInfo.cs" />

+ 20 - 0
MediaBrowser.Controller/Security/IEncryptionManager.cs

@@ -0,0 +1,20 @@
+
+namespace MediaBrowser.Controller.Security
+{
+    public interface IEncryptionManager
+    {
+        /// <summary>
+        /// Encrypts the string.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns>System.String.</returns>
+        string EncryptString(string value);
+
+        /// <summary>
+        /// Decrypts the string.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns>System.String.</returns>
+        string DecryptString(string value);
+    }
+}

+ 3 - 0
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -323,6 +323,9 @@ namespace MediaBrowser.Model.Configuration
         public bool DownloadMovieSubtitles { get; set; }
         public bool DownloadEpisodeSubtitles { get; set; }
 
+        public string OpenSubtitlesUsername { get; set; }
+        public string OpenSubtitlesPasswordHash { get; set; }
+
         public SubtitleOptions()
         {
             SubtitleDownloadLanguages = new string[] { };

+ 77 - 13
MediaBrowser.Providers/Subtitles/OpenSubtitleDownloader.cs

@@ -1,7 +1,9 @@
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Subtitles;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Providers;
@@ -16,16 +18,52 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Subtitles
 {
-    public class OpenSubtitleDownloader : ISubtitleProvider
+    public class OpenSubtitleDownloader : ISubtitleProvider, IDisposable
     {
         private readonly ILogger _logger;
         private readonly IHttpClient _httpClient;
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
-        public OpenSubtitleDownloader(ILogManager logManager, IHttpClient httpClient)
+        private readonly IServerConfigurationManager _config;
+        private readonly IEncryptionManager _encryption;
+
+        public OpenSubtitleDownloader(ILogManager logManager, IHttpClient httpClient, IServerConfigurationManager config, IEncryptionManager encryption)
         {
             _logger = logManager.GetLogger(GetType().Name);
             _httpClient = httpClient;
+            _config = config;
+            _encryption = encryption;
+
+            _config.ConfigurationUpdating += _config_ConfigurationUpdating;
+        }
+
+        private const string PasswordHashPrefix = "h:";
+        void _config_ConfigurationUpdating(object sender, GenericEventArgs<ServerConfiguration> e)
+        {
+            var options = e.Argument.SubtitleOptions;
+
+            if (options != null &&
+                !string.IsNullOrWhiteSpace(options.OpenSubtitlesPasswordHash) &&
+                !options.OpenSubtitlesPasswordHash.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase))
+            {
+                options.OpenSubtitlesPasswordHash = EncryptPassword(options.OpenSubtitlesPasswordHash);
+            }
+        }
+
+        private string EncryptPassword(string password)
+        {
+            return PasswordHashPrefix + _encryption.EncryptString(password);
+        }
+
+        private string DecryptPassword(string password)
+        {
+            if (password == null ||
+                !password.StartsWith(PasswordHashPrefix, StringComparison.OrdinalIgnoreCase))
+            {
+                return string.Empty;
+            }
+
+            return _encryption.DecryptString(password.Substring(2));
         }
 
         public string Name
@@ -35,7 +73,16 @@ namespace MediaBrowser.Providers.Subtitles
 
         public IEnumerable<SubtitleMediaType> SupportedMediaTypes
         {
-            get { return new[] { SubtitleMediaType.Episode, SubtitleMediaType.Movie }; }
+            get
+            {
+                if (string.IsNullOrWhiteSpace(_config.Configuration.SubtitleOptions.OpenSubtitlesUsername) ||
+                    string.IsNullOrWhiteSpace(_config.Configuration.SubtitleOptions.OpenSubtitlesPasswordHash))
+                {
+                    return new SubtitleMediaType[] { };
+                }
+
+                return new[] { SubtitleMediaType.Episode, SubtitleMediaType.Movie };
+            }
         }
 
         public Task<SubtitleResponse> GetSubtitles(string id, CancellationToken cancellationToken)
@@ -59,7 +106,10 @@ namespace MediaBrowser.Providers.Subtitles
 
             var downloadsList = new[] { int.Parse(ossId, _usCulture) };
 
-            var resultDownLoad = OpenSubtitles.DownloadSubtitles(downloadsList);
+            await Login(cancellationToken).ConfigureAwait(false);
+
+            var resultDownLoad = await OpenSubtitles.DownloadSubtitlesAsync(downloadsList, cancellationToken).ConfigureAwait(false);
+
             if (!(resultDownLoad is MethodResponseSubtitleDownload))
             {
                 throw new ApplicationException("Invalid response type");
@@ -77,6 +127,21 @@ namespace MediaBrowser.Providers.Subtitles
             };
         }
 
+        private async Task Login(CancellationToken cancellationToken)
+        {
+            var options = _config.Configuration.SubtitleOptions ?? new SubtitleOptions();
+
+            var user = options.OpenSubtitlesUsername ?? string.Empty;
+            var password = DecryptPassword(options.OpenSubtitlesPasswordHash);
+
+            var loginResponse = await OpenSubtitles.LogInAsync(user, password, "en", cancellationToken).ConfigureAwait(false);
+
+            if (!(loginResponse is MethodResponseLogIn))
+            {
+                throw new UnauthorizedAccessException("Authentication to OpenSubtitles failed.");
+            }
+        }
+
         public async Task<IEnumerable<RemoteSubtitleInfo>> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken)
         {
             var imdbIdText = request.GetProviderId(MetadataProviders.Imdb);
@@ -116,13 +181,7 @@ namespace MediaBrowser.Providers.Subtitles
             Utilities.HttpClient = _httpClient;
             OpenSubtitles.SetUserAgent("OS Test User Agent");
 
-            var loginResponse = await OpenSubtitles.LogInAsync("", "", "en", cancellationToken).ConfigureAwait(false);
-
-            if (!(loginResponse is MethodResponseLogIn))
-            {
-                _logger.Debug("Login error");
-                return new List<RemoteSubtitleInfo>();
-            }
+            await Login(cancellationToken).ConfigureAwait(false);
 
             var subLanguageId = request.Language;
             var hash = Utilities.ComputeHash(request.MediaPath);
@@ -178,5 +237,10 @@ namespace MediaBrowser.Providers.Subtitles
                         IsHashMatch = i.MovieHash == hasCopy
                     });
         }
+
+        public void Dispose()
+        {
+            _config.ConfigurationUpdating -= _config_ConfigurationUpdating;
+        }
     }
 }

+ 8 - 3
MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Implementations.Configuration;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
@@ -29,6 +30,8 @@ namespace MediaBrowser.Server.Implementations.Configuration
             UpdateMetadataPath();
         }
 
+        public event EventHandler<GenericEventArgs<ServerConfiguration>> ConfigurationUpdating;
+
         /// <summary>
         /// Gets the type of the configuration.
         /// </summary>
@@ -73,8 +76,8 @@ namespace MediaBrowser.Server.Implementations.Configuration
         /// </summary>
         private void UpdateItemsByNamePath()
         {
-            ((ServerApplicationPaths) ApplicationPaths).ItemsByNamePath = string.IsNullOrEmpty(Configuration.ItemsByNamePath) ? 
-                null : 
+            ((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = string.IsNullOrEmpty(Configuration.ItemsByNamePath) ?
+                null :
                 Configuration.ItemsByNamePath;
         }
 
@@ -105,13 +108,15 @@ namespace MediaBrowser.Server.Implementations.Configuration
         /// <exception cref="System.IO.DirectoryNotFoundException"></exception>
         public override void ReplaceConfiguration(BaseApplicationConfiguration newConfiguration)
         {
-            var newConfig = (ServerConfiguration) newConfiguration;
+            var newConfig = (ServerConfiguration)newConfiguration;
 
             ValidateItemByNamePath(newConfig);
             ValidateTranscodingTempPath(newConfig);
             ValidatePathSubstitutions(newConfig);
             ValidateMetadataPath(newConfig);
 
+            EventHelper.FireEventIfNotNull(ConfigurationUpdating, this, new GenericEventArgs<ServerConfiguration> { Argument = newConfig }, Logger);
+
             base.ReplaceConfiguration(newConfiguration);
         }
 

+ 0 - 2
MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json

@@ -17,8 +17,6 @@
 	"PasswordResetConfirmation": "Are you sure you wish to reset the password?",
 	"PasswordSaved": "Password saved.",
 	"PasswordMatchError": "Password and password confirmation must match.",
-	"OptionOff": "Off",
-	"OptionOn": "On",
 	"OptionRelease": "Official Release",
 	"OptionBeta": "Beta",
 	"OptionDev": "Dev (Unstable)",

+ 9 - 3
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -147,10 +147,8 @@
 	"ScheduledTasksTitle": "Scheduled Tasks",
 	"TabMyPlugins": "My Plugins",
 	"TabCatalog": "Catalog",
-	"TabUpdates": "Updates",
 	"PluginsTitle": "Plugins",
 	"HeaderAutomaticUpdates": "Automatic Updates",
-	"HeaderUpdateLevel": "Update Level",
 	"HeaderNowPlaying": "Now Playing",
 	"HeaderLatestAlbums": "Latest Albums",
 	"HeaderLatestSongs": "Latest Songs",
@@ -706,5 +704,13 @@
 	"OptionEnableM2tsModeHelp": "Enable m2ts mode when encoding to mpegts.",
 	"OptionEstimateContentLength": "Estimate content length when transcoding",
 	"OptionReportByteRangeSeekingWhenTranscoding": "Report that the server supports byte seeking when transcoding",
-	"OptionReportByteRangeSeekingWhenTranscodingHelp": "This is required for some devices that don't time seek very well."
+	"OptionReportByteRangeSeekingWhenTranscodingHelp": "This is required for some devices that don't time seek very well.",
+	"HeaderSubtitleDownloadingHelp": "Media Browser can inspect your video files for missing subtitles, and download them using a subtitle provider such as OpenSubtitles.org.",
+	"HeaderDownloadSubtitlesFor": "Download subtitles for:",
+	"LabelRequireExternalSubtitles": "Download even if the video already contains graphical subtitles",
+	"LabelRequireExternalSubtitlesHelp": "Keeping text versions of subtitles will result in more efficient delivery to mobile clients.",
+	"TabSubtitles": "Subtitles",
+	"LabelOpenSubtitlesUsername": "Open Subtitles username:",
+	"LabelOpenSubtitlesPassword": "Open Subtitles password:",
+	"LabelAudioLanguagePreferenceHelp": "If empty, the default audio track will be selected, regardless of language."
 }

+ 2 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -64,6 +64,7 @@
     <Reference Include="System.Drawing" />
     <Reference Include="Microsoft.CSharp" />
     <Reference Include="System.Data" />
+    <Reference Include="System.Security" />
     <Reference Include="System.Web" />
     <Reference Include="System.Xml" />
     <Reference Include="MoreLinq">
@@ -201,6 +202,7 @@
     <Compile Include="ScheduledTasks\ChapterImagesTask.cs" />
     <Compile Include="ScheduledTasks\RefreshIntrosTask.cs" />
     <Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" />
+    <Compile Include="Security\EncryptionManager.cs" />
     <Compile Include="ServerApplicationPaths.cs" />
     <Compile Include="ServerManager\ServerManager.cs" />
     <Compile Include="ServerManager\WebSocketConnection.cs" />

+ 36 - 0
MediaBrowser.Server.Implementations/Security/EncryptionManager.cs

@@ -0,0 +1,36 @@
+using MediaBrowser.Controller.Security;
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.Security
+{
+    public class EncryptionManager : IEncryptionManager
+    {
+        /// <summary>
+        /// Encrypts the string.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns>System.String.</returns>
+        /// <exception cref="System.ArgumentNullException">value</exception>
+        public string EncryptString(string value)
+        {
+            if (value == null) throw new ArgumentNullException("value");
+
+            return Encoding.Default.GetString(ProtectedData.Protect(Encoding.Default.GetBytes(value), null, DataProtectionScope.LocalMachine));
+        }
+
+        /// <summary>
+        /// Decrypts the string.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <returns>System.String.</returns>
+        /// <exception cref="System.ArgumentNullException">value</exception>
+        public string DecryptString(string value)
+        {
+            if (value == null) throw new ArgumentNullException("value");
+
+            return Encoding.Default.GetString(ProtectedData.Unprotect(Encoding.Default.GetBytes(value), null, DataProtectionScope.LocalMachine));
+        }
+    }
+}

+ 4 - 0
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -29,6 +29,7 @@ using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Subtitles;
@@ -61,6 +62,7 @@ using MediaBrowser.Server.Implementations.Localization;
 using MediaBrowser.Server.Implementations.MediaEncoder;
 using MediaBrowser.Server.Implementations.Notifications;
 using MediaBrowser.Server.Implementations.Persistence;
+using MediaBrowser.Server.Implementations.Security;
 using MediaBrowser.Server.Implementations.ServerManager;
 using MediaBrowser.Server.Implementations.Session;
 using MediaBrowser.Server.Implementations.Themes;
@@ -533,6 +535,8 @@ namespace MediaBrowser.ServerApplication
             NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager);
             RegisterSingleInstance(NotificationManager);
 
+            RegisterSingleInstance<IEncryptionManager>(new EncryptionManager());
+
             SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor);
             RegisterSingleInstance(SubtitleManager);
 

+ 1 - 1
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -585,6 +585,7 @@ namespace MediaBrowser.WebDashboard.Api
                                 "medialibrarypage.js",
                                 "metadataconfigurationpage.js",
                                 "metadataimagespage.js",
+                                "metadatasubtitles.js",
                                 "moviegenres.js",
                                 "moviecollections.js",
                                 "movies.js",
@@ -605,7 +606,6 @@ namespace MediaBrowser.WebDashboard.Api
                                 "playlist.js",
                                 "plugincatalogpage.js",
                                 "pluginspage.js",
-                                "pluginupdatespage.js",
                                 "remotecontrol.js",
                                 "scheduledtaskpage.js",
                                 "scheduledtaskspage.js",

+ 6 - 10
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -328,6 +328,9 @@
     <Content Include="dashboard-ui\livetvtimers.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\metadatasubtitles.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\movieslatest.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -658,6 +661,9 @@
     <Content Include="dashboard-ui\scripts\mediaplayer-video.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\metadatasubtitles.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\movieslatest.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -1781,16 +1787,6 @@
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
   </ItemGroup>
-  <ItemGroup>
-    <Content Include="dashboard-ui\pluginupdates.html">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-  </ItemGroup>
-  <ItemGroup>
-    <Content Include="dashboard-ui\scripts\pluginupdatespage.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
-  </ItemGroup>
   <ItemGroup>
     <Content Include="dashboard-ui\addplugin.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

+ 111 - 0
OpenSubtitlesHandler/OpenSubtitles.cs

@@ -551,6 +551,117 @@ namespace OpenSubtitlesHandler
             }
             return new MethodResponseError("Fail", "DownloadSubtitles call failed !");
         }
+
+        public static async Task<IMethodResponse> DownloadSubtitlesAsync(int[] subIDS, 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 (subIDS == null)
+            {
+                OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error);
+                return new MethodResponseError("Fail", "No subtitle id passed"); ;
+            }
+            if (subIDS.Length == 0)
+            {
+                OSHConsole.UpdateLine("No subtitle id passed !!", DebugCode.Error);
+                return new MethodResponseError("Fail", "No subtitle id 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 (int id in subIDS)
+            {
+                array.Values.Add(new XmlRpcValueBasic(id, XmlRpcBasicValueType.Int));
+            }
+            // Add the array to the parameters
+            parms.Add(array);
+            // Call !
+            XmlRpcMethodCall call = new XmlRpcMethodCall("DownloadSubtitles", parms);
+            OSHConsole.WriteLine("Sending DownloadSubtitles request to the server ...", DebugCode.Good);
+            // Send the request to the server
+
+            var httpResponse = await Utilities.SendRequestAsync(XmlRpcGenerator.Generate(call), XML_PRC_USERAGENT, cancellationToken).ConfigureAwait(false);
+
+            string response = Utilities.GetStreamString(httpResponse);
+            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
+                        MethodResponseSubtitleDownload R = new MethodResponseSubtitleDownload();
+
+                        // 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("Download results:");
+                                    XmlRpcValueArray rarray = (XmlRpcValueArray)MEMBER.Data;
+                                    foreach (IXmlRpcValue subStruct in rarray.Values)
+                                    {
+                                        if (subStruct == null) continue;
+                                        if (!(subStruct is XmlRpcValueStruct)) continue;
+
+                                        SubtitleDownloadResult result = new SubtitleDownloadResult();
+                                        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 "idsubtitlefile": result.IdSubtitleFile = (string)submember.Data.Data; break;
+                                                case "data": result.Data = (string)submember.Data.Data; break;
+                                            }
+                                        }
+                                        R.Results.Add(result);
+                                        OSHConsole.WriteLine("> IDSubtilteFile= " + 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", "DownloadSubtitles call failed !");
+        }
+        
         /// <summary>
         /// Returns comments for subtitles
         /// </summary>