mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-03 19:17:24 -05:00 
			
		
		
		
	Fix warnings
This commit is contained in:
		
							parent
							
								
									634ee2d1e9
								
							
						
					
					
						commit
						2b400c99ef
					
				@ -54,11 +54,15 @@ namespace Emby.Dlna
 | 
				
			|||||||
            _appHost = appHost;
 | 
					            _appHost = appHost;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task InitProfilesAsync()
 | 
					        public async Task InitProfilesAsync()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                await ExtractSystemProfilesAsync();
 | 
					                await ExtractSystemProfilesAsync().ConfigureAwait(false);
 | 
				
			||||||
                LoadProfiles();
 | 
					                LoadProfiles();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (Exception ex)
 | 
					            catch (Exception ex)
 | 
				
			||||||
@ -240,7 +244,7 @@ namespace Emby.Dlna
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var headerString = string.Join(", ", headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
 | 
					                var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value)));
 | 
				
			||||||
                _logger.LogDebug("No matching device profile found. {0}", headerString);
 | 
					                _logger.LogDebug("No matching device profile found. {0}", headerString);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -280,10 +284,6 @@ namespace Emby.Dlna
 | 
				
			|||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private IEnumerable<DeviceProfile> GetProfiles(string path, DeviceProfileType type)
 | 
					        private IEnumerable<DeviceProfile> GetProfiles(string path, DeviceProfileType type)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
 | 
				
			|||||||
@ -19,6 +19,8 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public class Device : IDisposable
 | 
					    public class Device : IDisposable
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private Timer _timer;
 | 
					        private Timer _timer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public DeviceInfo Properties { get; set; }
 | 
					        public DeviceInfo Properties { get; set; }
 | 
				
			||||||
@ -55,16 +57,13 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private readonly ILogger _logger;
 | 
					        private readonly ILogger _logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly IServerConfigurationManager _config;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public Action OnDeviceUnavailable { get; set; }
 | 
					        public Action OnDeviceUnavailable { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
 | 
					        public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Properties = deviceProperties;
 | 
					            Properties = deviceProperties;
 | 
				
			||||||
            _httpClient = httpClient;
 | 
					            _httpClient = httpClient;
 | 
				
			||||||
            _logger = logger;
 | 
					            _logger = logger;
 | 
				
			||||||
            _config = config;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void Start()
 | 
					        public void Start()
 | 
				
			||||||
@ -275,7 +274,7 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
                throw new InvalidOperationException("Unable to find service");
 | 
					                throw new InvalidOperationException("Unable to find service");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
 | 
					            await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
 | 
				
			||||||
                .ConfigureAwait(false);
 | 
					                .ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            RestartTimer(true);
 | 
					            RestartTimer(true);
 | 
				
			||||||
@ -285,7 +284,7 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
 | 
					            var avCommands = await GetAVProtocolAsync(cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            url = url.Replace("&", "&");
 | 
					            url = url.Replace("&", "&", StringComparison.Ordinal);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
 | 
					            _logger.LogDebug("{0} - SetAvTransport Uri: {1} DlnaHeaders: {2}", Properties.Name, url, header);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -297,8 +296,8 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var dictionary = new Dictionary<string, string>
 | 
					            var dictionary = new Dictionary<string, string>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                {"CurrentURI", url},
 | 
					                { "CurrentURI", url },
 | 
				
			||||||
                {"CurrentURIMetaData", CreateDidlMeta(metaData)}
 | 
					                { "CurrentURIMetaData", CreateDidlMeta(metaData) }
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var service = GetAvTransportService();
 | 
					            var service = GetAvTransportService();
 | 
				
			||||||
@ -732,10 +731,10 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
 | 
					            var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
 | 
				
			||||||
            var trackUri = trackUriElem == null ? null : trackUriElem.Value;
 | 
					            var trackUri = trackUriElem?.Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
 | 
					            var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
 | 
				
			||||||
            var duration = durationElem == null ? null : durationElem.Value;
 | 
					            var duration = durationElem?.Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(duration)
 | 
					            if (!string.IsNullOrWhiteSpace(duration)
 | 
				
			||||||
                && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
 | 
					                && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
@ -748,7 +747,7 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
 | 
					            var positionElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("RelTime")).FirstOrDefault(i => i != null);
 | 
				
			||||||
            var position = positionElem == null ? null : positionElem.Value;
 | 
					            var position = positionElem?.Value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
 | 
					            if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -819,7 +818,7 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
            // some devices send back invalid xml
 | 
					            // some devices send back invalid xml
 | 
				
			||||||
            try
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return XElement.Parse(xml.Replace("&", "&"));
 | 
					                return XElement.Parse(xml.Replace("&", "&", StringComparison.Ordinal));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (XmlException)
 | 
					            catch (XmlException)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -848,7 +847,7 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
                ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId),
 | 
					                ParentId = container.GetAttributeValue(uPnpNamespaces.ParentId),
 | 
				
			||||||
                Title = container.GetValue(uPnpNamespaces.title),
 | 
					                Title = container.GetValue(uPnpNamespaces.title),
 | 
				
			||||||
                IconUrl = container.GetValue(uPnpNamespaces.Artwork),
 | 
					                IconUrl = container.GetValue(uPnpNamespaces.Artwork),
 | 
				
			||||||
                SecondText = "",
 | 
					                SecondText = string.Empty,
 | 
				
			||||||
                Url = url,
 | 
					                Url = url,
 | 
				
			||||||
                ProtocolInfo = GetProtocolInfo(container),
 | 
					                ProtocolInfo = GetProtocolInfo(container),
 | 
				
			||||||
                MetaData = container.ToString()
 | 
					                MetaData = container.ToString()
 | 
				
			||||||
@ -941,12 +940,12 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
                return url;
 | 
					                return url;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!url.Contains("/"))
 | 
					            if (!url.Contains('/', StringComparison.Ordinal))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                url = "/dmr/" + url;
 | 
					                url = "/dmr/" + url;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!url.StartsWith("/"))
 | 
					            if (!url.StartsWith("/", StringComparison.Ordinal))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                url = "/" + url;
 | 
					                url = "/" + url;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -981,7 +980,7 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
            var deviceProperties = new DeviceInfo()
 | 
					            var deviceProperties = new DeviceInfo()
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Name = string.Join(" ", friendlyNames),
 | 
					                Name = string.Join(" ", friendlyNames),
 | 
				
			||||||
                BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port)
 | 
					                BaseUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", url.Host, url.Port)
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
 | 
					            var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
 | 
				
			||||||
@ -1068,10 +1067,9 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return new Device(deviceProperties, httpClient, logger, config);
 | 
					            return new Device(deviceProperties, httpClient, logger);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 | 
					 | 
				
			||||||
        private static DeviceIcon CreateIcon(XElement element)
 | 
					        private static DeviceIcon CreateIcon(XElement element)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (element == null)
 | 
					            if (element == null)
 | 
				
			||||||
@ -1222,7 +1220,7 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public override string ToString()
 | 
					        public override string ToString()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return string.Format("{0} - {1}", Properties.Name, Properties.BaseUrl);
 | 
					            return string.Format(CultureInfo.InvariantCulture, "{0} - {1}", Properties.Name, Properties.BaseUrl);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Xml.Linq;
 | 
					using System.Xml.Linq;
 | 
				
			||||||
using Emby.Dlna.Common;
 | 
					using Emby.Dlna.Common;
 | 
				
			||||||
@ -11,14 +12,16 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
    public class TransportCommands
 | 
					    public class TransportCommands
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
 | 
				
			||||||
        private List<StateVariable> _stateVariables = new List<StateVariable>();
 | 
					        private List<StateVariable> _stateVariables = new List<StateVariable>();
 | 
				
			||||||
 | 
					        private List<ServiceAction> _serviceActions = new List<ServiceAction>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public List<StateVariable> StateVariables
 | 
					        public List<StateVariable> StateVariables
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            get => _stateVariables;
 | 
					            get => _stateVariables;
 | 
				
			||||||
            set => _stateVariables = value;
 | 
					            set => _stateVariables = value;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private List<ServiceAction> _serviceActions = new List<ServiceAction>();
 | 
					 | 
				
			||||||
        public List<ServiceAction> ServiceActions
 | 
					        public List<ServiceAction> ServiceActions
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            get => _serviceActions;
 | 
					            get => _serviceActions;
 | 
				
			||||||
@ -123,7 +126,7 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return string.Format(CommandBase, action.Name, xmlNamespace, stateString);
 | 
					            return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string BuildPost(ServiceAction action, string xmlNamesapce, object value, string commandParameter = "")
 | 
					        public string BuildPost(ServiceAction action, string xmlNamesapce, object value, string commandParameter = "")
 | 
				
			||||||
@ -147,7 +150,7 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
 | 
					            return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string BuildPost(ServiceAction action, string xmlNamesapce, object value, Dictionary<string, string> dictionary)
 | 
					        public string BuildPost(ServiceAction action, string xmlNamesapce, object value, Dictionary<string, string> dictionary)
 | 
				
			||||||
@ -170,7 +173,7 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return string.Format(CommandBase, action.Name, xmlNamesapce, stateString);
 | 
					            return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private string BuildArgumentXml(Argument argument, string value, string commandParameter = "")
 | 
					        private string BuildArgumentXml(Argument argument, string value, string commandParameter = "")
 | 
				
			||||||
@ -183,12 +186,10 @@ namespace Emby.Dlna.PlayTo
 | 
				
			|||||||
                                 state.AllowedValues.FirstOrDefault() ??
 | 
					                                 state.AllowedValues.FirstOrDefault() ??
 | 
				
			||||||
                                 value;
 | 
					                                 value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return string.Format("<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue);
 | 
					                return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return string.Format("<{0}>{1}</{0}>", argument.Name, value);
 | 
					            return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        private const string CommandBase = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:{0} xmlns:m=\"{1}\">" + "{2}" + "</m:{0}>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>";
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4560,13 +4560,13 @@ namespace Emby.Server.Implementations.Data
 | 
				
			|||||||
            if (query.AncestorIds.Length > 1)
 | 
					            if (query.AncestorIds.Length > 1)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
 | 
					                var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
 | 
				
			||||||
                whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
 | 
					                whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
 | 
					            if (!string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
 | 
					                var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
 | 
				
			||||||
                whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
 | 
					                whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
 | 
				
			||||||
                if (statement != null)
 | 
					                if (statement != null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    statement.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
 | 
					                    statement.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
 | 
				
			||||||
 | 
				
			|||||||
@ -230,7 +230,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (filters.Count > 0)
 | 
					            if (filters.Count > 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                output += string.Format(" -vf \"{0}\"", string.Join(",", filters.ToArray()));
 | 
					                output += string.Format(CultureInfo.InvariantCulture, " -vf \"{0}\"", string.Join(",", filters.ToArray()));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return output;
 | 
					            return output;
 | 
				
			||||||
 | 
				
			|||||||
@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 | 
				
			|||||||
                    && !programInfo.IsRepeat
 | 
					                    && !programInfo.IsRepeat
 | 
				
			||||||
                    && (programInfo.EpisodeNumber ?? 0) == 0)
 | 
					                    && (programInfo.EpisodeNumber ?? 0) == 0)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    programInfo.ShowId = programInfo.ShowId + programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture);
 | 
					                    programInfo.ShowId += programInfo.StartDate.Ticks.ToString(CultureInfo.InvariantCulture);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
@ -246,7 +246,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Construct an id from the channel and start date
 | 
					            // Construct an id from the channel and start date
 | 
				
			||||||
            programInfo.Id = string.Format("{0}_{1:O}", program.ChannelId, program.StartDate);
 | 
					            programInfo.Id = string.Format(CultureInfo.InvariantCulture, "{0}_{1:O}", program.ChannelId, program.StartDate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (programInfo.IsMovie)
 | 
					            if (programInfo.IsMovie)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -296,7 +296,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
 | 
				
			|||||||
                Name = c.DisplayName,
 | 
					                Name = c.DisplayName,
 | 
				
			||||||
                ImageUrl = c.Icon != null && !string.IsNullOrEmpty(c.Icon.Source) ? c.Icon.Source : null,
 | 
					                ImageUrl = c.Icon != null && !string.IsNullOrEmpty(c.Icon.Source) ? c.Icon.Source : null,
 | 
				
			||||||
                Number = string.IsNullOrWhiteSpace(c.Number) ? c.Id : c.Number
 | 
					                Number = string.IsNullOrWhiteSpace(c.Number) ? c.Id : c.Number
 | 
				
			||||||
 | 
					 | 
				
			||||||
            }).ToList();
 | 
					            }).ToList();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -41,6 +41,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    public class LiveTvManager : ILiveTvManager, IDisposable
 | 
					    public class LiveTvManager : ILiveTvManager, IDisposable
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        private const int MaxGuideDays = 14;
 | 
				
			||||||
        private const string ExternalServiceTag = "ExternalServiceId";
 | 
					        private const string ExternalServiceTag = "ExternalServiceId";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private const string EtagKey = "ProgramEtag";
 | 
					        private const string EtagKey = "ProgramEtag";
 | 
				
			||||||
@ -560,7 +561,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            item.Audio = info.Audio;
 | 
					            item.Audio = info.Audio;
 | 
				
			||||||
            item.ChannelId = channel.Id;
 | 
					            item.ChannelId = channel.Id;
 | 
				
			||||||
            item.CommunityRating = item.CommunityRating ?? info.CommunityRating;
 | 
					            item.CommunityRating ??= info.CommunityRating;
 | 
				
			||||||
            if ((item.CommunityRating ?? 0).Equals(0))
 | 
					            if ((item.CommunityRating ?? 0).Equals(0))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                item.CommunityRating = null;
 | 
					                item.CommunityRating = null;
 | 
				
			||||||
@ -645,8 +646,8 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
            item.IsSeries = isSeries;
 | 
					            item.IsSeries = isSeries;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            item.Name = info.Name;
 | 
					            item.Name = info.Name;
 | 
				
			||||||
            item.OfficialRating = item.OfficialRating ?? info.OfficialRating;
 | 
					            item.OfficialRating ??= info.OfficialRating;
 | 
				
			||||||
            item.Overview = item.Overview ?? info.Overview;
 | 
					            item.Overview ??= info.Overview;
 | 
				
			||||||
            item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
 | 
					            item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks;
 | 
				
			||||||
            item.ProviderIds = info.ProviderIds;
 | 
					            item.ProviderIds = info.ProviderIds;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -683,19 +684,23 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                if (!string.IsNullOrWhiteSpace(info.ImagePath))
 | 
					                if (!string.IsNullOrWhiteSpace(info.ImagePath))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    item.SetImage(new ItemImageInfo
 | 
					                    item.SetImage(
 | 
				
			||||||
                    {
 | 
					                        new ItemImageInfo
 | 
				
			||||||
                        Path = info.ImagePath,
 | 
					                        {
 | 
				
			||||||
                        Type = ImageType.Primary
 | 
					                            Path = info.ImagePath,
 | 
				
			||||||
                    }, 0);
 | 
					                            Type = ImageType.Primary
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
 | 
					                else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    item.SetImage(new ItemImageInfo
 | 
					                    item.SetImage(
 | 
				
			||||||
                    {
 | 
					                        new ItemImageInfo
 | 
				
			||||||
                        Path = info.ImageUrl,
 | 
					                        {
 | 
				
			||||||
                        Type = ImageType.Primary
 | 
					                            Path = info.ImageUrl,
 | 
				
			||||||
                    }, 0);
 | 
					                            Type = ImageType.Primary
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -703,11 +708,13 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                if (!string.IsNullOrWhiteSpace(info.ThumbImageUrl))
 | 
					                if (!string.IsNullOrWhiteSpace(info.ThumbImageUrl))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    item.SetImage(new ItemImageInfo
 | 
					                    item.SetImage(
 | 
				
			||||||
                    {
 | 
					                        new ItemImageInfo
 | 
				
			||||||
                        Path = info.ThumbImageUrl,
 | 
					                        {
 | 
				
			||||||
                        Type = ImageType.Thumb
 | 
					                            Path = info.ThumbImageUrl,
 | 
				
			||||||
                    }, 0);
 | 
					                            Type = ImageType.Thumb
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -715,11 +722,13 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                if (!string.IsNullOrWhiteSpace(info.LogoImageUrl))
 | 
					                if (!string.IsNullOrWhiteSpace(info.LogoImageUrl))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    item.SetImage(new ItemImageInfo
 | 
					                    item.SetImage(
 | 
				
			||||||
                    {
 | 
					                        new ItemImageInfo
 | 
				
			||||||
                        Path = info.LogoImageUrl,
 | 
					                        {
 | 
				
			||||||
                        Type = ImageType.Logo
 | 
					                            Path = info.LogoImageUrl,
 | 
				
			||||||
                    }, 0);
 | 
					                            Type = ImageType.Logo
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -727,11 +736,13 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                if (!string.IsNullOrWhiteSpace(info.BackdropImageUrl))
 | 
					                if (!string.IsNullOrWhiteSpace(info.BackdropImageUrl))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    item.SetImage(new ItemImageInfo
 | 
					                    item.SetImage(
 | 
				
			||||||
                    {
 | 
					                        new ItemImageInfo
 | 
				
			||||||
                        Path = info.BackdropImageUrl,
 | 
					                        {
 | 
				
			||||||
                        Type = ImageType.Backdrop
 | 
					                            Path = info.BackdropImageUrl,
 | 
				
			||||||
                    }, 0);
 | 
					                            Type = ImageType.Backdrop
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        0);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -786,7 +797,6 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (query.OrderBy.Count == 0)
 | 
					            if (query.OrderBy.Count == 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Unless something else was specified, order by start date to take advantage of a specialized index
 | 
					                // Unless something else was specified, order by start date to take advantage of a specialized index
 | 
				
			||||||
                query.OrderBy = new[]
 | 
					                query.OrderBy = new[]
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@ -824,7 +834,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
 | 
					            if (!string.IsNullOrWhiteSpace(query.SeriesTimerId))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery { }, cancellationToken).ConfigureAwait(false);
 | 
					                var seriesTimers = await GetSeriesTimersInternal(new SeriesTimerQuery(), cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
                var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N", CultureInfo.InvariantCulture), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
 | 
					                var seriesTimer = seriesTimers.Items.FirstOrDefault(i => string.Equals(_tvDtoService.GetInternalSeriesTimerId(i.Id).ToString("N", CultureInfo.InvariantCulture), query.SeriesTimerId, StringComparison.OrdinalIgnoreCase));
 | 
				
			||||||
                if (seriesTimer != null)
 | 
					                if (seriesTimer != null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@ -847,13 +857,11 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user);
 | 
					            var returnArray = _dtoService.GetBaseItemDtos(queryResult.Items, options, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var result = new QueryResult<BaseItemDto>
 | 
					            return new QueryResult<BaseItemDto>
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Items = returnArray,
 | 
					                Items = returnArray,
 | 
				
			||||||
                TotalRecordCount = queryResult.TotalRecordCount
 | 
					                TotalRecordCount = queryResult.TotalRecordCount
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
 | 
					 | 
				
			||||||
            return result;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
 | 
					        public QueryResult<BaseItem> GetRecommendedProgramsInternal(InternalItemsQuery query, DtoOptions options, CancellationToken cancellationToken)
 | 
				
			||||||
@ -1173,7 +1181,6 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
 | 
					                    var existingPrograms = _libraryManager.GetItemList(new InternalItemsQuery
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
 | 
					 | 
				
			||||||
                        IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
 | 
					                        IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
 | 
				
			||||||
                        ChannelIds = new Guid[] { currentChannel.Id },
 | 
					                        ChannelIds = new Guid[] { currentChannel.Id },
 | 
				
			||||||
                        DtoOptions = new DtoOptions(true)
 | 
					                        DtoOptions = new DtoOptions(true)
 | 
				
			||||||
@ -1298,8 +1305,6 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private const int MaxGuideDays = 14;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private double GetGuideDays()
 | 
					        private double GetGuideDays()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var config = GetConfiguration();
 | 
					            var config = GetConfiguration();
 | 
				
			||||||
@ -1712,7 +1717,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (timer == null)
 | 
					            if (timer == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                throw new ResourceNotFoundException(string.Format("Timer with Id {0} not found", id));
 | 
					                throw new ResourceNotFoundException(string.Format(CultureInfo.InvariantCulture, "Timer with Id {0} not found", id));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var service = GetService(timer.ServiceName);
 | 
					            var service = GetService(timer.ServiceName);
 | 
				
			||||||
@ -1731,7 +1736,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (timer == null)
 | 
					            if (timer == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                throw new ResourceNotFoundException(string.Format("SeriesTimer with Id {0} not found", id));
 | 
					                throw new ResourceNotFoundException(string.Format(CultureInfo.InvariantCulture, "SeriesTimer with Id {0} not found", id));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var service = GetService(timer.ServiceName);
 | 
					            var service = GetService(timer.ServiceName);
 | 
				
			||||||
@ -1743,10 +1748,12 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
 | 
					        public async Task<TimerInfoDto> GetTimer(string id, CancellationToken cancellationToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var results = await GetTimers(new TimerQuery
 | 
					            var results = await GetTimers(
 | 
				
			||||||
            {
 | 
					                new TimerQuery
 | 
				
			||||||
                Id = id
 | 
					                {
 | 
				
			||||||
            }, cancellationToken).ConfigureAwait(false);
 | 
					                    Id = id
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
 | 
					            return results.Items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -1794,10 +1801,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var returnArray = timers
 | 
					            var returnArray = timers
 | 
				
			||||||
                .Select(i =>
 | 
					                .Select(i => i.Item1)
 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return i.Item1;
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                .ToArray();
 | 
					                .ToArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return new QueryResult<SeriesTimerInfo>
 | 
					            return new QueryResult<SeriesTimerInfo>
 | 
				
			||||||
@ -1968,7 +1972,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (service == null)
 | 
					            if (service == null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                service = _services.First();
 | 
					                service = _services[0];
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
 | 
					            var info = await service.GetNewTimerDefaultsAsync(cancellationToken, programInfo).ConfigureAwait(false);
 | 
				
			||||||
@ -1994,9 +1998,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            var info = await GetNewTimerDefaultsInternal(cancellationToken).ConfigureAwait(false);
 | 
					            var info = await GetNewTimerDefaultsInternal(cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var obj = _tvDtoService.GetSeriesTimerInfoDto(info.Item1, info.Item2, null);
 | 
					            return _tvDtoService.GetSeriesTimerInfoDto(info.Item1, info.Item2, null);
 | 
				
			||||||
 | 
					 | 
				
			||||||
            return obj;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
 | 
					        public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken)
 | 
				
			||||||
@ -2125,6 +2127,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
        public void Dispose()
 | 
					        public void Dispose()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Dispose(true);
 | 
					            Dispose(true);
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private bool _disposed = false;
 | 
					        private bool _disposed = false;
 | 
				
			||||||
@ -2447,8 +2450,7 @@ namespace Emby.Server.Implementations.LiveTv
 | 
				
			|||||||
                .SelectMany(i => i.Locations)
 | 
					                .SelectMany(i => i.Locations)
 | 
				
			||||||
                .Distinct(StringComparer.OrdinalIgnoreCase)
 | 
					                .Distinct(StringComparer.OrdinalIgnoreCase)
 | 
				
			||||||
                .Select(i => _libraryManager.FindByPath(i, true))
 | 
					                .Select(i => _libraryManager.FindByPath(i, true))
 | 
				
			||||||
                .Where(i => i != null)
 | 
					                .Where(i => i != null && i.IsVisibleStandalone(user))
 | 
				
			||||||
                .Where(i => i.IsVisibleStandalone(user))
 | 
					 | 
				
			||||||
                .SelectMany(i => _libraryManager.GetCollectionFolders(i))
 | 
					                .SelectMany(i => _libraryManager.GetCollectionFolders(i))
 | 
				
			||||||
                .GroupBy(x => x.Id)
 | 
					                .GroupBy(x => x.Id)
 | 
				
			||||||
                .Select(x => x.First())
 | 
					                .Select(x => x.First())
 | 
				
			||||||
 | 
				
			|||||||
@ -182,12 +182,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
 | 
					            var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            using (var response = await _httpClient.SendAsync(new HttpRequestOptions()
 | 
					            using (var response = await _httpClient.SendAsync(
 | 
				
			||||||
            {
 | 
					                new HttpRequestOptions()
 | 
				
			||||||
                Url = string.Format("{0}/tuners.html", GetApiUrl(info)),
 | 
					                {
 | 
				
			||||||
                CancellationToken = cancellationToken,
 | 
					                    Url = string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)),
 | 
				
			||||||
                BufferContent = false
 | 
					                    CancellationToken = cancellationToken,
 | 
				
			||||||
            }, HttpMethod.Get).ConfigureAwait(false))
 | 
					                    BufferContent = false
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                HttpMethod.Get).ConfigureAwait(false))
 | 
				
			||||||
            using (var stream = response.Content)
 | 
					            using (var stream = response.Content)
 | 
				
			||||||
            using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
 | 
					            using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -730,7 +732,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
 | 
				
			|||||||
                // Need a way to set the Receive timeout on the socket otherwise this might never timeout?
 | 
					                // Need a way to set the Receive timeout on the socket otherwise this might never timeout?
 | 
				
			||||||
                try
 | 
					                try
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IPEndPoint(IPAddress.Parse("255.255.255.255"), 65001), cancellationToken);
 | 
					                    await udpClient.SendToAsync(discBytes, 0, discBytes.Length, new IPEndPoint(IPAddress.Parse("255.255.255.255"), 65001), cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
                    var receiveBuffer = new byte[8192];
 | 
					                    var receiveBuffer = new byte[8192];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    while (!cancellationToken.IsCancellationRequested)
 | 
					                    while (!cancellationToken.IsCancellationRequested)
 | 
				
			||||||
 | 
				
			|||||||
@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
 | 
				
			|||||||
            user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
 | 
					            user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await _providerManager
 | 
					            await _providerManager
 | 
				
			||||||
                .SaveImage(user, memoryStream, mimeType, user.ProfileImage.Path)
 | 
					                .SaveImage(memoryStream, mimeType, user.ProfileImage.Path)
 | 
				
			||||||
                .ConfigureAwait(false);
 | 
					                .ConfigureAwait(false);
 | 
				
			||||||
            await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
 | 
					            await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Text.Json.Serialization;
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
@ -179,7 +181,7 @@ namespace MediaBrowser.Controller.Entities.Movies
 | 
				
			|||||||
                list.Add(new ExternalUrl
 | 
					                list.Add(new ExternalUrl
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    Name = "Trakt",
 | 
					                    Name = "Trakt",
 | 
				
			||||||
                    Url = string.Format("https://trakt.tv/movies/{0}", imdbId)
 | 
					                    Url = string.Format(CultureInfo.InvariantCulture, "https://trakt.tv/movies/{0}", imdbId)
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -496,7 +496,7 @@ namespace MediaBrowser.Controller.Entities.TV
 | 
				
			|||||||
                list.Add(new ExternalUrl
 | 
					                list.Add(new ExternalUrl
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    Name = "Trakt",
 | 
					                    Name = "Trakt",
 | 
				
			||||||
                    Url = string.Format("https://trakt.tv/shows/{0}", imdbId)
 | 
					                    Url = string.Format(CultureInfo.InvariantCulture, "https://trakt.tv/shows/{0}", imdbId)
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using System.Text.Json.Serialization;
 | 
					using System.Text.Json.Serialization;
 | 
				
			||||||
using Jellyfin.Data.Enums;
 | 
					using Jellyfin.Data.Enums;
 | 
				
			||||||
using MediaBrowser.Controller.Providers;
 | 
					using MediaBrowser.Controller.Providers;
 | 
				
			||||||
@ -86,7 +87,7 @@ namespace MediaBrowser.Controller.Entities
 | 
				
			|||||||
                list.Add(new ExternalUrl
 | 
					                list.Add(new ExternalUrl
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    Name = "Trakt",
 | 
					                    Name = "Trakt",
 | 
				
			||||||
                    Url = string.Format("https://trakt.tv/movies/{0}", imdbId)
 | 
					                    Url = string.Format(CultureInfo.InvariantCulture, "https://trakt.tv/movies/{0}", imdbId)
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Diagnostics;
 | 
					using System.Diagnostics;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					using Microsoft.Extensions.Logging;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MediaBrowser.Controller.Library
 | 
					namespace MediaBrowser.Controller.Library
 | 
				
			||||||
@ -13,6 +14,7 @@ namespace MediaBrowser.Controller.Library
 | 
				
			|||||||
        /// The name.
 | 
					        /// The name.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        readonly string _name;
 | 
					        readonly string _name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// The stopwatch.
 | 
					        /// The stopwatch.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
@ -44,6 +46,7 @@ namespace MediaBrowser.Controller.Library
 | 
				
			|||||||
        public void Dispose()
 | 
					        public void Dispose()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Dispose(true);
 | 
					            Dispose(true);
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
@ -58,13 +61,19 @@ namespace MediaBrowser.Controller.Library
 | 
				
			|||||||
                string message;
 | 
					                string message;
 | 
				
			||||||
                if (_stopwatch.ElapsedMilliseconds > 300000)
 | 
					                if (_stopwatch.ElapsedMilliseconds > 300000)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    message = string.Format("{0} took {1} minutes.",
 | 
					                    message = string.Format(
 | 
				
			||||||
                        _name, ((float)_stopwatch.ElapsedMilliseconds / 60000).ToString("F"));
 | 
					                        CultureInfo.InvariantCulture,
 | 
				
			||||||
 | 
					                        "{0} took {1} minutes.",
 | 
				
			||||||
 | 
					                        _name,
 | 
				
			||||||
 | 
					                        ((float)_stopwatch.ElapsedMilliseconds / 60000).ToString("F", CultureInfo.InvariantCulture));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    message = string.Format("{0} took {1} seconds.",
 | 
					                    message = string.Format(
 | 
				
			||||||
                        _name, ((float)_stopwatch.ElapsedMilliseconds / 1000).ToString("#0.000"));
 | 
					                        CultureInfo.InvariantCulture,
 | 
				
			||||||
 | 
					                        "{0} took {1} seconds.",
 | 
				
			||||||
 | 
					                        _name,
 | 
				
			||||||
 | 
					                        ((float)_stopwatch.ElapsedMilliseconds / 1000).ToString("#0.000", CultureInfo.InvariantCulture));
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                _logger.LogInformation(message);
 | 
					                _logger.LogInformation(message);
 | 
				
			||||||
 | 
				
			|||||||
@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.LiveTv
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                if (double.TryParse(Number, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
 | 
					                if (double.TryParse(Number, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return string.Format("{0:00000.0}", number) + "-" + (Name ?? string.Empty);
 | 
					                    return string.Format(CultureInfo.InvariantCulture, "{0:00000.0}", number) + "-" + (Name ?? string.Empty);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -261,7 +261,7 @@ namespace MediaBrowser.Controller.LiveTv
 | 
				
			|||||||
                    list.Add(new ExternalUrl
 | 
					                    list.Add(new ExternalUrl
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        Name = "Trakt",
 | 
					                        Name = "Trakt",
 | 
				
			||||||
                        Url = string.Format("https://trakt.tv/movies/{0}", imdbId)
 | 
					                        Url = string.Format(CultureInfo.InvariantCulture, "https://trakt.tv/movies/{0}", imdbId)
 | 
				
			||||||
                    });
 | 
					                    });
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -675,7 +675,7 @@ namespace MediaBrowser.Controller.MediaEncoding
 | 
				
			|||||||
            //     }
 | 
					            //     }
 | 
				
			||||||
            // }
 | 
					            // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // fallbackFontParam = string.Format(":force_style='FontName=Droid Sans Fallback':fontsdir='{0}'", _mediaEncoder.EscapeSubtitleFilterPath(_fileSystem.GetDirectoryName(fallbackFontPath)));
 | 
					            // fallbackFontParam = string.Format(CultureInfo.InvariantCulture, ":force_style='FontName=Droid Sans Fallback':fontsdir='{0}'", _mediaEncoder.EscapeSubtitleFilterPath(_fileSystem.GetDirectoryName(fallbackFontPath)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (state.SubtitleStream.IsExternal)
 | 
					            if (state.SubtitleStream.IsExternal)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -880,7 +880,7 @@ namespace MediaBrowser.Controller.MediaEncoding
 | 
				
			|||||||
                profileScore = Math.Min(profileScore, 2);
 | 
					                profileScore = Math.Min(profileScore, 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // http://www.webmproject.org/docs/encoder-parameters/
 | 
					                // http://www.webmproject.org/docs/encoder-parameters/
 | 
				
			||||||
                param += string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
 | 
					                param += string.Format(CultureInfo.InvariantCulture, "-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
 | 
				
			||||||
                    profileScore.ToString(_usCulture),
 | 
					                    profileScore.ToString(_usCulture),
 | 
				
			||||||
                    crf,
 | 
					                    crf,
 | 
				
			||||||
                    qmin,
 | 
					                    qmin,
 | 
				
			||||||
@ -904,7 +904,7 @@ namespace MediaBrowser.Controller.MediaEncoding
 | 
				
			|||||||
            var framerate = GetFramerateParam(state);
 | 
					            var framerate = GetFramerateParam(state);
 | 
				
			||||||
            if (framerate.HasValue)
 | 
					            if (framerate.HasValue)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                param += string.Format(" -r {0}", framerate.Value.ToString(_usCulture));
 | 
					                param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(_usCulture));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var targetVideoCodec = state.ActualOutputVideoCodec;
 | 
					            var targetVideoCodec = state.ActualOutputVideoCodec;
 | 
				
			||||||
@ -1484,7 +1484,7 @@ namespace MediaBrowser.Controller.MediaEncoding
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (time > 0)
 | 
					            if (time > 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return string.Format("-ss {0}", _mediaEncoder.GetTimeParameter(time));
 | 
					                return string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(time));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return string.Empty;
 | 
					            return string.Empty;
 | 
				
			||||||
 | 
				
			|||||||
@ -72,7 +72,7 @@ namespace MediaBrowser.Controller.Providers
 | 
				
			|||||||
        /// <returns>Task.</returns>
 | 
					        /// <returns>Task.</returns>
 | 
				
			||||||
        Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken);
 | 
					        Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Task SaveImage(User user, Stream source, string mimeType, string path);
 | 
					        Task SaveImage(Stream source, string mimeType, string path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Adds the metadata providers.
 | 
					        /// Adds the metadata providers.
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
#pragma warning disable CS1591
 | 
					#pragma warning disable CS1591
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using MediaBrowser.Model.MediaInfo;
 | 
					using MediaBrowser.Model.MediaInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -14,7 +15,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                var url = inputFiles[0];
 | 
					                var url = inputFiles[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return string.Format("\"{0}\"", url);
 | 
					                return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", url);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return GetConcatInputArgument(inputFiles);
 | 
					            return GetConcatInputArgument(inputFiles);
 | 
				
			||||||
@ -33,7 +34,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                var files = string.Join("|", inputFiles.Select(NormalizePath));
 | 
					                var files = string.Join("|", inputFiles.Select(NormalizePath));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return string.Format("concat:\"{0}\"", files);
 | 
					                return string.Format(CultureInfo.InvariantCulture, "concat:\"{0}\"", files);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Determine the input path for video files
 | 
					            // Determine the input path for video files
 | 
				
			||||||
@ -49,13 +50,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            if (path.IndexOf("://") != -1)
 | 
					            if (path.IndexOf("://") != -1)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return string.Format("\"{0}\"", path);
 | 
					                return string.Format(CultureInfo.InvariantCulture, "\"{0}\"", path);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Quotes are valid path characters in linux and they need to be escaped here with a leading \
 | 
					            // Quotes are valid path characters in linux and they need to be escaped here with a leading \
 | 
				
			||||||
            path = NormalizePath(path);
 | 
					            path = NormalizePath(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return string.Format("file:\"{0}\"", path);
 | 
					            return string.Format(CultureInfo.InvariantCulture, "file:\"{0}\"", path);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
 | 
				
			|||||||
@ -552,8 +552,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
 | 
					            // Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
 | 
				
			||||||
            var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty;
 | 
					            var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var args = useIFrame ? string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) :
 | 
					            var args = useIFrame ? string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}{4}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail) :
 | 
				
			||||||
                string.Format("-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
 | 
					                string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads 0 -v quiet -vframes 1 -vf \"{2}\" -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
 | 
					            var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
 | 
				
			||||||
            var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
 | 
					            var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
 | 
				
			||||||
@ -570,7 +570,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (offset.HasValue)
 | 
					            if (offset.HasValue)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args;
 | 
					                args = string.Format(CultureInfo.InvariantCulture, "-ss {0} ", GetTimeParameter(offset.Value)) + args;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (videoStream != null)
 | 
					            if (videoStream != null)
 | 
				
			||||||
@ -641,7 +641,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                if (exitCode == -1 || !file.Exists || file.Length == 0)
 | 
					                if (exitCode == -1 || !file.Exists || file.Length == 0)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath);
 | 
					                    var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    _logger.LogError(msg);
 | 
					                    _logger.LogError(msg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -684,13 +684,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                var maxWidthParam = maxWidth.Value.ToString(_usCulture);
 | 
					                var maxWidthParam = maxWidth.Value.ToString(_usCulture);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
 | 
					                vf += string.Format(CultureInfo.InvariantCulture, ",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Directory.CreateDirectory(targetDirectory);
 | 
					            Directory.CreateDirectory(targetDirectory);
 | 
				
			||||||
            var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
 | 
					            var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var args = string.Format("-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
 | 
					            var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads 0 -v quiet -vf \"{2}\" -f image2 \"{1}\"", inputArgument, outputPath, vf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
 | 
					            var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
 | 
				
			||||||
            var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
 | 
					            var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
 | 
				
			||||||
@ -790,7 +790,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                if (exitCode == -1)
 | 
					                if (exitCode == -1)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument);
 | 
					                    var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputArgument);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    _logger.LogError(msg);
 | 
					                    _logger.LogError(msg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -435,7 +435,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
 | 
				
			|||||||
                        CreateNoWindow = true,
 | 
					                        CreateNoWindow = true,
 | 
				
			||||||
                        UseShellExecute = false,
 | 
					                        UseShellExecute = false,
 | 
				
			||||||
                        FileName = _mediaEncoder.EncoderPath,
 | 
					                        FileName = _mediaEncoder.EncoderPath,
 | 
				
			||||||
                        Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
 | 
					                        Arguments = string.Format(CultureInfo.InvariantCulture, "{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
 | 
				
			||||||
                        WindowStyle = ProcessWindowStyle.Hidden,
 | 
					                        WindowStyle = ProcessWindowStyle.Hidden,
 | 
				
			||||||
                        ErrorDialog = false
 | 
					                        ErrorDialog = false
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
 | 
				
			|||||||
@ -157,7 +157,7 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
            //    flagValue = flagValue | DlnaFlags.TimeBasedSeek;
 | 
					            //    flagValue = flagValue | DlnaFlags.TimeBasedSeek;
 | 
				
			||||||
            //}
 | 
					            //}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}",
 | 
					            string dlnaflags = string.Format(CultureInfo.InvariantCulture, ";DLNA.ORG_FLAGS={0}",
 | 
				
			||||||
             DlnaMaps.FlagsToString(flagValue));
 | 
					             DlnaMaps.FlagsToString(flagValue));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ResponseProfile mediaProfile = _profile.GetVideoMediaProfile(container,
 | 
					            ResponseProfile mediaProfile = _profile.GetVideoMediaProfile(container,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,18 +1,20 @@
 | 
				
			|||||||
#pragma warning disable CS1591
 | 
					#pragma warning disable CS1591
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MediaBrowser.Model.Dlna
 | 
					namespace MediaBrowser.Model.Dlna
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public static class DlnaMaps
 | 
					    public static class DlnaMaps
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private static readonly string DefaultStreaming =
 | 
					        private static readonly string DefaultStreaming =
 | 
				
			||||||
             FlagsToString(DlnaFlags.StreamingTransferMode |
 | 
					            FlagsToString(DlnaFlags.StreamingTransferMode |
 | 
				
			||||||
                           DlnaFlags.BackgroundTransferMode |
 | 
					                           DlnaFlags.BackgroundTransferMode |
 | 
				
			||||||
                           DlnaFlags.ConnectionStall |
 | 
					                           DlnaFlags.ConnectionStall |
 | 
				
			||||||
                           DlnaFlags.ByteBasedSeek |
 | 
					                           DlnaFlags.ByteBasedSeek |
 | 
				
			||||||
                           DlnaFlags.DlnaV15);
 | 
					                           DlnaFlags.DlnaV15);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static readonly string DefaultInteractive =
 | 
					        private static readonly string DefaultInteractive =
 | 
				
			||||||
          FlagsToString(DlnaFlags.InteractiveTransferMode |
 | 
					            FlagsToString(DlnaFlags.InteractiveTransferMode |
 | 
				
			||||||
                        DlnaFlags.BackgroundTransferMode |
 | 
					                        DlnaFlags.BackgroundTransferMode |
 | 
				
			||||||
                        DlnaFlags.ConnectionStall |
 | 
					                        DlnaFlags.ConnectionStall |
 | 
				
			||||||
                        DlnaFlags.ByteBasedSeek |
 | 
					                        DlnaFlags.ByteBasedSeek |
 | 
				
			||||||
@ -20,7 +22,7 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        public static string FlagsToString(DlnaFlags flags)
 | 
					        public static string FlagsToString(DlnaFlags flags)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return string.Format("{0:X8}{1:D24}", (ulong)flags, 0);
 | 
					            return string.Format(CultureInfo.InvariantCulture, "{0:X8}{1:D24}", (ulong)flags, 0);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static string GetOrgOpValue(bool hasKnownRuntime, bool isDirectStream, TranscodeSeekInfo profileTranscodeSeekInfo)
 | 
					        public static string GetOrgOpValue(bool hasKnownRuntime, bool isDirectStream, TranscodeSeekInfo profileTranscodeSeekInfo)
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using MediaBrowser.Model.MediaInfo;
 | 
					using MediaBrowser.Model.MediaInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -142,26 +143,26 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
                {
 | 
					                {
 | 
				
			||||||
                    if (timestampType == TransportStreamTimestamp.None)
 | 
					                    if (timestampType == TransportStreamTimestamp.None)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_HP_{0}D_MPEG1_L2_ISO", resolution)) };
 | 
					                        return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_HP_{0}D_MPEG1_L2_ISO", resolution)) };
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_HP_{0}D_MPEG1_L2_T", resolution)) };
 | 
					                    return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_HP_{0}D_MPEG1_L2_T", resolution)) };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
 | 
					                if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_MP_{0}D_AAC_MULT5{1}", resolution, suffix)) };
 | 
					                    return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_MP_{0}D_AAC_MULT5{1}", resolution, suffix)) };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
 | 
					                if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_MP_{0}D_MPEG1_L3{1}", resolution, suffix)) };
 | 
					                    return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_MP_{0}D_MPEG1_L3{1}", resolution, suffix)) };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (string.IsNullOrEmpty(audioCodec) ||
 | 
					                if (string.IsNullOrEmpty(audioCodec) ||
 | 
				
			||||||
                    string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
 | 
					                    string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return new MediaFormatProfile[] { ValueOf(string.Format("AVC_TS_MP_{0}D_AC3{1}", resolution, suffix)) };
 | 
					                    return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "AVC_TS_MP_{0}D_AC3{1}", resolution, suffix)) };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (string.Equals(videoCodec, "vc1", StringComparison.OrdinalIgnoreCase))
 | 
					            else if (string.Equals(videoCodec, "vc1", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
@ -180,29 +181,29 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
                {
 | 
					                {
 | 
				
			||||||
                    suffix = string.Equals(suffix, "_ISO", StringComparison.OrdinalIgnoreCase) ? suffix : "_T";
 | 
					                    suffix = string.Equals(suffix, "_ISO", StringComparison.OrdinalIgnoreCase) ? suffix : "_T";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    return new MediaFormatProfile[] { ValueOf(string.Format("VC1_TS_HD_DTS{0}", suffix)) };
 | 
					                    return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "VC1_TS_HD_DTS{0}", suffix)) };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
 | 
					            else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
 | 
					                if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_AAC{0}", suffix)) };
 | 
					                    return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_AAC{0}", suffix)) };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
 | 
					                if (string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_MPEG1_L3{0}", suffix)) };
 | 
					                    return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_MPEG1_L3{0}", suffix)) };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (string.Equals(audioCodec, "mp2", StringComparison.OrdinalIgnoreCase))
 | 
					                if (string.Equals(audioCodec, "mp2", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_MPEG2_L2{0}", suffix)) };
 | 
					                    return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_MPEG2_L2{0}", suffix)) };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
 | 
					                if (string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return new MediaFormatProfile[] { ValueOf(string.Format("MPEG4_P2_TS_ASP_AC3{0}", suffix)) };
 | 
					                    return new MediaFormatProfile[] { ValueOf(string.Format(CultureInfo.InvariantCulture, "MPEG4_P2_TS_ASP_AC3{0}", suffix)) };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -191,7 +191,7 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                var encodedValue = pair.Value.Replace(" ", "%20");
 | 
					                var encodedValue = pair.Value.Replace(" ", "%20");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                list.Add(string.Format("{0}={1}", pair.Name, encodedValue));
 | 
					                list.Add(string.Format(CultureInfo.InvariantCulture, "{0}={1}", pair.Name, encodedValue));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            string queryString = string.Join("&", list.ToArray());
 | 
					            string queryString = string.Join("&", list.ToArray());
 | 
				
			||||||
@ -214,18 +214,18 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
 | 
					                if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return string.Format("{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
 | 
					                    return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return string.Format("{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
 | 
					                return string.Format(CultureInfo.InvariantCulture, "{0}/audio/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
 | 
					            if (string.Equals(SubProtocol, "hls", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return string.Format("{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
 | 
					                return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/master.m3u8?{2}", baseUrl, ItemId, queryString);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return string.Format("{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
 | 
					            return string.Format(CultureInfo.InvariantCulture, "{0}/videos/{1}/stream{2}?{3}", baseUrl, ItemId, extension, queryString);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static List<NameValuePair> BuildParams(StreamInfo item, string accessToken)
 | 
					        private static List<NameValuePair> BuildParams(StreamInfo item, string accessToken)
 | 
				
			||||||
@ -457,7 +457,7 @@ namespace MediaBrowser.Model.Dlna
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
 | 
					                if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    info.Url = string.Format("{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
 | 
					                    info.Url = string.Format(CultureInfo.InvariantCulture, "{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
 | 
				
			||||||
                        baseUrl,
 | 
					                        baseUrl,
 | 
				
			||||||
                        ItemId,
 | 
					                        ItemId,
 | 
				
			||||||
                        MediaSourceId,
 | 
					                        MediaSourceId,
 | 
				
			||||||
 | 
				
			|||||||
@ -187,7 +187,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task SaveImage(User user, Stream source, string path)
 | 
					        public async Task SaveImage(Stream source, string path)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            await SaveImageToLocation(source, path, path, CancellationToken.None).ConfigureAwait(false);
 | 
					            await SaveImageToLocation(source, path, path, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -355,7 +355,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(extension))
 | 
					            if (string.IsNullOrWhiteSpace(extension))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                throw new ArgumentException(string.Format("Unable to determine image file extension from mime type {0}", mimeType));
 | 
					                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Unable to determine image file extension from mime type {0}", mimeType));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (type == ImageType.Thumb && saveLocally)
 | 
					            if (type == ImageType.Thumb && saveLocally)
 | 
				
			||||||
 | 
				
			|||||||
@ -54,7 +54,12 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
            return hasChanges;
 | 
					            return hasChanges;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task<RefreshResult> RefreshImages(BaseItem item, LibraryOptions libraryOptions, List<IImageProvider> providers, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken)
 | 
					        public async Task<RefreshResult> RefreshImages(
 | 
				
			||||||
 | 
					            BaseItem item,
 | 
				
			||||||
 | 
					            LibraryOptions libraryOptions,
 | 
				
			||||||
 | 
					            List<IImageProvider> providers,
 | 
				
			||||||
 | 
					            ImageRefreshOptions refreshOptions,
 | 
				
			||||||
 | 
					            CancellationToken cancellationToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
 | 
					            if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -78,19 +83,15 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            foreach (var provider in providers)
 | 
					            foreach (var provider in providers)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var remoteProvider = provider as IRemoteImageProvider;
 | 
					                if (provider is IRemoteImageProvider remoteProvider)
 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (remoteProvider != null)
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    await RefreshFromProvider(item, libraryOptions, remoteProvider, refreshOptions, typeOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
 | 
					                    await RefreshFromProvider(item, libraryOptions, remoteProvider, refreshOptions, typeOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                var dynamicImageProvider = provider as IDynamicImageProvider;
 | 
					                if (provider is IDynamicImageProvider dynamicImageProvider)
 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (dynamicImageProvider != null)
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    await RefreshFromProvider(item, dynamicImageProvider, refreshOptions, typeOptions, libraryOptions, downloadedImages, result, cancellationToken).ConfigureAwait(false);
 | 
					                    await RefreshFromProvider(item, dynamicImageProvider, refreshOptions, typeOptions, downloadedImages, result, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -100,11 +101,11 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Refreshes from provider.
 | 
					        /// Refreshes from provider.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        private async Task RefreshFromProvider(BaseItem item,
 | 
					        private async Task RefreshFromProvider(
 | 
				
			||||||
 | 
					            BaseItem item,
 | 
				
			||||||
            IDynamicImageProvider provider,
 | 
					            IDynamicImageProvider provider,
 | 
				
			||||||
            ImageRefreshOptions refreshOptions,
 | 
					            ImageRefreshOptions refreshOptions,
 | 
				
			||||||
            TypeOptions savedOptions,
 | 
					            TypeOptions savedOptions,
 | 
				
			||||||
            LibraryOptions libraryOptions,
 | 
					 | 
				
			||||||
            ICollection<ImageType> downloadedImages,
 | 
					            ICollection<ImageType> downloadedImages,
 | 
				
			||||||
            RefreshResult result,
 | 
					            RefreshResult result,
 | 
				
			||||||
            CancellationToken cancellationToken)
 | 
					            CancellationToken cancellationToken)
 | 
				
			||||||
@ -115,7 +116,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                foreach (var imageType in images)
 | 
					                foreach (var imageType in images)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    if (!IsEnabled(savedOptions, imageType, item))
 | 
					                    if (!IsEnabled(savedOptions, imageType))
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        continue;
 | 
					                        continue;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@ -133,12 +134,13 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
                                if (response.Protocol == MediaProtocol.Http)
 | 
					                                if (response.Protocol == MediaProtocol.Http)
 | 
				
			||||||
                                {
 | 
					                                {
 | 
				
			||||||
                                    _logger.LogDebug("Setting image url into item {0}", item.Id);
 | 
					                                    _logger.LogDebug("Setting image url into item {0}", item.Id);
 | 
				
			||||||
                                    item.SetImage(new ItemImageInfo
 | 
					                                    item.SetImage(
 | 
				
			||||||
                                    {
 | 
					                                        new ItemImageInfo
 | 
				
			||||||
                                        Path = response.Path,
 | 
					                                        {
 | 
				
			||||||
                                        Type = imageType
 | 
					                                            Path = response.Path,
 | 
				
			||||||
 | 
					                                            Type = imageType
 | 
				
			||||||
                                    }, 0);
 | 
					                                        },
 | 
				
			||||||
 | 
					                                        0);
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                                else
 | 
					                                else
 | 
				
			||||||
                                {
 | 
					                                {
 | 
				
			||||||
@ -157,7 +159,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            downloadedImages.Add(imageType);
 | 
					                            downloadedImages.Add(imageType);
 | 
				
			||||||
                            result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
 | 
					                            result.UpdateType |= ItemUpdateType.ImageUpdate;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -279,7 +281,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                foreach (var imageType in _singularImages)
 | 
					                foreach (var imageType in _singularImages)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    if (!IsEnabled(savedOptions, imageType, item))
 | 
					                    if (!IsEnabled(savedOptions, imageType))
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        continue;
 | 
					                        continue;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
@ -299,8 +301,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
                minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
 | 
					                minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
 | 
				
			||||||
                await DownloadBackdrops(item, libraryOptions, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
 | 
					                await DownloadBackdrops(item, libraryOptions, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                var hasScreenshots = item as IHasScreenshots;
 | 
					                if (item is IHasScreenshots hasScreenshots)
 | 
				
			||||||
                if (hasScreenshots != null)
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    minWidth = savedOptions.GetMinWidth(ImageType.Screenshot);
 | 
					                    minWidth = savedOptions.GetMinWidth(ImageType.Screenshot);
 | 
				
			||||||
                    await DownloadBackdrops(item, libraryOptions, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
 | 
					                    await DownloadBackdrops(item, libraryOptions, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
@ -317,7 +318,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private bool IsEnabled(TypeOptions options, ImageType type, BaseItem item)
 | 
					        private bool IsEnabled(TypeOptions options, ImageType type)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return options.IsEnabled(type);
 | 
					            return options.IsEnabled(type);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -452,10 +453,10 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
                .Where(i => i.Type == type && !(i.Width.HasValue && i.Width.Value < minWidth))
 | 
					                .Where(i => i.Type == type && !(i.Width.HasValue && i.Width.Value < minWidth))
 | 
				
			||||||
                .ToList();
 | 
					                .ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (EnableImageStub(item, type, libraryOptions) && eligibleImages.Count > 0)
 | 
					            if (EnableImageStub(item, libraryOptions) && eligibleImages.Count > 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                SaveImageStub(item, type, eligibleImages.Select(i => i.Url));
 | 
					                SaveImageStub(item, type, eligibleImages.Select(i => i.Url));
 | 
				
			||||||
                result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
 | 
					                result.UpdateType |= ItemUpdateType.ImageUpdate;
 | 
				
			||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -476,7 +477,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
                        null,
 | 
					                        null,
 | 
				
			||||||
                        cancellationToken).ConfigureAwait(false);
 | 
					                        cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
 | 
					                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 | 
				
			||||||
                    return true;
 | 
					                    return true;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                catch (HttpException ex)
 | 
					                catch (HttpException ex)
 | 
				
			||||||
@ -495,7 +496,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private bool EnableImageStub(BaseItem item, ImageType type, LibraryOptions libraryOptions)
 | 
					        private bool EnableImageStub(BaseItem item, LibraryOptions libraryOptions)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (item is LiveTvProgram)
 | 
					            if (item is LiveTvProgram)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@ -563,10 +564,10 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                var url = image.Url;
 | 
					                var url = image.Url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (EnableImageStub(item, imageType, libraryOptions))
 | 
					                if (EnableImageStub(item, libraryOptions))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    SaveImageStub(item, imageType, new[] { url });
 | 
					                    SaveImageStub(item, imageType, new[] { url });
 | 
				
			||||||
                    result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
 | 
					                    result.UpdateType |= ItemUpdateType.ImageUpdate;
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -52,7 +52,6 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
        public async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
 | 
					        public async Task<ItemUpdateType> RefreshMetadata(BaseItem item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var itemOfType = (TItemType)item;
 | 
					            var itemOfType = (TItemType)item;
 | 
				
			||||||
            var config = ProviderManager.GetMetadataOptions(item);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var updateType = ItemUpdateType.None;
 | 
					            var updateType = ItemUpdateType.None;
 | 
				
			||||||
            var requiresRefresh = false;
 | 
					            var requiresRefresh = false;
 | 
				
			||||||
@ -86,7 +85,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
                // Always validate images and check for new locally stored ones.
 | 
					                // Always validate images and check for new locally stored ones.
 | 
				
			||||||
                if (itemImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions.DirectoryService))
 | 
					                if (itemImageProvider.ValidateImages(item, allImageProviders.OfType<ILocalImageProvider>(), refreshOptions.DirectoryService))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    updateType = updateType | ItemUpdateType.ImageUpdate;
 | 
					                    updateType |= ItemUpdateType.ImageUpdate;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (Exception ex)
 | 
					            catch (Exception ex)
 | 
				
			||||||
@ -102,7 +101,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            bool hasRefreshedMetadata = true;
 | 
					            bool hasRefreshedMetadata = true;
 | 
				
			||||||
            bool hasRefreshedImages = true;
 | 
					            bool hasRefreshedImages = true;
 | 
				
			||||||
            var isFirstRefresh = item.DateLastRefreshed == default(DateTime);
 | 
					            var isFirstRefresh = item.DateLastRefreshed == default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Next run metadata providers
 | 
					            // Next run metadata providers
 | 
				
			||||||
            if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
 | 
					            if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
 | 
				
			||||||
@ -114,7 +113,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
                {
 | 
					                {
 | 
				
			||||||
                    if (item.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata))
 | 
					                    if (item.BeforeMetadataRefresh(refreshOptions.ReplaceAllMetadata))
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        updateType = updateType | ItemUpdateType.MetadataImport;
 | 
					                        updateType |= ItemUpdateType.MetadataImport;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -132,7 +131,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false);
 | 
					                    var result = await RefreshWithProviders(metadataResult, id, refreshOptions, providers, itemImageProvider, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    updateType = updateType | result.UpdateType;
 | 
					                    updateType |= result.UpdateType;
 | 
				
			||||||
                    if (result.Failures > 0)
 | 
					                    if (result.Failures > 0)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        hasRefreshedMetadata = false;
 | 
					                        hasRefreshedMetadata = false;
 | 
				
			||||||
@ -147,9 +146,9 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                if (providers.Count > 0)
 | 
					                if (providers.Count > 0)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    var result = await itemImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false);
 | 
					                    var result = await itemImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    updateType = updateType | result.UpdateType;
 | 
					                    updateType |= result.UpdateType;
 | 
				
			||||||
                    if (result.Failures > 0)
 | 
					                    if (result.Failures > 0)
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        hasRefreshedImages = false;
 | 
					                        hasRefreshedImages = false;
 | 
				
			||||||
@ -158,7 +157,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType);
 | 
					            var beforeSaveResult = BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh || refreshOptions.ForceSave, updateType);
 | 
				
			||||||
            updateType = updateType | beforeSaveResult;
 | 
					            updateType |= beforeSaveResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Save if changes were made, or it's never been saved before
 | 
					            // Save if changes were made, or it's never been saved before
 | 
				
			||||||
            if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh)
 | 
					            if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh)
 | 
				
			||||||
@ -175,7 +174,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
                // If any of these properties are set then make sure the updateType is not None, just to force everything to save
 | 
					                // If any of these properties are set then make sure the updateType is not None, just to force everything to save
 | 
				
			||||||
                if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
 | 
					                if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    updateType = updateType | ItemUpdateType.MetadataDownload;
 | 
					                    updateType |= ItemUpdateType.MetadataDownload;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (hasRefreshedMetadata && hasRefreshedImages)
 | 
					                if (hasRefreshedMetadata && hasRefreshedImages)
 | 
				
			||||||
@ -184,11 +183,11 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    item.DateLastRefreshed = default(DateTime);
 | 
					                    item.DateLastRefreshed = default;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Save to database
 | 
					                // Save to database
 | 
				
			||||||
                SaveItem(metadataResult, libraryOptions, updateType, cancellationToken);
 | 
					                await SaveItemAsync(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
 | 
					            await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
@ -203,26 +202,26 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
            lookupInfo.Year = result.ProductionYear;
 | 
					            lookupInfo.Year = result.ProductionYear;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected void SaveItem(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken)
 | 
					        protected async Task SaveItemAsync(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (result.Item.SupportsPeople && result.People != null)
 | 
					            if (result.Item.SupportsPeople && result.People != null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var baseItem = result.Item;
 | 
					                var baseItem = result.Item;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                LibraryManager.UpdatePeople(baseItem, result.People);
 | 
					                LibraryManager.UpdatePeople(baseItem, result.People);
 | 
				
			||||||
                SavePeopleMetadata(result.People, libraryOptions, cancellationToken);
 | 
					                await SavePeopleMetadataAsync(result.People, libraryOptions, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            result.Item.UpdateToRepository(reason, cancellationToken);
 | 
					            result.Item.UpdateToRepository(reason, cancellationToken);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void SavePeopleMetadata(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken)
 | 
					        private async Task SavePeopleMetadataAsync(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            foreach (var person in people)
 | 
					            foreach (var person in people)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                cancellationToken.ThrowIfCancellationRequested();
 | 
					                cancellationToken.ThrowIfCancellationRequested();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (person.ProviderIds.Any() || !string.IsNullOrWhiteSpace(person.ImageUrl))
 | 
					                if (person.ProviderIds.Count > 0 || !string.IsNullOrWhiteSpace(person.ImageUrl))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    var updateType = ItemUpdateType.MetadataDownload;
 | 
					                    var updateType = ItemUpdateType.MetadataDownload;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -239,10 +238,10 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
 | 
					                    if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        AddPersonImage(personEntity, libraryOptions, person.ImageUrl, cancellationToken);
 | 
					                        await AddPersonImageAsync(personEntity, libraryOptions, person.ImageUrl, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        saveEntity = true;
 | 
					                        saveEntity = true;
 | 
				
			||||||
                        updateType = updateType | ItemUpdateType.ImageUpdate;
 | 
					                        updateType |= ItemUpdateType.ImageUpdate;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (saveEntity)
 | 
					                    if (saveEntity)
 | 
				
			||||||
@ -253,26 +252,28 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void AddPersonImage(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken)
 | 
					        private async Task AddPersonImageAsync(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // if (libraryOptions.DownloadImagesInAdvance)
 | 
					            if (libraryOptions.DownloadImagesInAdvance)
 | 
				
			||||||
            //{
 | 
					 | 
				
			||||||
            //    try
 | 
					 | 
				
			||||||
            //    {
 | 
					 | 
				
			||||||
            //        await ProviderManager.SaveImage(personEntity, imageUrl, ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
 | 
					 | 
				
			||||||
            //        return;
 | 
					 | 
				
			||||||
            //    }
 | 
					 | 
				
			||||||
            //    catch (Exception ex)
 | 
					 | 
				
			||||||
            //    {
 | 
					 | 
				
			||||||
            //        Logger.LogError(ex, "Error in AddPersonImage");
 | 
					 | 
				
			||||||
            //    }
 | 
					 | 
				
			||||||
            //}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            personEntity.SetImage(new ItemImageInfo
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Path = imageUrl,
 | 
					                try
 | 
				
			||||||
                Type = ImageType.Primary
 | 
					                {
 | 
				
			||||||
            }, 0);
 | 
					                    await ProviderManager.SaveImage(personEntity, imageUrl, ImageType.Primary, null, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch (Exception ex)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Logger.LogError(ex, "Error in AddPersonImage");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            personEntity.SetImage(
 | 
				
			||||||
 | 
					                new ItemImageInfo
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Path = imageUrl,
 | 
				
			||||||
 | 
					                    Type = ImageType.Primary
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                0);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
 | 
					        protected virtual Task AfterMetadataRefresh(TItemType item, MetadataRefreshOptions refreshOptions, CancellationToken cancellationToken)
 | 
				
			||||||
 | 
				
			|||||||
@ -210,10 +210,10 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <inheritdoc/>
 | 
					        /// <inheritdoc/>
 | 
				
			||||||
        public Task SaveImage(User user, Stream source, string mimeType, string path)
 | 
					        public Task SaveImage(Stream source, string mimeType, string path)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 | 
					            return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
 | 
				
			||||||
                .SaveImage(user, source, path);
 | 
					                .SaveImage(source, path);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <inheritdoc/>
 | 
					        /// <inheritdoc/>
 | 
				
			||||||
@ -563,7 +563,7 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
            var pluginList = summary.Plugins.ToList();
 | 
					            var pluginList = summary.Plugins.ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            AddMetadataPlugins(pluginList, dummy, libraryOptions, options);
 | 
					            AddMetadataPlugins(pluginList, dummy, libraryOptions, options);
 | 
				
			||||||
            AddImagePlugins(pluginList, dummy, imageProviders);
 | 
					            AddImagePlugins(pluginList, imageProviders);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var subtitleProviders = _subtitleManager.GetSupportedProviders(dummy);
 | 
					            var subtitleProviders = _subtitleManager.GetSupportedProviders(dummy);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -594,14 +594,14 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
            var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true).ToList();
 | 
					            var providers = GetMetadataProvidersInternal<T>(item, libraryOptions, options, true, true).ToList();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Locals
 | 
					            // Locals
 | 
				
			||||||
            list.AddRange(providers.Where(i => (i is ILocalMetadataProvider)).Select(i => new MetadataPlugin
 | 
					            list.AddRange(providers.Where(i => i is ILocalMetadataProvider).Select(i => new MetadataPlugin
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Name = i.Name,
 | 
					                Name = i.Name,
 | 
				
			||||||
                Type = MetadataPluginType.LocalMetadataProvider
 | 
					                Type = MetadataPluginType.LocalMetadataProvider
 | 
				
			||||||
            }));
 | 
					            }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Fetchers
 | 
					            // Fetchers
 | 
				
			||||||
            list.AddRange(providers.Where(i => (i is IRemoteMetadataProvider)).Select(i => new MetadataPlugin
 | 
					            list.AddRange(providers.Where(i => i is IRemoteMetadataProvider).Select(i => new MetadataPlugin
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Name = i.Name,
 | 
					                Name = i.Name,
 | 
				
			||||||
                Type = MetadataPluginType.MetadataFetcher
 | 
					                Type = MetadataPluginType.MetadataFetcher
 | 
				
			||||||
@ -615,11 +615,10 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
            }));
 | 
					            }));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void AddImagePlugins<T>(List<MetadataPlugin> list, T item, List<IImageProvider> imageProviders)
 | 
					        private void AddImagePlugins(List<MetadataPlugin> list, List<IImageProvider> imageProviders)
 | 
				
			||||||
            where T : BaseItem
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // Locals
 | 
					            // Locals
 | 
				
			||||||
            list.AddRange(imageProviders.Where(i => (i is ILocalImageProvider)).Select(i => new MetadataPlugin
 | 
					            list.AddRange(imageProviders.Where(i => i is ILocalImageProvider).Select(i => new MetadataPlugin
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Name = i.Name,
 | 
					                Name = i.Name,
 | 
				
			||||||
                Type = MetadataPluginType.LocalImageProvider
 | 
					                Type = MetadataPluginType.LocalImageProvider
 | 
				
			||||||
@ -1166,12 +1165,32 @@ namespace MediaBrowser.Providers.Manager
 | 
				
			|||||||
        /// <inheritdoc/>
 | 
					        /// <inheritdoc/>
 | 
				
			||||||
        public void Dispose()
 | 
					        public void Dispose()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _disposed = true;
 | 
					            Dispose(true);
 | 
				
			||||||
 | 
					            GC.SuppressFinalize(this);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Releases unmanaged and optionally managed resources.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
 | 
				
			||||||
 | 
					        protected virtual void Dispose(bool disposing)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (_disposed)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!_disposeCancellationTokenSource.IsCancellationRequested)
 | 
					            if (!_disposeCancellationTokenSource.IsCancellationRequested)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                _disposeCancellationTokenSource.Cancel();
 | 
					                _disposeCancellationTokenSource.Cancel();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (disposing)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _disposeCancellationTokenSource.Dispose();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _disposed = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,11 +2,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.Globalization;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using MediaBrowser.Common.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
using MediaBrowser.Controller.Entities.Audio;
 | 
					using MediaBrowser.Controller.Entities.Audio;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
@ -17,7 +15,6 @@ using MediaBrowser.Model.Dlna;
 | 
				
			|||||||
using MediaBrowser.Model.Dto;
 | 
					using MediaBrowser.Model.Dto;
 | 
				
			||||||
using MediaBrowser.Model.Entities;
 | 
					using MediaBrowser.Model.Entities;
 | 
				
			||||||
using MediaBrowser.Model.MediaInfo;
 | 
					using MediaBrowser.Model.MediaInfo;
 | 
				
			||||||
using MediaBrowser.Model.Serialization;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace MediaBrowser.Providers.MediaInfo
 | 
					namespace MediaBrowser.Providers.MediaInfo
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -25,19 +22,17 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly IMediaEncoder _mediaEncoder;
 | 
					        private readonly IMediaEncoder _mediaEncoder;
 | 
				
			||||||
        private readonly IItemRepository _itemRepo;
 | 
					        private readonly IItemRepository _itemRepo;
 | 
				
			||||||
        private readonly IApplicationPaths _appPaths;
 | 
					 | 
				
			||||||
        private readonly IJsonSerializer _json;
 | 
					 | 
				
			||||||
        private readonly ILibraryManager _libraryManager;
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
        private readonly IMediaSourceManager _mediaSourceManager;
 | 
					        private readonly IMediaSourceManager _mediaSourceManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 | 
					        public FFProbeAudioInfo(
 | 
				
			||||||
 | 
					            IMediaSourceManager mediaSourceManager,
 | 
				
			||||||
        public FFProbeAudioInfo(IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IApplicationPaths appPaths, IJsonSerializer json, ILibraryManager libraryManager)
 | 
					            IMediaEncoder mediaEncoder,
 | 
				
			||||||
 | 
					            IItemRepository itemRepo,
 | 
				
			||||||
 | 
					            ILibraryManager libraryManager)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _mediaEncoder = mediaEncoder;
 | 
					            _mediaEncoder = mediaEncoder;
 | 
				
			||||||
            _itemRepo = itemRepo;
 | 
					            _itemRepo = itemRepo;
 | 
				
			||||||
            _appPaths = appPaths;
 | 
					 | 
				
			||||||
            _json = json;
 | 
					 | 
				
			||||||
            _libraryManager = libraryManager;
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
            _mediaSourceManager = mediaSourceManager;
 | 
					            _mediaSourceManager = mediaSourceManager;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -40,19 +40,15 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
				
			|||||||
        IHasItemChangeMonitor
 | 
					        IHasItemChangeMonitor
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        private readonly ILogger<FFProbeProvider> _logger;
 | 
					        private readonly ILogger<FFProbeProvider> _logger;
 | 
				
			||||||
        private readonly IIsoManager _isoManager;
 | 
					 | 
				
			||||||
        private readonly IMediaEncoder _mediaEncoder;
 | 
					        private readonly IMediaEncoder _mediaEncoder;
 | 
				
			||||||
        private readonly IItemRepository _itemRepo;
 | 
					        private readonly IItemRepository _itemRepo;
 | 
				
			||||||
        private readonly IBlurayExaminer _blurayExaminer;
 | 
					        private readonly IBlurayExaminer _blurayExaminer;
 | 
				
			||||||
        private readonly ILocalizationManager _localization;
 | 
					        private readonly ILocalizationManager _localization;
 | 
				
			||||||
        private readonly IApplicationPaths _appPaths;
 | 
					 | 
				
			||||||
        private readonly IJsonSerializer _json;
 | 
					 | 
				
			||||||
        private readonly IEncodingManager _encodingManager;
 | 
					        private readonly IEncodingManager _encodingManager;
 | 
				
			||||||
        private readonly IServerConfigurationManager _config;
 | 
					        private readonly IServerConfigurationManager _config;
 | 
				
			||||||
        private readonly ISubtitleManager _subtitleManager;
 | 
					        private readonly ISubtitleManager _subtitleManager;
 | 
				
			||||||
        private readonly IChapterManager _chapterManager;
 | 
					        private readonly IChapterManager _chapterManager;
 | 
				
			||||||
        private readonly ILibraryManager _libraryManager;
 | 
					        private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
        private readonly IChannelManager _channelManager;
 | 
					 | 
				
			||||||
        private readonly IMediaSourceManager _mediaSourceManager;
 | 
					        private readonly IMediaSourceManager _mediaSourceManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string Name => "ffprobe";
 | 
					        public string Name => "ffprobe";
 | 
				
			||||||
@ -126,14 +122,10 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
				
			|||||||
        public FFProbeProvider(
 | 
					        public FFProbeProvider(
 | 
				
			||||||
            ILogger<FFProbeProvider> logger,
 | 
					            ILogger<FFProbeProvider> logger,
 | 
				
			||||||
            IMediaSourceManager mediaSourceManager,
 | 
					            IMediaSourceManager mediaSourceManager,
 | 
				
			||||||
            IChannelManager channelManager,
 | 
					 | 
				
			||||||
            IIsoManager isoManager,
 | 
					 | 
				
			||||||
            IMediaEncoder mediaEncoder,
 | 
					            IMediaEncoder mediaEncoder,
 | 
				
			||||||
            IItemRepository itemRepo,
 | 
					            IItemRepository itemRepo,
 | 
				
			||||||
            IBlurayExaminer blurayExaminer,
 | 
					            IBlurayExaminer blurayExaminer,
 | 
				
			||||||
            ILocalizationManager localization,
 | 
					            ILocalizationManager localization,
 | 
				
			||||||
            IApplicationPaths appPaths,
 | 
					 | 
				
			||||||
            IJsonSerializer json,
 | 
					 | 
				
			||||||
            IEncodingManager encodingManager,
 | 
					            IEncodingManager encodingManager,
 | 
				
			||||||
            IServerConfigurationManager config,
 | 
					            IServerConfigurationManager config,
 | 
				
			||||||
            ISubtitleManager subtitleManager,
 | 
					            ISubtitleManager subtitleManager,
 | 
				
			||||||
@ -141,19 +133,15 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
				
			|||||||
            ILibraryManager libraryManager)
 | 
					            ILibraryManager libraryManager)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _logger = logger;
 | 
					            _logger = logger;
 | 
				
			||||||
            _isoManager = isoManager;
 | 
					 | 
				
			||||||
            _mediaEncoder = mediaEncoder;
 | 
					            _mediaEncoder = mediaEncoder;
 | 
				
			||||||
            _itemRepo = itemRepo;
 | 
					            _itemRepo = itemRepo;
 | 
				
			||||||
            _blurayExaminer = blurayExaminer;
 | 
					            _blurayExaminer = blurayExaminer;
 | 
				
			||||||
            _localization = localization;
 | 
					            _localization = localization;
 | 
				
			||||||
            _appPaths = appPaths;
 | 
					 | 
				
			||||||
            _json = json;
 | 
					 | 
				
			||||||
            _encodingManager = encodingManager;
 | 
					            _encodingManager = encodingManager;
 | 
				
			||||||
            _config = config;
 | 
					            _config = config;
 | 
				
			||||||
            _subtitleManager = subtitleManager;
 | 
					            _subtitleManager = subtitleManager;
 | 
				
			||||||
            _chapterManager = chapterManager;
 | 
					            _chapterManager = chapterManager;
 | 
				
			||||||
            _libraryManager = libraryManager;
 | 
					            _libraryManager = libraryManager;
 | 
				
			||||||
            _channelManager = channelManager;
 | 
					 | 
				
			||||||
            _mediaSourceManager = mediaSourceManager;
 | 
					            _mediaSourceManager = mediaSourceManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager);
 | 
					            _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager);
 | 
				
			||||||
@ -211,9 +199,9 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private string NormalizeStrmLine(string line)
 | 
					        private string NormalizeStrmLine(string line)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return line.Replace("\t", string.Empty)
 | 
					            return line.Replace("\t", string.Empty, StringComparison.Ordinal)
 | 
				
			||||||
                .Replace("\r", string.Empty)
 | 
					                .Replace("\r", string.Empty, StringComparison.Ordinal)
 | 
				
			||||||
                .Replace("\n", string.Empty)
 | 
					                .Replace("\n", string.Empty, StringComparison.Ordinal)
 | 
				
			||||||
                .Trim();
 | 
					                .Trim();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -242,10 +230,11 @@ namespace MediaBrowser.Providers.MediaInfo
 | 
				
			|||||||
                FetchShortcutInfo(item);
 | 
					                FetchShortcutInfo(item);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var prober = new FFProbeAudioInfo(_mediaSourceManager, _mediaEncoder, _itemRepo, _appPaths, _json, _libraryManager);
 | 
					            var prober = new FFProbeAudioInfo(_mediaSourceManager, _mediaEncoder, _itemRepo, _libraryManager);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return prober.Probe(item, options, cancellationToken);
 | 
					            return prober.Probe(item, options, cancellationToken);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Run last
 | 
					        // Run last
 | 
				
			||||||
        public int Order => 100;
 | 
					        public int Order => 100;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ using MediaBrowser.Model.Entities;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
namespace MediaBrowser.Providers.Music
 | 
					namespace MediaBrowser.Providers.Music
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    public static class Extensions
 | 
					    public static class AlbumInfoExtensions
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        public static string GetAlbumArtist(this AlbumInfo info)
 | 
					        public static string GetAlbumArtist(this AlbumInfo info)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -18,7 +18,7 @@ namespace MediaBrowser.Providers.Music
 | 
				
			|||||||
                return id;
 | 
					                return id;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return info.AlbumArtists.FirstOrDefault();
 | 
					            return info.AlbumArtists.Count > 0 ? info.AlbumArtists[0] : default;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static string GetReleaseGroupId(this AlbumInfo info)
 | 
					        public static string GetReleaseGroupId(this AlbumInfo info)
 | 
				
			||||||
 | 
				
			|||||||
@ -276,7 +276,7 @@ namespace MediaBrowser.Providers.Music
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private async Task<ReleaseResult> GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken)
 | 
					        private async Task<ReleaseResult> GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var url = string.Format("/ws/2/release/?query=\"{0}\" AND arid:{1}",
 | 
					            var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/release/?query=\"{0}\" AND arid:{1}",
 | 
				
			||||||
                WebUtility.UrlEncode(albumName),
 | 
					                WebUtility.UrlEncode(albumName),
 | 
				
			||||||
                artistId);
 | 
					                artistId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -46,7 +46,7 @@ namespace MediaBrowser.Providers.Music
 | 
				
			|||||||
                // They seem to throw bad request failures on any term with a slash
 | 
					                // They seem to throw bad request failures on any term with a slash
 | 
				
			||||||
                var nameToSearch = searchInfo.Name.Replace('/', ' ');
 | 
					                var nameToSearch = searchInfo.Name.Replace('/', ' ');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                var url = string.Format("/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch));
 | 
					                var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
 | 
					                using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
 | 
				
			||||||
                await using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
 | 
					                await using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
 | 
				
			||||||
@ -62,7 +62,7 @@ namespace MediaBrowser.Providers.Music
 | 
				
			|||||||
                if (HasDiacritics(searchInfo.Name))
 | 
					                if (HasDiacritics(searchInfo.Name))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    // Try again using the search with accent characters url
 | 
					                    // Try again using the search with accent characters url
 | 
				
			||||||
                    url = string.Format("/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
 | 
					                    url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
 | 
					                    using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
                    await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
 | 
					                    await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.Net.Http;
 | 
					using System.Net.Http;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using MediaBrowser.Common;
 | 
					using MediaBrowser.Common;
 | 
				
			||||||
@ -70,7 +71,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
 | 
				
			|||||||
                        list.Add(new RemoteImageInfo
 | 
					                        list.Add(new RemoteImageInfo
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            ProviderName = Name,
 | 
					                            ProviderName = Name,
 | 
				
			||||||
                            Url = string.Format("https://img.omdbapi.com/?i={0}&apikey=2c9d9507", imdbId)
 | 
					                            Url = string.Format(CultureInfo.InvariantCulture, "https://img.omdbapi.com/?i={0}&apikey=2c9d9507", imdbId)
 | 
				
			||||||
                        });
 | 
					                        });
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
				
			|||||||
@ -127,7 +127,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var url = OmdbProvider.GetOmdbUrl(urlQuery, _appHost, cancellationToken);
 | 
					            var url = OmdbProvider.GetOmdbUrl(urlQuery);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
 | 
					            using var response = await OmdbProvider.GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
 | 
					            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
 | 
				
			||||||
 | 
				
			|||||||
@ -256,16 +256,16 @@ namespace MediaBrowser.Providers.Plugins.Omdb
 | 
				
			|||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public static string GetOmdbUrl(string query, IApplicationHost appHost, CancellationToken cancellationToken)
 | 
					        public static string GetOmdbUrl(string query)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            const string url = "https://www.omdbapi.com?apikey=2c9d9507";
 | 
					            const string Url = "https://www.omdbapi.com?apikey=2c9d9507";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(query))
 | 
					            if (string.IsNullOrWhiteSpace(query))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return url;
 | 
					                return Url;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return url + "&" + query;
 | 
					            return Url + "&" + query;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private async Task<string> EnsureItemInfo(string imdbId, CancellationToken cancellationToken)
 | 
					        private async Task<string> EnsureItemInfo(string imdbId, CancellationToken cancellationToken)
 | 
				
			||||||
@ -290,7 +290,11 @@ namespace MediaBrowser.Providers.Plugins.Omdb
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var url = GetOmdbUrl(string.Format("i={0}&plot=short&tomatoes=true&r=json", imdbParam), _appHost, cancellationToken);
 | 
					            var url = GetOmdbUrl(
 | 
				
			||||||
 | 
					                string.Format(
 | 
				
			||||||
 | 
					                    CultureInfo.InvariantCulture,
 | 
				
			||||||
 | 
					                    "i={0}&plot=short&tomatoes=true&r=json",
 | 
				
			||||||
 | 
					                    imdbParam));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
 | 
					            using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
 | 
					            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
 | 
				
			||||||
@ -323,7 +327,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var url = GetOmdbUrl(string.Format("i={0}&season={1}&detail=full", imdbParam, seasonId), _appHost, cancellationToken);
 | 
					            var url = GetOmdbUrl(
 | 
				
			||||||
 | 
					                string.Format(
 | 
				
			||||||
 | 
					                    CultureInfo.InvariantCulture,
 | 
				
			||||||
 | 
					                    "i={0}&season={1}&detail=full",
 | 
				
			||||||
 | 
					                    imdbParam,
 | 
				
			||||||
 | 
					                    seasonId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
 | 
					            using var response = await GetOmdbResponse(_httpClientFactory.CreateClient(), url, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
 | 
					            await using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
 | 
				
			||||||
@ -348,7 +357,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
 | 
					            var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var filename = string.Format("{0}.json", imdbId);
 | 
					            var filename = string.Format(CultureInfo.InvariantCulture, "{0}.json", imdbId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Path.Combine(dataPath, filename);
 | 
					            return Path.Combine(dataPath, filename);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -362,7 +371,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
 | 
					            var dataPath = Path.Combine(_configurationManager.ApplicationPaths.CachePath, "omdb");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var filename = string.Format("{0}_season_{1}.json", imdbId, seasonId);
 | 
					            var filename = string.Format(CultureInfo.InvariantCulture, "{0}_season_{1}.json", imdbId, seasonId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Path.Combine(dataPath, filename);
 | 
					            return Path.Combine(dataPath, filename);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Reflection;
 | 
					using System.Reflection;
 | 
				
			||||||
using System.Runtime.CompilerServices;
 | 
					using System.Runtime.CompilerServices;
 | 
				
			||||||
@ -19,7 +20,6 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        private const string DefaultLanguage = "en";
 | 
					        private const string DefaultLanguage = "en";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly SemaphoreSlim _cacheWriteLock = new SemaphoreSlim(1, 1);
 | 
					 | 
				
			||||||
        private readonly IMemoryCache _cache;
 | 
					        private readonly IMemoryCache _cache;
 | 
				
			||||||
        private readonly TvDbClient _tvDbClient;
 | 
					        private readonly TvDbClient _tvDbClient;
 | 
				
			||||||
        private DateTime _tokenCreatedAt;
 | 
					        private DateTime _tokenCreatedAt;
 | 
				
			||||||
@ -176,7 +176,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
 | 
				
			|||||||
            string language,
 | 
					            string language,
 | 
				
			||||||
            CancellationToken cancellationToken)
 | 
					            CancellationToken cancellationToken)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            searchInfo.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(),
 | 
					            searchInfo.SeriesProviderIds.TryGetValue(nameof(MetadataProvider.Tvdb),
 | 
				
			||||||
                out var seriesTvdbId);
 | 
					                out var seriesTvdbId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var episodeQuery = new EpisodeQuery();
 | 
					            var episodeQuery = new EpisodeQuery();
 | 
				
			||||||
@ -203,10 +203,10 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
 | 
				
			|||||||
            else if (searchInfo.PremiereDate.HasValue)
 | 
					            else if (searchInfo.PremiereDate.HasValue)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // tvdb expects yyyy-mm-dd format
 | 
					                // tvdb expects yyyy-mm-dd format
 | 
				
			||||||
                episodeQuery.FirstAired = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd");
 | 
					                episodeQuery.FirstAired = searchInfo.PremiereDate.Value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken);
 | 
					            return GetEpisodeTvdbId(Convert.ToInt32(seriesTvdbId, CultureInfo.InvariantCulture), episodeQuery, language, cancellationToken);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task<string> GetEpisodeTvdbId(
 | 
					        public async Task<string> GetEpisodeTvdbId(
 | 
				
			||||||
@ -218,7 +218,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
 | 
				
			|||||||
            var episodePage =
 | 
					            var episodePage =
 | 
				
			||||||
                await GetEpisodesPageAsync(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken)
 | 
					                await GetEpisodesPageAsync(Convert.ToInt32(seriesTvdbId), episodeQuery, language, cancellationToken)
 | 
				
			||||||
                    .ConfigureAwait(false);
 | 
					                    .ConfigureAwait(false);
 | 
				
			||||||
            return episodePage.Data.FirstOrDefault()?.Id.ToString();
 | 
					            return episodePage.Data.FirstOrDefault()?.Id.ToString(CultureInfo.InvariantCulture);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(
 | 
					        public Task<TvDbResponse<EpisodeRecord[]>> GetEpisodesPageAsync(
 | 
				
			||||||
@ -276,23 +276,10 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
 | 
				
			|||||||
                return cachedValue;
 | 
					                return cachedValue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await _cacheWriteLock.WaitAsync().ConfigureAwait(false);
 | 
					            _tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage;
 | 
				
			||||||
            try
 | 
					            var result = await resultFactory.Invoke().ConfigureAwait(false);
 | 
				
			||||||
            {
 | 
					            _cache.Set(key, result, TimeSpan.FromHours(1));
 | 
				
			||||||
                if (_cache.TryGetValue(key, out cachedValue))
 | 
					            return result;
 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return cachedValue;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                _tvDbClient.AcceptedLanguage = TvdbUtils.NormalizeLanguage(language) ?? DefaultLanguage;
 | 
					 | 
				
			||||||
                var result = await resultFactory.Invoke().ConfigureAwait(false);
 | 
					 | 
				
			||||||
                _cache.Set(key, result, TimeSpan.FromHours(1));
 | 
					 | 
				
			||||||
                return result;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            finally
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                _cacheWriteLock.Release();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private static string GenerateKey(params object[] objects)
 | 
					        private static string GenerateKey(params object[] objects)
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@
 | 
				
			|||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
using System.Net.Http;
 | 
					using System.Net.Http;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
@ -76,7 +77,7 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    var episodeResult =
 | 
					                    var episodeResult =
 | 
				
			||||||
                        await _tvdbClientManager
 | 
					                        await _tvdbClientManager
 | 
				
			||||||
                            .GetEpisodesAsync(Convert.ToInt32(episodeTvdbId), language, cancellationToken)
 | 
					                            .GetEpisodesAsync(Convert.ToInt32(episodeTvdbId, CultureInfo.InvariantCulture), language, cancellationToken)
 | 
				
			||||||
                            .ConfigureAwait(false);
 | 
					                            .ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    var image = GetImageInfo(episodeResult.Data);
 | 
					                    var image = GetImageInfo(episodeResult.Data);
 | 
				
			||||||
@ -103,8 +104,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            return new RemoteImageInfo
 | 
					            return new RemoteImageInfo
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                Width = Convert.ToInt32(episode.ThumbWidth),
 | 
					                Width = Convert.ToInt32(episode.ThumbWidth, CultureInfo.InvariantCulture),
 | 
				
			||||||
                Height = Convert.ToInt32(episode.ThumbHeight),
 | 
					                Height = Convert.ToInt32(episode.ThumbHeight, CultureInfo.InvariantCulture),
 | 
				
			||||||
                ProviderName = Name,
 | 
					                ProviderName = Name,
 | 
				
			||||||
                Url = TvdbUtils.BannerUrl + episode.Filename,
 | 
					                Url = TvdbUtils.BannerUrl + episode.Filename,
 | 
				
			||||||
                Type = ImageType.Primary
 | 
					                Type = ImageType.Primary
 | 
				
			||||||
 | 
				
			|||||||
@ -180,7 +180,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (!string.IsNullOrEmpty(language))
 | 
					            if (!string.IsNullOrEmpty(language))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                url += string.Format("&language={0}", TmdbMovieProvider.NormalizeLanguage(language));
 | 
					                url += string.Format(CultureInfo.InvariantCulture, "&language={0}", TmdbMovieProvider.NormalizeLanguage(language));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Get images in english and with no language
 | 
					                // Get images in english and with no language
 | 
				
			||||||
                url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language);
 | 
					                url += "&include_image_language=" + TmdbMovieProvider.GetImageLanguagesParam(language);
 | 
				
			||||||
@ -250,7 +250,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            var path = GetDataPath(appPaths, tmdbId);
 | 
					            var path = GetDataPath(appPaths, tmdbId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var filename = string.Format("all-{0}.json", preferredLanguage ?? string.Empty);
 | 
					            var filename = string.Format(CultureInfo.InvariantCulture, "all-{0}.json", preferredLanguage ?? string.Empty);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Path.Combine(path, filename);
 | 
					            return Path.Combine(path, filename);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -300,7 +300,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                movie.RemoteTrailers = movieData.Trailers.Youtube.Select(i => new MediaUrl
 | 
					                movie.RemoteTrailers = movieData.Trailers.Youtube.Select(i => new MediaUrl
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    Url = string.Format("https://www.youtube.com/watch?v={0}", i.Source),
 | 
					                    Url = string.Format(CultureInfo.InvariantCulture, "https://www.youtube.com/watch?v={0}", i.Source),
 | 
				
			||||||
                    Name = i.Name
 | 
					                    Name = i.Name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                }).ToArray();
 | 
					                }).ToArray();
 | 
				
			||||||
 | 
				
			|||||||
@ -37,7 +37,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
 | 
				
			|||||||
            ).* # Match rest of string",
 | 
					            ).* # Match rest of string",
 | 
				
			||||||
            RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);
 | 
					            RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private const string _searchURL = TmdbUtils.BaseTmdbApiUrl + @"3/search/{3}?api_key={1}&query={0}&language={2}";
 | 
					        private const string SearchUrl = TmdbUtils.BaseTmdbApiUrl + @"3/search/{3}?api_key={1}&query={0}&language={2}";
 | 
				
			||||||
 | 
					        private const string SearchUrlWithYear = TmdbUtils.BaseTmdbApiUrl + @"3/search/{3}?api_key={1}&query={0}&language={2}&first_air_date_year={4}";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly ILogger _logger;
 | 
					        private readonly ILogger _logger;
 | 
				
			||||||
        private readonly IJsonSerializer _json;
 | 
					        private readonly IJsonSerializer _json;
 | 
				
			||||||
@ -124,7 +125,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
 | 
				
			|||||||
                name2 = name2.Trim();
 | 
					                name2 = name2.Trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Search again if the new name is different
 | 
					                // Search again if the new name is different
 | 
				
			||||||
                if (!string.Equals(name2, name) && !string.IsNullOrWhiteSpace(name2))
 | 
					                if (!string.Equals(name2, name, StringComparison.Ordinal) && !string.IsNullOrWhiteSpace(name2))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    _logger.LogInformation("TmdbSearch: Finding id for item: {0} ({1})", name2, year);
 | 
					                    _logger.LogInformation("TmdbSearch: Finding id for item: {0} ({1})", name2, year);
 | 
				
			||||||
                    results = await GetSearchResults(name2, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false);
 | 
					                    results = await GetSearchResults(name2, searchType, year, language, tmdbImageUrl, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
@ -164,10 +165,30 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(name))
 | 
					            if (string.IsNullOrWhiteSpace(name))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                throw new ArgumentException("name");
 | 
					                throw new ArgumentException("String can't be null or empty.", nameof(name));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var url3 = string.Format(_searchURL, WebUtility.UrlEncode(name), TmdbUtils.ApiKey, language, type);
 | 
					            string url3;
 | 
				
			||||||
 | 
					            if (year != null && string.Equals(type, "movie", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                url3 = string.Format(
 | 
				
			||||||
 | 
					                    CultureInfo.InvariantCulture,
 | 
				
			||||||
 | 
					                    SearchUrl,
 | 
				
			||||||
 | 
					                    WebUtility.UrlEncode(name),
 | 
				
			||||||
 | 
					                    TmdbUtils.ApiKey,
 | 
				
			||||||
 | 
					                    language,
 | 
				
			||||||
 | 
					                    type) + "&primary_release_year=" + year;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                url3 = string.Format(
 | 
				
			||||||
 | 
					                    CultureInfo.InvariantCulture,
 | 
				
			||||||
 | 
					                    SearchUrl,
 | 
				
			||||||
 | 
					                    WebUtility.UrlEncode(name),
 | 
				
			||||||
 | 
					                    TmdbUtils.ApiKey,
 | 
				
			||||||
 | 
					                    language,
 | 
				
			||||||
 | 
					                    type);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var requestMessage = new HttpRequestMessage(HttpMethod.Get, url3);
 | 
					            var requestMessage = new HttpRequestMessage(HttpMethod.Get, url3);
 | 
				
			||||||
            foreach (var header in TmdbUtils.AcceptHeaders)
 | 
					            foreach (var header in TmdbUtils.AcceptHeaders)
 | 
				
			||||||
@ -207,10 +228,31 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(name))
 | 
					            if (string.IsNullOrWhiteSpace(name))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                throw new ArgumentException("name");
 | 
					                throw new ArgumentException("String can't be null or empty.", nameof(name));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var url3 = string.Format(_searchURL, WebUtility.UrlEncode(name), TmdbUtils.ApiKey, language, "tv");
 | 
					            string url3;
 | 
				
			||||||
 | 
					            if (year == null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                url3 = string.Format(
 | 
				
			||||||
 | 
					                CultureInfo.InvariantCulture,
 | 
				
			||||||
 | 
					                SearchUrl,
 | 
				
			||||||
 | 
					                WebUtility.UrlEncode(name),
 | 
				
			||||||
 | 
					                TmdbUtils.ApiKey,
 | 
				
			||||||
 | 
					                language,
 | 
				
			||||||
 | 
					                "tv");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                url3 = string.Format(
 | 
				
			||||||
 | 
					                    CultureInfo.InvariantCulture,
 | 
				
			||||||
 | 
					                    SearchUrlWithYear,
 | 
				
			||||||
 | 
					                    WebUtility.UrlEncode(name),
 | 
				
			||||||
 | 
					                    TmdbUtils.ApiKey,
 | 
				
			||||||
 | 
					                    language,
 | 
				
			||||||
 | 
					                    "tv",
 | 
				
			||||||
 | 
					                    year);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var requestMessage = new HttpRequestMessage(HttpMethod.Get, url3);
 | 
					            var requestMessage = new HttpRequestMessage(HttpMethod.Get, url3);
 | 
				
			||||||
            foreach (var header in TmdbUtils.AcceptHeaders)
 | 
					            foreach (var header in TmdbUtils.AcceptHeaders)
 | 
				
			||||||
 | 
				
			|||||||
@ -131,7 +131,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 | 
				
			|||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            if (video.Site.Equals("youtube", System.StringComparison.OrdinalIgnoreCase))
 | 
					                            if (video.Site.Equals("youtube", System.StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
                            {
 | 
					                            {
 | 
				
			||||||
                                var videoUrl = string.Format("http://www.youtube.com/watch?v={0}", video.Key);
 | 
					                                var videoUrl = string.Format(CultureInfo.InvariantCulture, "http://www.youtube.com/watch?v={0}", video.Key);
 | 
				
			||||||
                                item.AddTrailerUrl(videoUrl);
 | 
					                                item.AddTrailerUrl(videoUrl);
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
				
			|||||||
@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var path = TmdbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
 | 
					            var path = TmdbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var filename = string.Format("season-{0}-episode-{1}-{2}.json",
 | 
					            var filename = string.Format(CultureInfo.InvariantCulture, "season-{0}-episode-{1}-{2}.json",
 | 
				
			||||||
                seasonNumber.ToString(CultureInfo.InvariantCulture),
 | 
					                seasonNumber.ToString(CultureInfo.InvariantCulture),
 | 
				
			||||||
                episodeNumber.ToString(CultureInfo.InvariantCulture),
 | 
					                episodeNumber.ToString(CultureInfo.InvariantCulture),
 | 
				
			||||||
                preferredLanguage);
 | 
					                preferredLanguage);
 | 
				
			||||||
@ -116,7 +116,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (!string.IsNullOrEmpty(language))
 | 
					            if (!string.IsNullOrEmpty(language))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                url += string.Format("&language={0}", language);
 | 
					                url += string.Format(CultureInfo.InvariantCulture, "&language={0}", language);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var includeImageLanguageParam = TmdbMovieProvider.GetImageLanguagesParam(language);
 | 
					            var includeImageLanguageParam = TmdbMovieProvider.GetImageLanguagesParam(language);
 | 
				
			||||||
 | 
				
			|||||||
@ -180,7 +180,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var path = TmdbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
 | 
					            var path = TmdbSeriesProvider.GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var filename = string.Format("season-{0}-{1}.json",
 | 
					            var filename = string.Format(CultureInfo.InvariantCulture, "season-{0}-{1}.json",
 | 
				
			||||||
                seasonNumber.ToString(CultureInfo.InvariantCulture),
 | 
					                seasonNumber.ToString(CultureInfo.InvariantCulture),
 | 
				
			||||||
                preferredLanguage);
 | 
					                preferredLanguage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -203,7 +203,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (!string.IsNullOrEmpty(language))
 | 
					            if (!string.IsNullOrEmpty(language))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                url += string.Format("&language={0}", TmdbMovieProvider.NormalizeLanguage(language));
 | 
					                url += string.Format(CultureInfo.InvariantCulture, "&language={0}", TmdbMovieProvider.NormalizeLanguage(language));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var includeImageLanguageParam = TmdbMovieProvider.GetImageLanguagesParam(language);
 | 
					            var includeImageLanguageParam = TmdbMovieProvider.GetImageLanguagesParam(language);
 | 
				
			||||||
 | 
				
			|||||||
@ -496,7 +496,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var path = GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
 | 
					            var path = GetSeriesDataPath(_configurationManager.ApplicationPaths, tmdbId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var filename = string.Format("series-{0}.json", preferredLanguage ?? string.Empty);
 | 
					            var filename = string.Format(CultureInfo.InvariantCulture, "series-{0}.json", preferredLanguage ?? string.Empty);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Path.Combine(path, filename);
 | 
					            return Path.Combine(path, filename);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using System;
 | 
					using System;
 | 
				
			||||||
using System.Collections.Generic;
 | 
					using System.Collections.Generic;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
using System.IO;
 | 
					using System.IO;
 | 
				
			||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Net.Http;
 | 
					using System.Net.Http;
 | 
				
			||||||
@ -100,7 +101,7 @@ namespace MediaBrowser.Providers.Studios
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        private string GetUrl(string image, string filename)
 | 
					        private string GetUrl(string image, string filename)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return string.Format("https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studios/{0}/{1}.jpg", image, filename);
 | 
					            return string.Format(CultureInfo.InvariantCulture, "https://raw.github.com/MediaBrowser/MediaBrowser.Resources/master/images/imagesbyname/studios/{0}/{1}.jpg", image, filename);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken)
 | 
					        private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken)
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user