Explorar o código

add latest translations

Luke Pulverenti %!s(int64=11) %!d(string=hai) anos
pai
achega
0200911afc
Modificáronse 37 ficheiros con 319 adicións e 101 borrados
  1. 29 16
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  2. 4 4
      MediaBrowser.Api/SessionsService.cs
  3. 4 15
      MediaBrowser.Controller/Session/SessionInfo.cs
  4. 10 3
      MediaBrowser.Dlna/PlayTo/PlayToManager.cs
  5. 3 3
      MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
  6. 11 32
      MediaBrowser.Model/Dlna/StreamBuilder.cs
  7. 6 8
      MediaBrowser.Model/Session/SessionCapabilities.cs
  8. 7 0
      MediaBrowser.Model/Session/SessionInfoDto.cs
  9. 1 0
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  10. 0 1
      MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs
  11. 21 3
      MediaBrowser.Providers/Savers/SeasonXmlSaver.cs
  12. 46 0
      MediaBrowser.Providers/TV/SeasonXmlParser.cs
  13. 1 1
      MediaBrowser.Providers/TV/SeasonXmlProvider.cs
  14. 22 5
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  15. 77 0
      MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
  16. 1 0
      MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json
  17. 1 1
      MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json
  18. 0 0
      MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json
  19. 2 1
      MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
  20. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/de.json
  21. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/en_US.json
  22. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/es.json
  23. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/es_MX.json
  24. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/fr.json
  25. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/he.json
  26. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/it.json
  27. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/nl.json
  28. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json
  29. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json
  30. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/ru.json
  31. 60 1
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  32. 0 0
      MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json
  33. 2 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  34. 0 1
      MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs
  35. 4 5
      MediaBrowser.Server.Implementations/Session/SessionManager.cs
  36. 2 1
      MediaBrowser.ServerApplication/ApplicationHost.cs
  37. 5 0
      MediaBrowser.WebDashboard/Api/DashboardService.cs

+ 29 - 16
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -328,7 +328,8 @@ namespace MediaBrowser.Api.Playback
         {
             var param = string.Empty;
 
-            var hasFixedResolution = state.VideoRequest.HasFixedResolution;
+            var isVc1 = state.VideoStream != null && 
+                string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
 
             var qualitySetting = GetQualitySetting();
 
@@ -364,24 +365,36 @@ namespace MediaBrowser.Api.Playback
             // webm
             else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
             {
-                // http://www.webmproject.org/docs/encoder-parameters/
-                param = "-speed 16 -quality good -profile:v 0 -slices 8";
+                // Values 0-3, 0 being highest quality but slower
+                var profileScore = 0;
+
+                string crf;
 
-                if (!hasFixedResolution)
+                switch (qualitySetting)
                 {
-                    switch (qualitySetting)
-                    {
-                        case EncodingQuality.HighSpeed:
-                            param += " -crf 18";
-                            break;
-                        case EncodingQuality.HighQuality:
-                            param += " -crf 10";
-                            break;
-                        case EncodingQuality.MaxQuality:
-                            param += " -crf 4";
-                            break;
-                    }
+                    case EncodingQuality.HighSpeed:
+                        crf = "18";
+                        profileScore++;
+                        break;
+                    case EncodingQuality.HighQuality:
+                        crf = "10";
+                        break;
+                    case EncodingQuality.MaxQuality:
+                        crf = "4";
+                        break;
+                    default:
+                        throw new ArgumentException("Unrecognized quality setting");
+                }
+
+                if (isVc1)
+                {
+                    profileScore++;
                 }
+
+                // http://www.webmproject.org/docs/encoder-parameters/
+                param = string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1}",
+                    profileScore.ToString(UsCulture),
+                    crf);
             }
 
             else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase))

+ 4 - 4
MediaBrowser.Api/SessionsService.cs

@@ -231,8 +231,8 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
         public string PlayableMediaTypes { get; set; }
 
-        [ApiMember(Name = "SupportsFullscreenToggle", Description = "Whether or not the session supports fullscreen toggle", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")]
-        public bool SupportsFullscreenToggle { get; set; }
+        [ApiMember(Name = "SupportedCommands", Description = "A list of supported remote control commands, comma delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string SupportedCommands { get; set; }
     }
 
     /// <summary>
@@ -425,9 +425,9 @@ namespace MediaBrowser.Api
         {
             _sessionManager.ReportCapabilities(request.Id, new SessionCapabilities
             {
-                PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries),
+                PlayableMediaTypes = request.PlayableMediaTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList(),
 
-                SupportsFullscreenToggle = request.SupportsFullscreenToggle
+                SupportedCommands = request.SupportedCommands.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList()
             });
         }
 

+ 4 - 15
MediaBrowser.Controller/Session/SessionInfo.cs

@@ -25,6 +25,7 @@ namespace MediaBrowser.Controller.Session
             };
 
             AdditionalUsers = new List<SessionUserInfo>();
+            SupportedCommands = new List<string>();
         }
 
         public List<SessionUserInfo> AdditionalUsers { get; set; }
@@ -168,23 +169,11 @@ namespace MediaBrowser.Controller.Session
         public ISessionController SessionController { get; set; }
 
         /// <summary>
-        /// Gets or sets a value indicating whether [supports fullscreen toggle].
+        /// Gets or sets the supported commands.
         /// </summary>
-        /// <value><c>true</c> if [supports fullscreen toggle]; otherwise, <c>false</c>.</value>
-        public bool SupportsFullscreenToggle { get; set; }
+        /// <value>The supported commands.</value>
+        public List<string> SupportedCommands { get; set; }
 
-        /// <summary>
-        /// Gets or sets a value indicating whether [supports osd toggle].
-        /// </summary>
-        /// <value><c>true</c> if [supports osd toggle]; otherwise, <c>false</c>.</value>
-        public bool SupportsOsdToggle { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether [supports navigation commands].
-        /// </summary>
-        /// <value><c>true</c> if [supports navigation commands]; otherwise, <c>false</c>.</value>
-        public bool SupportsNavigationControl { get; set; }
-        
         /// <summary>
         /// Gets a value indicating whether this instance is active.
         /// </summary>

+ 10 - 3
MediaBrowser.Dlna/PlayTo/PlayToManager.cs

@@ -146,7 +146,7 @@ namespace MediaBrowser.Dlna.PlayTo
             }, _tokenSource.Token, TaskCreationOptions.LongRunning);
         }
 
-        private void TryCreateController(IDictionary<string,string> headers)
+        private void TryCreateController(IDictionary<string, string> headers)
         {
             string location;
 
@@ -244,9 +244,16 @@ namespace MediaBrowser.Dlna.PlayTo
 
                     _sessionManager.ReportCapabilities(sessionInfo.Id, new SessionCapabilities
                     {
-                        PlayableMediaTypes = profile.GetSupportedMediaTypes().ToArray(),
+                        PlayableMediaTypes = profile.GetSupportedMediaTypes(),
 
-                        SupportsFullscreenToggle = false
+                        SupportedCommands = new List<string>
+                        {
+                            GeneralCommandType.VolumeDown.ToString(),
+                            GeneralCommandType.VolumeUp.ToString(),
+                            GeneralCommandType.Mute.ToString(),
+                            GeneralCommandType.Unmute.ToString(),
+                            GeneralCommandType.ToggleMute.ToString()
+                        }
                     });
 
                     _logger.Info("DLNA Session created for {0} - {1}", device.Properties.Name, device.Properties.ModelName);

+ 3 - 3
MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs

@@ -209,7 +209,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             {
                 if (isWebm)
                 {
-                    return Math.Max(Environment.ProcessorCount - 1, 1);
+                    return Math.Max(Environment.ProcessorCount - 1, 2);
                 }
 
                 return 0;
@@ -224,9 +224,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 case EncodingQuality.HighSpeed:
                     return 2;
                 case EncodingQuality.HighQuality:
-                    return isWebm ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
+                    return isWebm ? Math.Max(Environment.ProcessorCount - 1, 2) : 0;
                 case EncodingQuality.MaxQuality:
-                    return isWebm ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
+                    return isWebm ? Math.Max(Environment.ProcessorCount - 1, 2) : 0;
                 default:
                     throw new Exception("Unrecognized MediaEncodingQuality value.");
             }

+ 11 - 32
MediaBrowser.Model/Dlna/StreamBuilder.cs

@@ -1,10 +1,9 @@
-using System;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
 using System.Globalization;
-using System.IO;
 using System.Linq;
-using MediaBrowser.Model.Dto;
-using System.Collections.Generic;
-using MediaBrowser.Model.Entities;
 
 namespace MediaBrowser.Model.Dlna
 {
@@ -453,19 +452,19 @@ namespace MediaBrowser.Model.Dlna
 
                 if (actualValue.HasValue)
                 {
-                    long expected;
-                    if (long.TryParse(condition.Value, NumberStyles.Any, _usCulture, out expected))
+                    double expected;
+                    if (double.TryParse(condition.Value, NumberStyles.Any, _usCulture, out expected))
                     {
                         switch (condition.Condition)
                         {
                             case ProfileConditionType.Equals:
-                                return actualValue.Value == expected;
+                                return actualValue.Value.Equals(expected);
                             case ProfileConditionType.GreaterThanEqual:
                                 return actualValue.Value >= expected;
                             case ProfileConditionType.LessThanEqual:
                                 return actualValue.Value <= expected;
                             case ProfileConditionType.NotEquals:
-                                return actualValue.Value != expected;
+                                return !actualValue.Value.Equals(expected);
                             default:
                                 throw new InvalidOperationException("Unexpected ProfileConditionType");
                         }
@@ -486,7 +485,7 @@ namespace MediaBrowser.Model.Dlna
         /// <param name="audioStream">The audio stream.</param>
         /// <returns>System.Nullable{System.Int64}.</returns>
         /// <exception cref="System.InvalidOperationException">Unexpected Property</exception>
-        private long? GetConditionValue(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream)
+        private double? GetConditionValue(ProfileCondition condition, string mediaPath, MediaStream videoStream, MediaStream audioStream)
         {
             switch (condition.Property)
             {
@@ -497,37 +496,17 @@ namespace MediaBrowser.Model.Dlna
                 case ProfileConditionValue.VideoBitrate:
                     return videoStream == null ? null : videoStream.BitRate;
                 case ProfileConditionValue.VideoFramerate:
-                    return videoStream == null ? null : (ConvertToLong(videoStream.AverageFrameRate ?? videoStream.RealFrameRate));
+                    return videoStream == null ? null : (videoStream.AverageFrameRate ?? videoStream.RealFrameRate);
                 case ProfileConditionValue.Height:
                     return videoStream == null ? null : videoStream.Height;
                 case ProfileConditionValue.Width:
                     return videoStream == null ? null : videoStream.Width;
                 case ProfileConditionValue.VideoLevel:
-                    return videoStream == null ? null : ConvertToLong(videoStream.Level);
+                    return videoStream == null ? null : videoStream.Level;
                 default:
                     throw new InvalidOperationException("Unexpected Property");
             }
         }
-
-        /// <summary>
-        /// Converts to long.
-        /// </summary>
-        /// <param name="val">The value.</param>
-        /// <returns>System.Nullable{System.Int64}.</returns>
-        private long? ConvertToLong(float? val)
-        {
-            return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null;
-        }
-
-        /// <summary>
-        /// Converts to long.
-        /// </summary>
-        /// <param name="val">The value.</param>
-        /// <returns>System.Nullable{System.Int64}.</returns>
-        private long? ConvertToLong(double? val)
-        {
-            return val.HasValue ? Convert.ToInt64(val.Value) : (long?)null;
-        }
     }
 
 }

+ 6 - 8
MediaBrowser.Model/Session/SessionCapabilities.cs

@@ -1,19 +1,17 @@
-
+using System.Collections.Generic;
+
 namespace MediaBrowser.Model.Session
 {
     public class SessionCapabilities
     {
-        public string[] PlayableMediaTypes { get; set; }
-
-        public bool SupportsFullscreenToggle { get; set; }
+        public List<string> PlayableMediaTypes { get; set; }
 
-        public bool SupportsOsdToggle { get; set; }
+        public List<string> SupportedCommands { get; set; }
 
-        public bool SupportsNavigationControl { get; set; }
-        
         public SessionCapabilities()
         {
-            PlayableMediaTypes = new string[] {};
+            PlayableMediaTypes = new List<string>();
+            SupportedCommands = new List<string>();
         }
     }
 }

+ 7 - 0
MediaBrowser.Model/Session/SessionInfoDto.cs

@@ -15,6 +15,12 @@ namespace MediaBrowser.Model.Session
         /// <value><c>true</c> if this instance can seek; otherwise, <c>false</c>.</value>
         public bool CanSeek { get; set; }
 
+        /// <summary>
+        /// Gets or sets the supported commands.
+        /// </summary>
+        /// <value>The supported commands.</value>
+        public List<string> SupportedCommands { get; set; }
+        
         /// <summary>
         /// Gets or sets the remote end point.
         /// </summary>
@@ -167,6 +173,7 @@ namespace MediaBrowser.Model.Session
 
             PlayableMediaTypes = new List<string>();
             QueueableMediaTypes = new List<string>();
+            SupportedCommands = new List<string>();
         }
     }
 

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

@@ -196,6 +196,7 @@
     <Compile Include="TV\MissingEpisodeProvider.cs" />
     <Compile Include="TV\MovieDbSeriesImageProvider.cs" />
     <Compile Include="TV\MovieDbSeriesProvider.cs" />
+    <Compile Include="TV\SeasonXmlParser.cs" />
     <Compile Include="TV\SeriesMetadataService.cs" />
     <Compile Include="TV\TvdbEpisodeImageProvider.cs" />
     <Compile Include="People\TvdbPersonImageProvider.cs" />

+ 0 - 1
MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs

@@ -2,7 +2,6 @@
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;

+ 21 - 3
MediaBrowser.Providers/Savers/SeasonXmlSaver.cs

@@ -1,9 +1,10 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
+using System.Security;
 using System.Text;
 using System.Threading;
 
@@ -32,9 +33,16 @@ namespace MediaBrowser.Providers.Savers
                 return false;
             }
 
-            return item is Season && updateType >= ItemUpdateType.MetadataDownload;
+            if (!(item is Season))
+            {
+                return false;
+            }
+
+            return updateType >= ItemUpdateType.MetadataDownload || (updateType >= ItemUpdateType.MetadataImport && File.Exists(GetSavePath(item)));
         }
 
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+        
         /// <summary>
         /// Saves the specified item.
         /// </summary>
@@ -47,13 +55,23 @@ namespace MediaBrowser.Providers.Savers
 
             builder.Append("<Item>");
 
+            var season = (Season)item;
+
+            if (season.IndexNumber.HasValue)
+            {
+                builder.Append("<SeasonNumber>" + SecurityElement.Escape(season.IndexNumber.Value.ToString(_usCulture)) + "</SeasonNumber>");
+            }
+            
             XmlSaverHelpers.AddCommonNodes((Season)item, builder);
 
             builder.Append("</Item>");
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string> { });
+            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
+            {
+                "SeasonNumber"
+            });
         }
 
         /// <summary>

+ 46 - 0
MediaBrowser.Providers/TV/SeasonXmlParser.cs

@@ -0,0 +1,46 @@
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using System.Xml;
+
+namespace MediaBrowser.Providers.TV
+{
+    public class SeasonXmlParser : BaseItemXmlParser<Season>
+    {
+        public SeasonXmlParser(ILogger logger)
+            : base(logger)
+        {
+        }
+
+        /// <summary>
+        /// Fetches the data from XML node.
+        /// </summary>
+        /// <param name="reader">The reader.</param>
+        /// <param name="item">The item.</param>
+        protected override void FetchDataFromXmlNode(XmlReader reader, Season item)
+        {
+            switch (reader.Name)
+            {
+                case "SeasonNumber":
+                    {
+                        var number = reader.ReadElementContentAsString();
+
+                        if (!string.IsNullOrWhiteSpace(number))
+                        {
+                            int num;
+
+                            if (int.TryParse(number, out num))
+                            {
+                                item.IndexNumber = num;
+                            }
+                        }
+                        break;
+                    }
+
+                default:
+                    base.FetchDataFromXmlNode(reader, item);
+                    break;
+            }
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Providers/TV/SeasonXmlProvider.cs

@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.TV
 
         protected override void Fetch(LocalMetadataResult<Season> result, string path, CancellationToken cancellationToken)
         {
-            new BaseItemXmlParser<Season>(_logger).Fetch(result.Item, path, cancellationToken);
+            new SeasonXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
         }
 
         protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService)

+ 22 - 5
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -268,9 +268,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 PlayableMediaTypes = session.PlayableMediaTypes,
                 RemoteEndPoint = session.RemoteEndPoint,
                 AdditionalUsers = session.AdditionalUsers,
-                SupportsFullscreenToggle = session.SupportsFullscreenToggle,
-                SupportsNavigationControl = session.SupportsNavigationControl,
-                SupportsOsdToggle = session.SupportsOsdToggle
+                SupportedCommands = session.SupportedCommands
             };
 
             if (session.NowPlayingItem != null)
@@ -1347,7 +1345,7 @@ namespace MediaBrowser.Server.Implementations.Dto
         {
             var mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery { ItemId = i.Id }).ToList();
 
-            return new MediaSourceInfo
+            var info = new MediaSourceInfo
             {
                 Id = i.Id.ToString("N"),
                 IsoType = i.IsoType,
@@ -1359,11 +1357,22 @@ namespace MediaBrowser.Server.Implementations.Dto
                 Video3DFormat = i.Video3DFormat,
                 VideoType = i.VideoType
             };
+
+            if (i.VideoType == VideoType.VideoFile || i.VideoType == VideoType.Iso)
+            {
+                var locationType = i.LocationType;
+                if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
+                {
+                    info.Container = Path.GetExtension(i.Path).TrimStart('.');
+                }
+            }
+
+            return info;
         }
 
         private MediaSourceInfo GetVersionInfo(Audio i)
         {
-            return new MediaSourceInfo
+            var info = new MediaSourceInfo
             {
                 Id = i.Id.ToString("N"),
                 LocationType = i.LocationType,
@@ -1372,6 +1381,14 @@ namespace MediaBrowser.Server.Implementations.Dto
                 Path = GetMappedPath(i),
                 RunTimeTicks = i.RunTimeTicks
             };
+
+            var locationType = i.LocationType;
+            if (!string.IsNullOrWhiteSpace(i.Path) && locationType != LocationType.Remote && locationType != LocationType.Virtual)
+            {
+                info.Container = Path.GetExtension(i.Path).TrimStart('.');
+            }
+
+            return info;
         }
 
         private string GetMappedPath(IHasMetadata item)

+ 77 - 0
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -417,6 +417,11 @@ namespace MediaBrowser.Server.Implementations.IO
                 _affectedPaths.AddOrUpdate(path, path, (key, oldValue) => affectedPath);
             }
 
+            RestartTimer();
+        }
+
+        private void RestartTimer()
+        {
             lock (_timerLock)
             {
                 if (_updateTimer == null)
@@ -436,6 +441,14 @@ namespace MediaBrowser.Server.Implementations.IO
         /// <param name="stateInfo">The state info.</param>
         private async void TimerStopped(object stateInfo)
         {
+            // Extend the timer as long as any of the paths are still being written to.
+            if (_affectedPaths.Any(p => IsFileLocked(p.Key)))
+            {
+                Logger.Info("Timer extended.");
+                RestartTimer();
+                return;
+            }
+
             Logger.Debug("Timer stopped.");
 
             DisposeTimer();
@@ -453,6 +466,70 @@ namespace MediaBrowser.Server.Implementations.IO
             }
         }
 
+        private bool IsFileLocked(string path)
+        {
+            try
+            {
+                var data = _fileSystem.GetFileSystemInfo(path);
+
+                if (!data.Exists
+                    || data.Attributes.HasFlag(FileAttributes.Directory)
+
+                    // Opening a writable stream will fail with readonly files
+                    || data.Attributes.HasFlag(FileAttributes.ReadOnly))
+                {
+                    return false;
+                }
+            }
+            catch (IOException)
+            {
+                return false;
+            }
+            catch (Exception ex)
+            {
+                Logger.ErrorException("Error getting file system info for: {0}", ex, path);
+                return false;
+            }
+
+            try
+            {
+                using (_fileSystem.GetFileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
+                {
+                    if (_updateTimer != null)
+                    {
+                        //file is not locked
+                        return false;
+                    }
+                }
+            }
+            catch (DirectoryNotFoundException)
+            {
+                // File may have been deleted
+                return false;
+            }
+            catch (FileNotFoundException)
+            {
+                // File may have been deleted
+                return false;
+            }
+            catch (IOException)
+            {
+                //the file is unavailable because it is:
+                //still being written to
+                //or being processed by another thread
+                //or does not exist (has already been processed)
+                Logger.Debug("{0} is locked.", path);
+                return true;
+            }
+            catch (Exception ex)
+            {
+                Logger.ErrorException("Error determining if file is locked: {0}", ex, path);
+                return false;
+            }
+
+            return false;
+        }
+
         private void DisposeTimer()
         {
             lock (_timerLock)

+ 1 - 0
MediaBrowser.Server.Implementations/Localization/JavaScript/es_MX.json

@@ -0,0 +1 @@
+{"SettingsSaved":"Configuraci\u00f3n guardada.","AddUser":"Agregar usuario","Users":"Usuarios","Delete":"Eliminar","Administrator":"Administrador","Password":"Contrase\u00f1a","DeleteImage":"Eliminar imagen","DeleteImageConfirmation":"\u00bfEst\u00e1 seguro que desea eliminar esta imagen?","FileReadCancelled":"La lectura del archivo ha sido cancelada.","FileNotFound":"Archivo no encontrado.","FileReadError":"Ha ocurrido un error al leer el archivo.","DeleteUser":"Eliminar Usuario","DeleteUserConfirmation":"\u00bfEsta seguro que desea eliminar a {0}?","PasswordResetHeader":"Restablecer Contrase\u00f1a","PasswordResetComplete":"La contrase\u00f1a ha sido restablecida.","PasswordResetConfirmation":"\u00bfEst\u00e1 seguro que desea restablecer la contrase\u00f1a?","PasswordSaved":"Contrase\u00f1a guardada.","PasswordMatchError":"La Contrase\u00f1a y la confirmaci\u00f3n de la contrase\u00f1a deben coincidir.","OptionOff":"Apagado","OptionOn":"Encendido","OptionRelease":"Liberar","OptionBeta":"Beta","OptionDev":"Desarrollo","UninstallPluginHeader":"Desinstalar Complemento","UninstallPluginConfirmation":"\u00bfEst\u00e1 seguro que desea desinstalar {0}?","NoPluginConfigurationMessage":"El complemento no requiere configuraci\u00f3n","NoPluginsInstalledMessage":"No tiene complementos instalados.","BrowsePluginCatalogMessage":"Navege en el catalogo de complementos para ver los complementos disponibles."}

+ 1 - 1
MediaBrowser.Server.Implementations/Localization/JavaScript/pt_PT.json

@@ -1 +1 @@
-{"SettingsSaved":"Configura\u00e7\u00f5es guardadas.","AddUser":"Adicionar Utilizador","Users":"Utilizadores","Delete":"Apagar","Administrator":"Administrador","Password":"Senha","DeleteImage":"Apagar Imagem","DeleteImageConfirmation":"Tem a certeza que pretende apagar a imagem?","FileReadCancelled":"A leitura do ficheiro foi cancelada.","FileNotFound":"Ficheiro n\u00e3o encontrado","FileReadError":"Ocorreu um erro ao ler o ficheiro.","DeleteUser":"Apagar Utilizador","DeleteUserConfirmation":"Tem a certeza que pretende apagar {0}?","PasswordResetHeader":"Redefinir Senha","PasswordResetComplete":"A senha foi redefinida.","PasswordResetConfirmation":"Tem a certeza que pretende redefinir a senha?","PasswordSaved":"Senha guardada.","PasswordMatchError":"A senha e a confirma\u00e7\u00e3o da senha devem coincidir.","OptionOff":"Desligado","OptionOn":"Ligado","OptionRelease":"Final","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Desinstalar extens\u00e3o","UninstallPluginConfirmation":"Tem a certeza que pretende desinstalar {0}?","NoPluginConfigurationMessage":"Esta extens\u00e3o n\u00e3o \u00e9 configur\u00e1vel.","NoPluginsInstalledMessage":"N\u00e3o tem extens\u00f5es instaladas.","BrowsePluginCatalogMessage":"Navegue o nosso cat\u00e1logo de extens\u00f5es para ver as extens\u00f5es dispon\u00edveis."}
+{"SettingsSaved":"Configura\u00e7\u00f5es guardadas.","AddUser":"Adicionar Utilizador","Users":"Utilizadores","Delete":"Apagar","Administrator":"Administrador","Password":"Senha","DeleteImage":"Apagar Imagem","DeleteImageConfirmation":"Tem a certeza que pretende apagar a imagem?","FileReadCancelled":"A leitura do ficheiro foi cancelada.","FileNotFound":"Ficheiro n\u00e3o encontrado","FileReadError":"Ocorreu um erro ao ler o ficheiro.","DeleteUser":"Apagar Utilizador","DeleteUserConfirmation":"Tem a certeza que pretende apagar {0}?","PasswordResetHeader":"Redefinir Senha","PasswordResetComplete":"A senha foi redefinida.","PasswordResetConfirmation":"Tem a certeza que deseja redefinir a senha?","PasswordSaved":"Senha guardada.","PasswordMatchError":"A senha e a confirma\u00e7\u00e3o da senha devem coincidir.","OptionOff":"Desligado","OptionOn":"Ligado","OptionRelease":"Final","OptionBeta":"Beta","OptionDev":"Dev","UninstallPluginHeader":"Desinstalar extens\u00e3o","UninstallPluginConfirmation":"Tem a certeza que pretende desinstalar {0}?","NoPluginConfigurationMessage":"Esta extens\u00e3o n\u00e3o \u00e9 configur\u00e1vel.","NoPluginsInstalledMessage":"N\u00e3o tem extens\u00f5es instaladas.","BrowsePluginCatalogMessage":"Navegue o nosso cat\u00e1logo de extens\u00f5es, para ver as extens\u00f5es dispon\u00edveis."}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/JavaScript/ru.json


+ 2 - 1
MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs

@@ -343,7 +343,8 @@ namespace MediaBrowser.Server.Implementations.Localization
                 new LocalizatonOption{ Name="Portuguese (Brazil)", Value="pt-BR"},
                 new LocalizatonOption{ Name="Portuguese (Portugal)", Value="pt-PT"},
                 new LocalizatonOption{ Name="Russian", Value="ru"},
-                new LocalizatonOption{ Name="Spanish", Value="es"}
+                new LocalizatonOption{ Name="Spanish", Value="es"},
+                new LocalizatonOption{ Name="Spanish (Mexico)", Value="es-MX"}
 
             }.OrderBy(i => i.Name);
         }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/de.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/en_US.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/es.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/es_MX.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/fr.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/he.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/it.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/nl.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/pt_BR.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/pt_PT.json


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/ru.json


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

@@ -85,5 +85,64 @@
 	"TabGenres": "Genres",
 	"TabPeople": "People",
 	"TabNetworks": "Networks",
-	"HeaderUsers": "Users"
+	"HeaderUsers": "Users",
+	"HeaderFilters": "Filters:",
+	"ButtonFilter": "Filter",
+	"OptionFavorite": "Favorites",
+	"OptionLikes": "Likes",
+	"OptionDislikes": "Dislikes",
+	"OptionActors": "Actors",
+	"OptionGuestStars": "Guest Stars",
+	"OptionDirectors": "Directors",
+	"OptionWriters": "Writers",
+	"OptionProducers": "Producers",
+	"HeaderResume": "Resume",
+	"HeaderNextUp": "Next Up",
+	"NoNextUpItemsMessage": "None found. Start watching your shows!",
+	"HeaderLatestEpisodes": "Latest Episodes",
+	"HeaderPersonTypes": "Person Types:",
+	"TabSongs": "Songs",
+	"TabAlbums": "Albums",
+	"TabArtists": "Artists",
+	"TabAlbumArtists": "Album Artists",
+	"TabMusicVideos": "Music Videos",
+	"ButtonSort": "Sort",
+	"HeaderSortBy": "Sort By:",
+	"HeaderSortOrder": "Sort Order:",
+	"OptionPlayed": "Played",
+	"OptionUnplayed": "Unplayed",
+	"OptionAscending": "Ascending",
+	"OptionDescending": "Descending",
+	"OptionRuntime": "Runtime",
+	"OptionReleaseDate": "Release Date",
+	"OptionPlayCount": "Play Count",
+	"OptionDatePlayed": "Date Played",
+	"OptionDateAdded": "Date Added",
+	"OptionAlbumArtist": "Album Artist",
+	"OptionArtist": "Artist",
+	"OptionAlbum": "Album",
+	"OptionTrackName": "Track Name",
+	"OptionCommunityRating": "Community Rating",
+	"OptionNameSort": "Name",
+	"OptionBudget": "Budget",
+	"OptionRevenue": "Revenue",
+	"OptionPoster": "Poster",
+	"OptionTimeline": "Timeline",
+	"OptionCriticRating": "Critic Rating",
+	"OptionVideoBitrate": "Video Bitrate",
+	"OptionResumable": "Resumable",
+	"ScheduledTasksHelp": "Click a task to adjust it's schedule.",
+	"ScheduledTasksTitle": "ScheduledTasks",
+	"TabMyPlugins": "My Plugins",
+	"TabCatalog": "Catalog",
+	"TabUpdates": "Updates",
+	"PluginsTitle": "Plugins",
+	"HeaderAutomaticUpdates": "Automatic Updates",
+	"HeaderUpdateLevel": "Update Level",
+	"HeaderNowPlaying": "Now Playing",
+	"HeaderLatestAlbums": "Latest Albums",
+	"HeaderLatestSongs": "Latest Songs",
+	"HeaderRecentlyPlayed": "Recently Played",
+	"HeaderFrequentlyPlayed": "Frequently Played",
+	"DevBuildWarning": "Dev builds are the bleeding edge. Released often, these build have not been tested. The application may crash and entire features may not work at all."
 }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
MediaBrowser.Server.Implementations/Localization/Server/zh_TW.json


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

@@ -305,6 +305,8 @@
     <EmbeddedResource Include="Localization\JavaScript\pt_BR.json" />
     <EmbeddedResource Include="Localization\Server\he.json" />
     <EmbeddedResource Include="Localization\Server\it.json" />
+    <EmbeddedResource Include="Localization\Server\es_MX.json" />
+    <EmbeddedResource Include="Localization\JavaScript\es_MX.json" />
     <None Include="packages.config" />
   </ItemGroup>
   <ItemGroup>

+ 0 - 1
MediaBrowser.Server.Implementations/Roku/RokuControllerFactory.cs

@@ -26,7 +26,6 @@ namespace MediaBrowser.Server.Implementations.Roku
             if (string.Equals(session.Client, "roku", StringComparison.OrdinalIgnoreCase))
             {
                 session.PlayableMediaTypes = new List<string> { MediaType.Video, MediaType.Audio };
-                session.SupportsFullscreenToggle = false;
 
                 return new RokuSessionController(_httpClient, _json, _appHost, session);
             }

+ 4 - 5
MediaBrowser.Server.Implementations/Session/SessionManager.cs

@@ -80,7 +80,7 @@ namespace MediaBrowser.Server.Implementations.Session
         /// <param name="logger">The logger.</param>
         /// <param name="userRepository">The user repository.</param>
         /// <param name="libraryManager">The library manager.</param>
-        public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager)
+        public SessionManager(IUserDataManager userDataRepository, IServerConfigurationManager configurationManager, ILogger logger, IUserRepository userRepository, ILibraryManager libraryManager, IUserManager userManager, IMusicManager musicManager)
         {
             _userDataRepository = userDataRepository;
             _configurationManager = configurationManager;
@@ -88,6 +88,7 @@ namespace MediaBrowser.Server.Implementations.Session
             _userRepository = userRepository;
             _libraryManager = libraryManager;
             _userManager = userManager;
+            _musicManager = musicManager;
         }
 
         /// <summary>
@@ -1013,10 +1014,8 @@ namespace MediaBrowser.Server.Implementations.Session
         {
             var session = GetSession(sessionId);
 
-            session.PlayableMediaTypes = capabilities.PlayableMediaTypes.ToList();
-            session.SupportsFullscreenToggle = capabilities.SupportsFullscreenToggle;
-            session.SupportsOsdToggle = capabilities.SupportsOsdToggle;
-            session.SupportsNavigationControl = capabilities.SupportsNavigationControl;
+            session.PlayableMediaTypes = capabilities.PlayableMediaTypes;
+            session.SupportedCommands = capabilities.SupportedCommands;
         }
     }
 }

+ 2 - 1
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -452,6 +452,7 @@ namespace MediaBrowser.ServerApplication
             LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager);
             RegisterSingleInstance(LibraryManager);
 
+            var musicManager = new MusicManager(LibraryManager);
             RegisterSingleInstance<IMusicManager>(new MusicManager(LibraryManager));
 
             LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager);
@@ -465,7 +466,7 @@ namespace MediaBrowser.ServerApplication
 
             RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
 
-            SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager);
+            SessionManager = new SessionManager(UserDataManager, ServerConfigurationManager, Logger, UserRepository, LibraryManager, UserManager, musicManager);
             RegisterSingleInstance(SessionManager);
 
             HttpServer = ServerFactory.CreateServer(this, LogManager, "Media Browser", "mediabrowser", "dashboard/index.html");

+ 5 - 0
MediaBrowser.WebDashboard/Api/DashboardService.cs

@@ -312,6 +312,7 @@ namespace MediaBrowser.WebDashboard.Api
         /// Modifies the HTML by adding common meta tags, css and js.
         /// </summary>
         /// <param name="sourceStream">The source stream.</param>
+        /// <param name="localizationCulture">The localization culture.</param>
         /// <returns>Task{Stream}.</returns>
         private async Task<Stream> ModifyHtml(Stream sourceStream, string localizationCulture)
         {
@@ -327,7 +328,11 @@ namespace MediaBrowser.WebDashboard.Api
 
                     if (!string.IsNullOrWhiteSpace(localizationCulture))
                     {
+                        var lang = localizationCulture.Split('-').FirstOrDefault();
+
                         html = _localization.LocalizeDocument(html, localizationCulture, GetLocalizationToken);
+
+                        html = html.Replace("<html>", "<html lang=\"" + lang + "\">");
                     }
 
                     try

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio