From 08e58886f767056c67adb7c1859f864f9a9bffea Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sat, 2 Jul 2016 22:47:39 -0400 Subject: [PATCH 1/6] update metadata editor --- .../Encoder/FontConfigLoader.cs | 179 +++++++++++++++++ .../Encoder/MediaEncoder.cs | 18 +- .../MediaBrowser.MediaEncoding.csproj | 1 + .../IO/FileRefresher.cs | 2 +- .../ApplicationHost.cs | 4 +- .../FFMpeg/FFMpegLoader.cs | 180 ------------------ 6 files changed, 201 insertions(+), 183 deletions(-) create mode 100644 MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs diff --git a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs new file mode 100644 index 0000000000..d7ef493c2c --- /dev/null +++ b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs @@ -0,0 +1,179 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CommonIO; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Net; + +namespace MediaBrowser.MediaEncoding.Encoder +{ + public class FontConfigLoader + { + private readonly IHttpClient _httpClient; + private readonly IApplicationPaths _appPaths; + private readonly ILogger _logger; + private readonly IZipClient _zipClient; + private readonly IFileSystem _fileSystem; + + private readonly string[] _fontUrls = + { + "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z" + }; + + public FontConfigLoader(IHttpClient httpClient, IApplicationPaths appPaths, ILogger logger, IZipClient zipClient, IFileSystem fileSystem) + { + _httpClient = httpClient; + _appPaths = appPaths; + _logger = logger; + _zipClient = zipClient; + _fileSystem = fileSystem; + } + + /// + /// Extracts the fonts. + /// + /// The target path. + /// Task. + public async Task DownloadFonts(string targetPath) + { + try + { + var fontsDirectory = Path.Combine(targetPath, "fonts"); + + _fileSystem.CreateDirectory(fontsDirectory); + + const string fontFilename = "ARIALUNI.TTF"; + + var fontFile = Path.Combine(fontsDirectory, fontFilename); + + if (_fileSystem.FileExists(fontFile)) + { + await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); + } + else + { + // Kick this off, but no need to wait on it + Task.Run(async () => + { + await DownloadFontFile(fontsDirectory, fontFilename, new Progress()).ConfigureAwait(false); + + await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); + }); + } + } + catch (HttpException ex) + { + // Don't let the server crash because of this + _logger.ErrorException("Error downloading ffmpeg font files", ex); + } + catch (Exception ex) + { + // Don't let the server crash because of this + _logger.ErrorException("Error writing ffmpeg font files", ex); + } + } + + /// + /// Downloads the font file. + /// + /// The fonts directory. + /// The font filename. + /// Task. + private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress progress) + { + var existingFile = Directory + .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories) + .FirstOrDefault(); + + if (existingFile != null) + { + try + { + _fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true); + return; + } + catch (IOException ex) + { + // Log this, but don't let it fail the operation + _logger.ErrorException("Error copying file", ex); + } + } + + string tempFile = null; + + foreach (var url in _fontUrls) + { + progress.Report(0); + + try + { + tempFile = await _httpClient.GetTempFile(new HttpRequestOptions + { + Url = url, + Progress = progress + + }).ConfigureAwait(false); + + break; + } + catch (Exception ex) + { + // The core can function without the font file, so handle this + _logger.ErrorException("Failed to download ffmpeg font file from {0}", ex, url); + } + } + + if (string.IsNullOrEmpty(tempFile)) + { + return; + } + + Extract7zArchive(tempFile, fontsDirectory); + + try + { + _fileSystem.DeleteFile(tempFile); + } + catch (IOException ex) + { + // Log this, but don't let it fail the operation + _logger.ErrorException("Error deleting temp file {0}", ex, tempFile); + } + } + private void Extract7zArchive(string archivePath, string targetPath) + { + _logger.Info("Extracting {0} to {1}", archivePath, targetPath); + + _zipClient.ExtractAllFrom7z(archivePath, targetPath, true); + } + + /// + /// Writes the font config file. + /// + /// The fonts directory. + /// Task. + private async Task WriteFontConfigFile(string fontsDirectory) + { + const string fontConfigFilename = "fonts.conf"; + var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename); + + if (!_fileSystem.FileExists(fontConfigFile)) + { + var contents = string.Format("{0}ArialArial Unicode MS", fontsDirectory); + + var bytes = Encoding.UTF8.GetBytes(contents); + + using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileMode.Create, FileAccess.Write, + FileShare.Read, true)) + { + await fileStream.WriteAsync(bytes, 0, bytes.Length); + } + } + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a78e23669d..383e937583 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -24,6 +24,7 @@ using CommonIO; using MediaBrowser.Model.Configuration; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; namespace MediaBrowser.MediaEncoding.Encoder { @@ -76,11 +77,13 @@ namespace MediaBrowser.MediaEncoding.Encoder protected readonly ISessionManager SessionManager; protected readonly Func SubtitleEncoder; protected readonly Func MediaSourceManager; + private readonly IHttpClient _httpClient; + private readonly IZipClient _zipClient; private readonly List _runningProcesses = new List(); private readonly bool _hasExternalEncoder; - public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager) + public MediaEncoder(ILogger logger, IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, bool hasExternalEncoder, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ILiveTvManager liveTvManager, IIsoManager isoManager, ILibraryManager libraryManager, IChannelManager channelManager, ISessionManager sessionManager, Func subtitleEncoder, Func mediaSourceManager, IHttpClient httpClient, IZipClient zipClient) { _logger = logger; _jsonSerializer = jsonSerializer; @@ -93,6 +96,8 @@ namespace MediaBrowser.MediaEncoding.Encoder SessionManager = sessionManager; SubtitleEncoder = subtitleEncoder; MediaSourceManager = mediaSourceManager; + _httpClient = httpClient; + _zipClient = zipClient; FFProbePath = ffProbePath; FFMpegPath = ffMpegPath; @@ -142,6 +147,17 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableDecoders(result.Item1); SetAvailableEncoders(result.Item2); + + if (Environment.OSVersion.Platform == PlatformID.Win32NT) + { + var directory = Path.GetDirectoryName(FFMpegPath); + + if (FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory)) + { + await new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient, + FileSystem).DownloadFonts(directory).ConfigureAwait(false); + } + } } } diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 7943534515..1b55995778 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -72,6 +72,7 @@ + diff --git a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs index 4bea6ad34d..f48beacb57 100644 --- a/MediaBrowser.Server.Implementations/IO/FileRefresher.cs +++ b/MediaBrowser.Server.Implementations/IO/FileRefresher.cs @@ -33,13 +33,13 @@ namespace MediaBrowser.Server.Implementations.IO { logger.Debug("New file refresher created for {0}", path); Path = path; - _affectedPaths.Add(path); _fileSystem = fileSystem; ConfigurationManager = configurationManager; LibraryManager = libraryManager; TaskManager = taskManager; Logger = logger; + AddPath(path); } private void AddAffectedPath(string path) diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 63bdddac99..bcd2ed8bd8 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -660,7 +660,9 @@ namespace MediaBrowser.Server.Startup.Common ChannelManager, SessionManager, () => SubtitleEncoder, - () => MediaSourceManager); + () => MediaSourceManager, + HttpClient, + ZipClient); MediaEncoder = mediaEncoder; RegisterSingleInstance(MediaEncoder); diff --git a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs index 4c5759b567..68e2a49275 100644 --- a/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs +++ b/MediaBrowser.Server.Startup.Common/FFMpeg/FFMpegLoader.cs @@ -2,14 +2,11 @@ using MediaBrowser.Common.Net; using MediaBrowser.Model.IO; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Net; using Mono.Unix.Native; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; -using System.Text; using System.Threading; using System.Threading.Tasks; using CommonIO; @@ -26,11 +23,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg private readonly NativeEnvironment _environment; private readonly FFMpegInstallInfo _ffmpegInstallInfo; - private readonly string[] _fontUrls = - { - "https://github.com/MediaBrowser/MediaBrowser.Resources/raw/master/ffmpeg/ARIALUNI.7z" - }; - public FFMpegLoader(ILogger logger, IApplicationPaths appPaths, IHttpClient httpClient, IZipClient zipClient, IFileSystem fileSystem, NativeEnvironment environment, FFMpegInstallInfo ffmpegInstallInfo) { _logger = logger; @@ -112,13 +104,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg } } - if (_environment.OperatingSystem == OperatingSystem.Windows) - { - await DownloadFonts(versionedDirectoryPath).ConfigureAwait(false); - } - - DeleteOlderFolders(Path.GetDirectoryName(versionedDirectoryPath), excludeFromDeletions); - // Allow just one of these to be overridden, if desired. if (!string.IsNullOrWhiteSpace(customffMpegPath)) { @@ -132,30 +117,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg return info; } - private void DeleteOlderFolders(string path, IEnumerable excludeFolders) - { - var folders = Directory.GetDirectories(path) - .Where(i => !excludeFolders.Contains(i, StringComparer.OrdinalIgnoreCase)) - .ToList(); - - foreach (var folder in folders) - { - DeleteFolder(folder); - } - } - - private void DeleteFolder(string path) - { - try - { - _fileSystem.DeleteDirectory(path, true); - } - catch (Exception ex) - { - _logger.ErrorException("Error deleting {0}", ex, path); - } - } - private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath) { var encoderFilename = Path.GetFileName(info.EncoderPath); @@ -270,12 +231,6 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg _zipClient.ExtractAllFromTar(archivePath, targetPath, true); } } - private void Extract7zArchive(string archivePath, string targetPath) - { - _logger.Info("Extracting {0} to {1}", archivePath, targetPath); - - _zipClient.ExtractAllFrom7z(archivePath, targetPath, true); - } private void DeleteFile(string path) { @@ -289,140 +244,5 @@ namespace MediaBrowser.Server.Startup.Common.FFMpeg } } - /// - /// Extracts the fonts. - /// - /// The target path. - /// Task. - private async Task DownloadFonts(string targetPath) - { - try - { - var fontsDirectory = Path.Combine(targetPath, "fonts"); - - _fileSystem.CreateDirectory(fontsDirectory); - - const string fontFilename = "ARIALUNI.TTF"; - - var fontFile = Path.Combine(fontsDirectory, fontFilename); - - if (_fileSystem.FileExists(fontFile)) - { - await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); - } - else - { - // Kick this off, but no need to wait on it - Task.Run(async () => - { - await DownloadFontFile(fontsDirectory, fontFilename, new Progress()).ConfigureAwait(false); - - await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false); - }); - } - } - catch (HttpException ex) - { - // Don't let the server crash because of this - _logger.ErrorException("Error downloading ffmpeg font files", ex); - } - catch (Exception ex) - { - // Don't let the server crash because of this - _logger.ErrorException("Error writing ffmpeg font files", ex); - } - } - - /// - /// Downloads the font file. - /// - /// The fonts directory. - /// The font filename. - /// Task. - private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress progress) - { - var existingFile = Directory - .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories) - .FirstOrDefault(); - - if (existingFile != null) - { - try - { - _fileSystem.CopyFile(existingFile, Path.Combine(fontsDirectory, fontFilename), true); - return; - } - catch (IOException ex) - { - // Log this, but don't let it fail the operation - _logger.ErrorException("Error copying file", ex); - } - } - - string tempFile = null; - - foreach (var url in _fontUrls) - { - progress.Report(0); - - try - { - tempFile = await _httpClient.GetTempFile(new HttpRequestOptions - { - Url = url, - Progress = progress - - }).ConfigureAwait(false); - - break; - } - catch (Exception ex) - { - // The core can function without the font file, so handle this - _logger.ErrorException("Failed to download ffmpeg font file from {0}", ex, url); - } - } - - if (string.IsNullOrEmpty(tempFile)) - { - return; - } - - Extract7zArchive(tempFile, fontsDirectory); - - try - { - _fileSystem.DeleteFile(tempFile); - } - catch (IOException ex) - { - // Log this, but don't let it fail the operation - _logger.ErrorException("Error deleting temp file {0}", ex, tempFile); - } - } - - /// - /// Writes the font config file. - /// - /// The fonts directory. - /// Task. - private async Task WriteFontConfigFile(string fontsDirectory) - { - const string fontConfigFilename = "fonts.conf"; - var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename); - - if (!_fileSystem.FileExists(fontConfigFile)) - { - var contents = string.Format("{0}ArialArial Unicode MS", fontsDirectory); - - var bytes = Encoding.UTF8.GetBytes(contents); - - using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileMode.Create, FileAccess.Write, - FileShare.Read, true)) - { - await fileStream.WriteAsync(bytes, 0, bytes.Length); - } - } - } } } From 373448675b4209267b9fcf5e3999376c02a69e25 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 3 Jul 2016 19:05:13 -0400 Subject: [PATCH 2/6] update components --- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 4 ++-- .../Persistence/SqliteItemRepository.cs | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index f779fcd610..6031ae88e4 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -192,7 +192,7 @@ namespace MediaBrowser.Model.Configuration public int MigrationVersion { get; set; } public int SchemaVersion { get; set; } - public int SqliteCachePages { get; set; } + public int SqliteCacheSizeKb { get; set; } public bool DownloadImagesInAdvance { get; set; } @@ -212,7 +212,7 @@ namespace MediaBrowser.Model.Configuration { LocalNetworkAddresses = new string[] { }; Migrations = new string[] { }; - SqliteCachePages = 10000; + SqliteCacheSizeKb = 50000; EnableCustomPathSubFolders = true; EnableLocalizedGuids = true; diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 2a22fc537a..7d7299107c 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -123,7 +123,13 @@ namespace MediaBrowser.Server.Implementations.Persistence protected override async Task CreateConnection(bool isReadOnly = false) { - var connection = await DbConnector.Connect(DbFilePath, false, false, _config.Configuration.SqliteCachePages).ConfigureAwait(false); + var cacheSize = _config.Configuration.SqliteCacheSizeKb; + if (cacheSize <= 0) + { + cacheSize = Math.Min(Environment.ProcessorCount * 50000, 200000); + } + + var connection = await DbConnector.Connect(DbFilePath, false, false, 0 - cacheSize).ConfigureAwait(false); connection.RunQueries(new[] { From 986cdc429ff91d9f04f3fbc1d1c549012c264232 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 3 Jul 2016 19:54:19 -0400 Subject: [PATCH 3/6] update db upgrade --- .../Persistence/SqliteItemRepository.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index 7d7299107c..d1b23a0ae8 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -2975,8 +2975,15 @@ namespace MediaBrowser.Server.Implementations.Persistence } if (query.IsVirtualItem.HasValue) { - whereClauses.Add("IsVirtualItem=@IsVirtualItem"); - cmd.Parameters.Add(cmd, "@IsVirtualItem", DbType.Boolean).Value = query.IsVirtualItem.Value; + if (_config.Configuration.SchemaVersion >= 90) + { + whereClauses.Add("IsVirtualItem=@IsVirtualItem"); + cmd.Parameters.Add(cmd, "@IsVirtualItem", DbType.Boolean).Value = query.IsVirtualItem.Value; + } + else if (!query.IsVirtualItem.Value) + { + whereClauses.Add("LocationType<>'Virtual'"); + } } if (query.MediaTypes.Length == 1) { From a38c6b2662dcc410dedca3a501f10edfb8e3974e Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 3 Jul 2016 19:55:05 -0400 Subject: [PATCH 4/6] 3.1.55 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 73dead8473..8319e2b26e 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.54")] +[assembly: AssemblyVersion("3.1.55")] From fcc64173f6528e5252079f870afbc5d80bd90bb5 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 3 Jul 2016 20:14:28 -0400 Subject: [PATCH 5/6] update default cache size --- MediaBrowser.Model/Configuration/ServerConfiguration.cs | 4 ++-- .../Persistence/SqliteItemRepository.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 6031ae88e4..081c46f0ae 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -192,7 +192,7 @@ namespace MediaBrowser.Model.Configuration public int MigrationVersion { get; set; } public int SchemaVersion { get; set; } - public int SqliteCacheSizeKb { get; set; } + public int SqliteCacheSize { get; set; } public bool DownloadImagesInAdvance { get; set; } @@ -212,7 +212,7 @@ namespace MediaBrowser.Model.Configuration { LocalNetworkAddresses = new string[] { }; Migrations = new string[] { }; - SqliteCacheSizeKb = 50000; + SqliteCacheSize = 0; EnableCustomPathSubFolders = true; EnableLocalizedGuids = true; diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs index d1b23a0ae8..5b492c240e 100644 --- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs +++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs @@ -123,7 +123,7 @@ namespace MediaBrowser.Server.Implementations.Persistence protected override async Task CreateConnection(bool isReadOnly = false) { - var cacheSize = _config.Configuration.SqliteCacheSizeKb; + var cacheSize = _config.Configuration.SqliteCacheSize; if (cacheSize <= 0) { cacheSize = Math.Min(Environment.ProcessorCount * 50000, 200000); From b73e733a7066922c36cdb30966955f7a17fe2e5f Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Sun, 3 Jul 2016 20:19:21 -0400 Subject: [PATCH 6/6] 3.1.56 --- SharedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SharedVersion.cs b/SharedVersion.cs index 8319e2b26e..bd39b81319 100644 --- a/SharedVersion.cs +++ b/SharedVersion.cs @@ -1,4 +1,4 @@ using System.Reflection; //[assembly: AssemblyVersion("3.1.*")] -[assembly: AssemblyVersion("3.1.55")] +[assembly: AssemblyVersion("3.1.56")]