mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-04 03:27:21 -05:00 
			
		
		
		
	Reworked FFmpeg path discovery and always display to user
1) Reworked FFmpeg and FFprobe path discovery (CLI switch, Custom xml, system $PATH, UI update trigger). Removed FFMpeg folder from Emby.Server.Implementations. All path discovery now in MediaEncoder. 2) Always display FFmpeg path to user in Transcode page. 3) Allow user to remove a Custome FFmpeg path and return to using system $PATH (or --ffmpeg if available). 4) Remove unused code associated with 'prebuilt' FFmpeg. 5) Much improved logging during path discovery.
This commit is contained in:
		
							parent
							
								
									5587dd8bfb
								
							
						
					
					
						commit
						20775116f7
					
				@ -28,7 +28,6 @@ using Emby.Server.Implementations.Data;
 | 
				
			|||||||
using Emby.Server.Implementations.Devices;
 | 
					using Emby.Server.Implementations.Devices;
 | 
				
			||||||
using Emby.Server.Implementations.Diagnostics;
 | 
					using Emby.Server.Implementations.Diagnostics;
 | 
				
			||||||
using Emby.Server.Implementations.Dto;
 | 
					using Emby.Server.Implementations.Dto;
 | 
				
			||||||
using Emby.Server.Implementations.FFMpeg;
 | 
					 | 
				
			||||||
using Emby.Server.Implementations.HttpServer;
 | 
					using Emby.Server.Implementations.HttpServer;
 | 
				
			||||||
using Emby.Server.Implementations.HttpServer.Security;
 | 
					using Emby.Server.Implementations.HttpServer.Security;
 | 
				
			||||||
using Emby.Server.Implementations.IO;
 | 
					using Emby.Server.Implementations.IO;
 | 
				
			||||||
@ -792,7 +791,8 @@ namespace Emby.Server.Implementations
 | 
				
			|||||||
            ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
 | 
					            ChapterManager = new ChapterManager(LibraryManager, LoggerFactory, ServerConfigurationManager, ItemRepository);
 | 
				
			||||||
            serviceCollection.AddSingleton(ChapterManager);
 | 
					            serviceCollection.AddSingleton(ChapterManager);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            RegisterMediaEncoder(serviceCollection);
 | 
					            MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(LoggerFactory, JsonSerializer, StartupOptions.FFmpegPath, StartupOptions.FFprobePath, ServerConfigurationManager, FileSystemManager, () => SubtitleEncoder, () => MediaSourceManager, ProcessFactory, 5000);
 | 
				
			||||||
 | 
					            serviceCollection.AddSingleton(MediaEncoder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
 | 
					            EncodingManager = new MediaEncoder.EncodingManager(FileSystemManager, LoggerFactory, MediaEncoder, ChapterManager, LibraryManager);
 | 
				
			||||||
            serviceCollection.AddSingleton(EncodingManager);
 | 
					            serviceCollection.AddSingleton(EncodingManager);
 | 
				
			||||||
@ -908,83 +908,25 @@ namespace Emby.Server.Implementations
 | 
				
			|||||||
            return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
 | 
					            return new ImageProcessor(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager, ImageEncoder, () => LibraryManager, () => MediaEncoder);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected virtual FFMpegInstallInfo GetFfmpegInstallInfo()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var info = new FFMpegInstallInfo();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Windows builds: http://ffmpeg.zeranoe.com/builds/
 | 
					 | 
				
			||||||
            // Linux builds: http://johnvansickle.com/ffmpeg/
 | 
					 | 
				
			||||||
            // OS X builds: http://ffmpegmac.net/
 | 
					 | 
				
			||||||
            // OS X x64: http://www.evermeet.cx/ffmpeg/
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Linux)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                info.FFMpegFilename = "ffmpeg";
 | 
					 | 
				
			||||||
                info.FFProbeFilename = "ffprobe";
 | 
					 | 
				
			||||||
                info.ArchiveType = "7z";
 | 
					 | 
				
			||||||
                info.Version = "20170308";
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                info.FFMpegFilename = "ffmpeg.exe";
 | 
					 | 
				
			||||||
                info.FFProbeFilename = "ffprobe.exe";
 | 
					 | 
				
			||||||
                info.Version = "20170308";
 | 
					 | 
				
			||||||
                info.ArchiveType = "7z";
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else if (EnvironmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.OSX)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                info.FFMpegFilename = "ffmpeg";
 | 
					 | 
				
			||||||
                info.FFProbeFilename = "ffprobe";
 | 
					 | 
				
			||||||
                info.ArchiveType = "7z";
 | 
					 | 
				
			||||||
                info.Version = "20170308";
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return info;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected virtual FFMpegInfo GetFFMpegInfo()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return new FFMpegLoader(ApplicationPaths, FileSystemManager, GetFfmpegInstallInfo())
 | 
					 | 
				
			||||||
                .GetFFMpegInfo(StartupOptions);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Registers the media encoder.
 | 
					        /// Registers the media encoder.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
        /// <returns>Task.</returns>
 | 
					        /// <returns>Task.</returns>
 | 
				
			||||||
        private void RegisterMediaEncoder(IServiceCollection serviceCollection)
 | 
					        private void RegisterMediaEncoder(IAssemblyInfo assemblyInfo)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            string encoderPath = null;
 | 
					            MediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
 | 
				
			||||||
            string probePath = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var info = GetFFMpegInfo();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            encoderPath = info.EncoderPath;
 | 
					 | 
				
			||||||
            probePath = info.ProbePath;
 | 
					 | 
				
			||||||
            var hasExternalEncoder = string.Equals(info.Version, "external", StringComparison.OrdinalIgnoreCase);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var mediaEncoder = new MediaBrowser.MediaEncoding.Encoder.MediaEncoder(
 | 
					 | 
				
			||||||
                LoggerFactory,
 | 
					                LoggerFactory,
 | 
				
			||||||
                JsonSerializer,
 | 
					                JsonSerializer,
 | 
				
			||||||
                encoderPath,
 | 
					                StartupOptions.FFmpegPath,
 | 
				
			||||||
                probePath,
 | 
					                StartupOptions.FFprobePath,
 | 
				
			||||||
                hasExternalEncoder,
 | 
					 | 
				
			||||||
                ServerConfigurationManager,
 | 
					                ServerConfigurationManager,
 | 
				
			||||||
                FileSystemManager,
 | 
					                FileSystemManager,
 | 
				
			||||||
                LiveTvManager,
 | 
					 | 
				
			||||||
                IsoManager,
 | 
					 | 
				
			||||||
                LibraryManager,
 | 
					 | 
				
			||||||
                ChannelManager,
 | 
					 | 
				
			||||||
                SessionManager,
 | 
					 | 
				
			||||||
                () => SubtitleEncoder,
 | 
					                () => SubtitleEncoder,
 | 
				
			||||||
                () => MediaSourceManager,
 | 
					                () => MediaSourceManager,
 | 
				
			||||||
                HttpClient,
 | 
					 | 
				
			||||||
                ZipClient,
 | 
					 | 
				
			||||||
                ProcessFactory,
 | 
					                ProcessFactory,
 | 
				
			||||||
                5000);
 | 
					                5000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            MediaEncoder = mediaEncoder;
 | 
					            RegisterSingleInstance(MediaEncoder);
 | 
				
			||||||
            serviceCollection.AddSingleton(MediaEncoder);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,24 +0,0 @@
 | 
				
			|||||||
namespace Emby.Server.Implementations.FFMpeg
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    /// <summary>
 | 
					 | 
				
			||||||
    /// Class FFMpegInfo
 | 
					 | 
				
			||||||
    /// </summary>
 | 
					 | 
				
			||||||
    public class FFMpegInfo
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the path.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The path.</value>
 | 
					 | 
				
			||||||
        public string EncoderPath { get; set; }
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the probe path.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The probe path.</value>
 | 
					 | 
				
			||||||
        public string ProbePath { get; set; }
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets or sets the version.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The version.</value>
 | 
					 | 
				
			||||||
        public string Version { get; set; }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,17 +0,0 @@
 | 
				
			|||||||
namespace Emby.Server.Implementations.FFMpeg
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public class FFMpegInstallInfo
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        public string Version { get; set; }
 | 
					 | 
				
			||||||
        public string FFMpegFilename { get; set; }
 | 
					 | 
				
			||||||
        public string FFProbeFilename { get; set; }
 | 
					 | 
				
			||||||
        public string ArchiveType { get; set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public FFMpegInstallInfo()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Version = "Path";
 | 
					 | 
				
			||||||
            FFMpegFilename = "ffmpeg";
 | 
					 | 
				
			||||||
            FFProbeFilename = "ffprobe";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,132 +0,0 @@
 | 
				
			|||||||
using System;
 | 
					 | 
				
			||||||
using System.Collections.Generic;
 | 
					 | 
				
			||||||
using System.IO;
 | 
					 | 
				
			||||||
using System.Linq;
 | 
					 | 
				
			||||||
using MediaBrowser.Common.Configuration;
 | 
					 | 
				
			||||||
using MediaBrowser.Model.IO;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace Emby.Server.Implementations.FFMpeg
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    public class FFMpegLoader
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        private readonly IApplicationPaths _appPaths;
 | 
					 | 
				
			||||||
        private readonly IFileSystem _fileSystem;
 | 
					 | 
				
			||||||
        private readonly FFMpegInstallInfo _ffmpegInstallInfo;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public FFMpegLoader(IApplicationPaths appPaths, IFileSystem fileSystem, FFMpegInstallInfo ffmpegInstallInfo)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            _appPaths = appPaths;
 | 
					 | 
				
			||||||
            _fileSystem = fileSystem;
 | 
					 | 
				
			||||||
            _ffmpegInstallInfo = ffmpegInstallInfo;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public FFMpegInfo GetFFMpegInfo(IStartupOptions options)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var customffMpegPath = options.FFmpegPath;
 | 
					 | 
				
			||||||
            var customffProbePath = options.FFprobePath;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(customffMpegPath) && !string.IsNullOrWhiteSpace(customffProbePath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return new FFMpegInfo
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    ProbePath = customffProbePath,
 | 
					 | 
				
			||||||
                    EncoderPath = customffMpegPath,
 | 
					 | 
				
			||||||
                    Version = "external"
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var downloadInfo = _ffmpegInstallInfo;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var prebuiltFolder = _appPaths.ProgramSystemPath;
 | 
					 | 
				
			||||||
            var prebuiltffmpeg = Path.Combine(prebuiltFolder, downloadInfo.FFMpegFilename);
 | 
					 | 
				
			||||||
            var prebuiltffprobe = Path.Combine(prebuiltFolder, downloadInfo.FFProbeFilename);
 | 
					 | 
				
			||||||
            if (File.Exists(prebuiltffmpeg) && File.Exists(prebuiltffprobe))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return new FFMpegInfo
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    ProbePath = prebuiltffprobe,
 | 
					 | 
				
			||||||
                    EncoderPath = prebuiltffmpeg,
 | 
					 | 
				
			||||||
                    Version = "external"
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var version = downloadInfo.Version;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (string.Equals(version, "0", StringComparison.OrdinalIgnoreCase))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return new FFMpegInfo();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var rootEncoderPath = Path.Combine(_appPaths.ProgramDataPath, "ffmpeg");
 | 
					 | 
				
			||||||
            var versionedDirectoryPath = Path.Combine(rootEncoderPath, version);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var info = new FFMpegInfo
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                ProbePath = Path.Combine(versionedDirectoryPath, downloadInfo.FFProbeFilename),
 | 
					 | 
				
			||||||
                EncoderPath = Path.Combine(versionedDirectoryPath, downloadInfo.FFMpegFilename),
 | 
					 | 
				
			||||||
                Version = version
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Directory.CreateDirectory(versionedDirectoryPath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var excludeFromDeletions = new List<string> { versionedDirectoryPath };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!File.Exists(info.ProbePath) || !File.Exists(info.EncoderPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                // ffmpeg not present. See if there's an older version we can start with
 | 
					 | 
				
			||||||
                var existingVersion = GetExistingVersion(info, rootEncoderPath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // No older version. Need to download and block until complete
 | 
					 | 
				
			||||||
                if (existingVersion == null)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return new FFMpegInfo();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                else
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    info = existingVersion;
 | 
					 | 
				
			||||||
                    versionedDirectoryPath = Path.GetDirectoryName(info.EncoderPath);
 | 
					 | 
				
			||||||
                    excludeFromDeletions.Add(versionedDirectoryPath);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Allow just one of these to be overridden, if desired.
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(customffMpegPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                info.EncoderPath = customffMpegPath;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(customffProbePath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                info.ProbePath = customffProbePath;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return info;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private FFMpegInfo GetExistingVersion(FFMpegInfo info, string rootEncoderPath)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var encoderFilename = Path.GetFileName(info.EncoderPath);
 | 
					 | 
				
			||||||
            var probeFilename = Path.GetFileName(info.ProbePath);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            foreach (var directory in _fileSystem.GetDirectoryPaths(rootEncoderPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var allFiles = _fileSystem.GetFilePaths(directory, true).ToList();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                var encoder = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), encoderFilename, StringComparison.OrdinalIgnoreCase));
 | 
					 | 
				
			||||||
                var probe = allFiles.FirstOrDefault(i => string.Equals(Path.GetFileName(i), probeFilename, StringComparison.OrdinalIgnoreCase));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (!string.IsNullOrWhiteSpace(encoder) &&
 | 
					 | 
				
			||||||
                    !string.IsNullOrWhiteSpace(probe))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return new FFMpegInfo
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        EncoderPath = encoder,
 | 
					 | 
				
			||||||
                        ProbePath = probe,
 | 
					 | 
				
			||||||
                        Version = Path.GetFileName(Path.GetDirectoryName(probe))
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -19,7 +19,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            _processFactory = processFactory;
 | 
					            _processFactory = processFactory;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public (IEnumerable<string> decoders, IEnumerable<string> encoders) Validate(string encoderPath)
 | 
					        public (IEnumerable<string> decoders, IEnumerable<string> encoders) GetAvailableCoders(string encoderPath)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _logger.LogInformation("Validating media encoder at {EncoderPath}", encoderPath);
 | 
					            _logger.LogInformation("Validating media encoder at {EncoderPath}", encoderPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,13 +7,9 @@ using System.Threading;
 | 
				
			|||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using MediaBrowser.Common.Configuration;
 | 
					using MediaBrowser.Common.Configuration;
 | 
				
			||||||
using MediaBrowser.Common.Extensions;
 | 
					using MediaBrowser.Common.Extensions;
 | 
				
			||||||
using MediaBrowser.Common.Net;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Channels;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					using MediaBrowser.Controller.Configuration;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
using MediaBrowser.Controller.LiveTv;
 | 
					 | 
				
			||||||
using MediaBrowser.Controller.MediaEncoding;
 | 
					using MediaBrowser.Controller.MediaEncoding;
 | 
				
			||||||
using MediaBrowser.Controller.Session;
 | 
					 | 
				
			||||||
using MediaBrowser.MediaEncoding.Probing;
 | 
					using MediaBrowser.MediaEncoding.Probing;
 | 
				
			||||||
using MediaBrowser.Model.Configuration;
 | 
					using MediaBrowser.Model.Configuration;
 | 
				
			||||||
using MediaBrowser.Model.Diagnostics;
 | 
					using MediaBrowser.Model.Diagnostics;
 | 
				
			||||||
@ -32,323 +28,288 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
    public class MediaEncoder : IMediaEncoder, IDisposable
 | 
					    public class MediaEncoder : IMediaEncoder, IDisposable
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// The _logger
 | 
					        /// Gets the encoder path.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <value>The encoder path.</value>
 | 
				
			||||||
 | 
					        public string EncoderPath => FFmpegPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// External: path supplied via command line
 | 
				
			||||||
 | 
					        /// Custom: coming from UI or config/encoding.xml file
 | 
				
			||||||
 | 
					        /// System: FFmpeg found in system $PATH
 | 
				
			||||||
 | 
					        /// null: No FFmpeg found
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public string EncoderLocationType { get; private set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private readonly ILogger _logger;
 | 
					        private readonly ILogger _logger;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the json serializer.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The json serializer.</value>
 | 
					 | 
				
			||||||
        private readonly IJsonSerializer _jsonSerializer;
 | 
					        private readonly IJsonSerializer _jsonSerializer;
 | 
				
			||||||
 | 
					        private string FFmpegPath { get; set; }
 | 
				
			||||||
        /// <summary>
 | 
					        private string FFprobePath { get; set; }
 | 
				
			||||||
        /// The _thumbnail resource pool
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public string FFMpegPath { get; private set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public string FFProbePath { get; private set; }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        protected readonly IServerConfigurationManager ConfigurationManager;
 | 
					        protected readonly IServerConfigurationManager ConfigurationManager;
 | 
				
			||||||
        protected readonly IFileSystem FileSystem;
 | 
					        protected readonly IFileSystem FileSystem;
 | 
				
			||||||
        protected readonly ILiveTvManager LiveTvManager;
 | 
					 | 
				
			||||||
        protected readonly IIsoManager IsoManager;
 | 
					 | 
				
			||||||
        protected readonly ILibraryManager LibraryManager;
 | 
					 | 
				
			||||||
        protected readonly IChannelManager ChannelManager;
 | 
					 | 
				
			||||||
        protected readonly ISessionManager SessionManager;
 | 
					 | 
				
			||||||
        protected readonly Func<ISubtitleEncoder> SubtitleEncoder;
 | 
					        protected readonly Func<ISubtitleEncoder> SubtitleEncoder;
 | 
				
			||||||
        protected readonly Func<IMediaSourceManager> MediaSourceManager;
 | 
					        protected readonly Func<IMediaSourceManager> MediaSourceManager;
 | 
				
			||||||
        private readonly IHttpClient _httpClient;
 | 
					 | 
				
			||||||
        private readonly IZipClient _zipClient;
 | 
					 | 
				
			||||||
        private readonly IProcessFactory _processFactory;
 | 
					        private readonly IProcessFactory _processFactory;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
 | 
					 | 
				
			||||||
        private readonly bool _hasExternalEncoder;
 | 
					 | 
				
			||||||
        private readonly string _originalFFMpegPath;
 | 
					 | 
				
			||||||
        private readonly string _originalFFProbePath;
 | 
					 | 
				
			||||||
        private readonly int DefaultImageExtractionTimeoutMs;
 | 
					        private readonly int DefaultImageExtractionTimeoutMs;
 | 
				
			||||||
 | 
					        private readonly string StartupOptionFFmpegPath;
 | 
				
			||||||
 | 
					        private readonly string StartupOptionFFprobePath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(1, 1);
 | 
				
			||||||
 | 
					        private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public MediaEncoder(
 | 
					        public MediaEncoder(
 | 
				
			||||||
            ILoggerFactory loggerFactory,
 | 
					            ILoggerFactory loggerFactory,
 | 
				
			||||||
            IJsonSerializer jsonSerializer,
 | 
					            IJsonSerializer jsonSerializer,
 | 
				
			||||||
            string ffMpegPath,
 | 
					            string startupOptionsFFmpegPath,
 | 
				
			||||||
            string ffProbePath,
 | 
					            string startupOptionsFFprobePath,
 | 
				
			||||||
            bool hasExternalEncoder,
 | 
					 | 
				
			||||||
            IServerConfigurationManager configurationManager,
 | 
					            IServerConfigurationManager configurationManager,
 | 
				
			||||||
            IFileSystem fileSystem,
 | 
					            IFileSystem fileSystem,
 | 
				
			||||||
            ILiveTvManager liveTvManager,
 | 
					 | 
				
			||||||
            IIsoManager isoManager,
 | 
					 | 
				
			||||||
            ILibraryManager libraryManager,
 | 
					 | 
				
			||||||
            IChannelManager channelManager,
 | 
					 | 
				
			||||||
            ISessionManager sessionManager,
 | 
					 | 
				
			||||||
            Func<ISubtitleEncoder> subtitleEncoder,
 | 
					            Func<ISubtitleEncoder> subtitleEncoder,
 | 
				
			||||||
            Func<IMediaSourceManager> mediaSourceManager,
 | 
					            Func<IMediaSourceManager> mediaSourceManager,
 | 
				
			||||||
            IHttpClient httpClient,
 | 
					 | 
				
			||||||
            IZipClient zipClient,
 | 
					 | 
				
			||||||
            IProcessFactory processFactory,
 | 
					            IProcessFactory processFactory,
 | 
				
			||||||
            int defaultImageExtractionTimeoutMs)
 | 
					            int defaultImageExtractionTimeoutMs)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _logger = loggerFactory.CreateLogger(nameof(MediaEncoder));
 | 
					            _logger = loggerFactory.CreateLogger(nameof(MediaEncoder));
 | 
				
			||||||
            _jsonSerializer = jsonSerializer;
 | 
					            _jsonSerializer = jsonSerializer;
 | 
				
			||||||
 | 
					            StartupOptionFFmpegPath = startupOptionsFFmpegPath;
 | 
				
			||||||
 | 
					            StartupOptionFFprobePath = startupOptionsFFprobePath;
 | 
				
			||||||
            ConfigurationManager = configurationManager;
 | 
					            ConfigurationManager = configurationManager;
 | 
				
			||||||
            FileSystem = fileSystem;
 | 
					            FileSystem = fileSystem;
 | 
				
			||||||
            LiveTvManager = liveTvManager;
 | 
					 | 
				
			||||||
            IsoManager = isoManager;
 | 
					 | 
				
			||||||
            LibraryManager = libraryManager;
 | 
					 | 
				
			||||||
            ChannelManager = channelManager;
 | 
					 | 
				
			||||||
            SessionManager = sessionManager;
 | 
					 | 
				
			||||||
            SubtitleEncoder = subtitleEncoder;
 | 
					            SubtitleEncoder = subtitleEncoder;
 | 
				
			||||||
            MediaSourceManager = mediaSourceManager;
 | 
					 | 
				
			||||||
            _httpClient = httpClient;
 | 
					 | 
				
			||||||
            _zipClient = zipClient;
 | 
					 | 
				
			||||||
            _processFactory = processFactory;
 | 
					            _processFactory = processFactory;
 | 
				
			||||||
            DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
 | 
					            DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
 | 
				
			||||||
            FFProbePath = ffProbePath;
 | 
					 | 
				
			||||||
            FFMpegPath = ffMpegPath;
 | 
					 | 
				
			||||||
            _originalFFProbePath = ffProbePath;
 | 
					 | 
				
			||||||
            _originalFFMpegPath = ffMpegPath;
 | 
					 | 
				
			||||||
            _hasExternalEncoder = hasExternalEncoder;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public string EncoderLocationType
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Run at startup or if the user removes a Custom path from transcode page.
 | 
				
			||||||
 | 
					        /// Sets global variables FFmpegPath and EncoderLocationType.
 | 
				
			||||||
 | 
					        /// If startup options --ffprobe is given then FFprobePath is set too.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        public void Init()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            get
 | 
					            // 1) If given, use the --ffmpeg CLI switch
 | 
				
			||||||
 | 
					            if (ValidatePathFFmpeg("From CLI Switch", StartupOptionFFmpegPath))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                if (_hasExternalEncoder)
 | 
					                _logger.LogInformation("FFmpeg: Using path from command line switch --ffmpeg");
 | 
				
			||||||
 | 
					                EncoderLocationType = "External";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 2) Try Custom path stroed in config/encoding xml file under tag <EncoderAppPathCustom>
 | 
				
			||||||
 | 
					            else if (ValidatePathFFmpeg("From Config File", ConfigurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPathCustom))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogInformation("FFmpeg: Using path from config/encoding.xml file");
 | 
				
			||||||
 | 
					                EncoderLocationType = "Custom";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 3) Search system $PATH environment variable for valid FFmpeg
 | 
				
			||||||
 | 
					            else if (ValidatePathFFmpeg("From $PATH", ExistsOnSystemPath("ffmpeg")))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogInformation("FFmpeg: Using system $PATH for FFmpeg");
 | 
				
			||||||
 | 
					                EncoderLocationType = "System";
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogError("FFmpeg: No suitable executable found");
 | 
				
			||||||
 | 
					                FFmpegPath = null;
 | 
				
			||||||
 | 
					                EncoderLocationType = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // If given, use the --ffprobe CLI switch
 | 
				
			||||||
 | 
					            if (ValidatePathFFprobe("CLI Switch", StartupOptionFFprobePath))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                _logger.LogInformation("FFprobe: Using path from command line switch --ffprobe");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // FFprobe path from command line is no good, so set to null and let ReInit() try
 | 
				
			||||||
 | 
					                // and set using the FFmpeg path.
 | 
				
			||||||
 | 
					                FFprobePath = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ReInit();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Writes the currently used FFmpeg to config/encoding.xml file.
 | 
				
			||||||
 | 
					        /// Sets the FFprobe path if not currently set.
 | 
				
			||||||
 | 
					        /// Interrogates the FFmpeg tool to identify what encoders/decodres are available.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        private void ReInit()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Write the FFmpeg path to the config/encoding.xml file so it appears in UI
 | 
				
			||||||
 | 
					            var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
 | 
				
			||||||
 | 
					            config.EncoderAppPath = FFmpegPath ?? string.Empty;
 | 
				
			||||||
 | 
					            ConfigurationManager.SaveConfiguration("encoding", config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Only if mpeg path is set, try and set path to probe
 | 
				
			||||||
 | 
					            if (FFmpegPath != null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                // Probe would be null here if no valid --ffprobe path was given
 | 
				
			||||||
 | 
					                // at startup, or we're performing ReInit following mpeg path update from UI
 | 
				
			||||||
 | 
					                if (FFprobePath == null)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    return "External";
 | 
					                    // Use the mpeg path to create a probe path
 | 
				
			||||||
 | 
					                    if (ValidatePathFFprobe("Copied from FFmpeg:", GetProbePathFromEncoderPath(FFmpegPath)))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogInformation("FFprobe: Using FFprobe in same folders as FFmpeg");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogError("FFprobe: No suitable executable found");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (string.IsNullOrWhiteSpace(FFMpegPath))
 | 
					                // Interrogate to understand what coders it supports
 | 
				
			||||||
                {
 | 
					                var result = new EncoderValidator(_logger, _processFactory).GetAvailableCoders(FFmpegPath);
 | 
				
			||||||
                    return null;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (IsSystemInstalledPath(FFMpegPath))
 | 
					                SetAvailableDecoders(result.decoders);
 | 
				
			||||||
                {
 | 
					                SetAvailableEncoders(result.encoders);
 | 
				
			||||||
                    return "System";
 | 
					            }
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return "Custom";
 | 
					            // Stamp FFmpeg paths to the log file
 | 
				
			||||||
 | 
					            LogPaths();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Triggered from the Settings > Trascoding UI page when users sumits Custom FFmpeg path to use.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="path"></param>
 | 
				
			||||||
 | 
					        /// <param name="pathType"></param>
 | 
				
			||||||
 | 
					        public void UpdateEncoderPath(string path, string pathType)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            _logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                throw new ArgumentException("Unexpected pathType value");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (string.IsNullOrWhiteSpace(path))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // User had cleared the cutom path in UI.  Clear the Custom config
 | 
				
			||||||
 | 
					                    // setting and peform full Init to relook any CLI switches and system $PATH
 | 
				
			||||||
 | 
					                    var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
 | 
				
			||||||
 | 
					                    config.EncoderAppPathCustom = string.Empty;
 | 
				
			||||||
 | 
					                    ConfigurationManager.SaveConfiguration("encoding", config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Init();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if (!File.Exists(path) && !Directory.Exists(path))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // Given path is neither file or folder
 | 
				
			||||||
 | 
					                    throw new ResourceNotFoundException();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    // Supplied path could be either file path or folder path.
 | 
				
			||||||
 | 
					                    // Resolve down to file path and validate
 | 
				
			||||||
 | 
					                    path = GetEncoderPath(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (path == null)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        throw new ResourceNotFoundException("FFmpeg not found");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else if (!ValidatePathFFmpeg("New From UI", path))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        throw new ResourceNotFoundException("Failed validation checks.  Version 4.0 or greater is required");
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        EncoderLocationType = "Custom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Write the validated mpeg path to the xml as <EncoderAppPathCustom>
 | 
				
			||||||
 | 
					                        // This ensures its not lost on new startup
 | 
				
			||||||
 | 
					                        var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
 | 
				
			||||||
 | 
					                        config.EncoderAppPathCustom = FFmpegPath;
 | 
				
			||||||
 | 
					                        ConfigurationManager.SaveConfiguration("encoding", config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        FFprobePath = null; // Clear probe path so it gets relooked in ReInit()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        ReInit();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private bool IsSystemInstalledPath(string path)
 | 
					        private bool ValidatePath(string type, string path)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1)
 | 
					            if (!string.IsNullOrEmpty(path))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
					                if (File.Exists(path))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    var valid = new EncoderValidator(_logger, _processFactory).ValidateVersion(path, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (valid == true)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        return true;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        _logger.LogError("{0}: Failed validation checks.  Version 4.0 or greater is required: {1}", type, path);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    _logger.LogError("{0}: File not found: {1}", type, path);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private bool ValidatePathFFmpeg(string comment, string path)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (ValidatePath("FFmpeg: " + comment, path) == true)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                FFmpegPath = path;
 | 
				
			||||||
                return true;
 | 
					                return true;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void Init()
 | 
					        private bool ValidatePathFFprobe(string comment, string path)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            InitPaths();
 | 
					            if (ValidatePath("FFprobe: " + comment, path) == true)
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(FFMpegPath))
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
 | 
					                FFprobePath = path;
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
                SetAvailableDecoders(result.decoders);
 | 
					 | 
				
			||||||
                SetAvailableEncoders(result.encoders);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void InitPaths()
 | 
					        private string GetEncoderPath(string path)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            ConfigureEncoderPaths();
 | 
					            if (Directory.Exists(path))
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (_hasExternalEncoder)
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                LogPaths();
 | 
					                return GetEncoderPathFromDirectory(path);
 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // If the path was passed in, save it into config now.
 | 
					            if (File.Exists(path))
 | 
				
			||||||
            var encodingOptions = GetEncodingOptions();
 | 
					 | 
				
			||||||
            var appPath = encodingOptions.EncoderAppPath;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var valueToSave = FFMpegPath;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(valueToSave))
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // if using system variable, don't save this.
 | 
					                return path;
 | 
				
			||||||
                if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    valueToSave = null;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal))
 | 
					            return null;
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                encodingOptions.EncoderAppPath = valueToSave;
 | 
					 | 
				
			||||||
                ConfigurationManager.SaveConfiguration("encoding", encodingOptions);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public void UpdateEncoderPath(string path, string pathType)
 | 
					        private string GetEncoderPathFromDirectory(string path)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (_hasExternalEncoder)
 | 
					            try
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return;
 | 
					                var files = FileSystem.GetFilePaths(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                var excludeExtensions = new[] { ".c" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception)
 | 
				
			||||||
            _logger.LogInformation("Attempting to update encoder path to {0}. pathType: {1}", path ?? string.Empty, pathType ?? string.Empty);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Tuple<string, string> newPaths;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (string.Equals(pathType, "system", StringComparison.OrdinalIgnoreCase))
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                path = "ffmpeg";
 | 
					                // Trap all exceptions, like DirNotExists, and return null
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
                newPaths = TestForInstalledVersions();
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (string.IsNullOrWhiteSpace(path))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    throw new ArgumentNullException(nameof(path));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (!File.Exists(path) && !Directory.Exists(path))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    throw new ResourceNotFoundException();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                newPaths = GetEncoderPaths(path);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                throw new ArgumentException("Unexpected pathType value");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(newPaths.Item1))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                throw new ResourceNotFoundException("ffmpeg not found");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(newPaths.Item2))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                throw new ResourceNotFoundException("ffprobe not found");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            path = newPaths.Item1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!ValidateVersion(path, true))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                throw new ResourceNotFoundException("ffmpeg version 3.0 or greater is required.");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var config = GetEncodingOptions();
 | 
					 | 
				
			||||||
            config.EncoderAppPath = path;
 | 
					 | 
				
			||||||
            ConfigurationManager.SaveConfiguration("encoding", config);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Init();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private bool ValidateVersion(string path, bool logOutput)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return new EncoderValidator(_logger, _processFactory).ValidateVersion(path, logOutput);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private void ConfigureEncoderPaths()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (_hasExternalEncoder)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var appPath = GetEncodingOptions().EncoderAppPath;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(appPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                appPath = Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "ffmpeg");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var newPaths = GetEncoderPaths(appPath);
 | 
					 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(newPaths.Item1) || string.IsNullOrWhiteSpace(newPaths.Item2) || IsSystemInstalledPath(appPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                newPaths = TestForInstalledVersions();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(newPaths.Item1) && !string.IsNullOrWhiteSpace(newPaths.Item2))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                FFMpegPath = newPaths.Item1;
 | 
					 | 
				
			||||||
                FFProbePath = newPaths.Item2;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            LogPaths();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private Tuple<string, string> GetEncoderPaths(string configuredPath)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var appPath = configuredPath;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!string.IsNullOrWhiteSpace(appPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (Directory.Exists(appPath))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return GetPathsFromDirectory(appPath);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (File.Exists(appPath))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    return new Tuple<string, string>(appPath, GetProbePathFromEncoderPath(appPath));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return new Tuple<string, string>(null, null);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private Tuple<string, string> TestForInstalledVersions()
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            string encoderPath = null;
 | 
					 | 
				
			||||||
            string probePath = null;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (_hasExternalEncoder && ValidateVersion(_originalFFMpegPath, true))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                encoderPath = _originalFFMpegPath;
 | 
					 | 
				
			||||||
                probePath = _originalFFProbePath;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(encoderPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (ValidateVersion("ffmpeg", true) && ValidateVersion("ffprobe", false))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    encoderPath = "ffmpeg";
 | 
					 | 
				
			||||||
                    probePath = "ffprobe";
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return new Tuple<string, string>(encoderPath, probePath);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private Tuple<string, string> GetPathsFromDirectory(string path)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            // Since we can't predict the file extension, first try directly within the folder
 | 
					 | 
				
			||||||
            // If that doesn't pan out, then do a recursive search
 | 
					 | 
				
			||||||
            var files = FileSystem.GetFilePaths(path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var excludeExtensions = new[] { ".c" };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
 | 
					 | 
				
			||||||
            var ffprobePath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (string.IsNullOrWhiteSpace(ffmpegPath) || !File.Exists(ffmpegPath))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                files = FileSystem.GetFilePaths(path, true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ffmpegPath = files.FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffmpeg", StringComparison.OrdinalIgnoreCase) && !excludeExtensions.Contains(Path.GetExtension(i) ?? string.Empty));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (!string.IsNullOrWhiteSpace(ffmpegPath))
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    ffprobePath = GetProbePathFromEncoderPath(ffmpegPath);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return new Tuple<string, string>(ffmpegPath, ffprobePath);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private string GetProbePathFromEncoderPath(string appPath)
 | 
					        private string GetProbePathFromEncoderPath(string appPath)
 | 
				
			||||||
@ -357,15 +318,31 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
                .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase));
 | 
					                .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private void LogPaths()
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Search the system $PATH environment variable looking for given filename.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="fileName"></param>
 | 
				
			||||||
 | 
					        /// <returns></returns>
 | 
				
			||||||
 | 
					        private string ExistsOnSystemPath(string fileName)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _logger.LogInformation("FFMpeg: {0}", FFMpegPath ?? "not found");
 | 
					            var values = Environment.GetEnvironmentVariable("PATH");
 | 
				
			||||||
            _logger.LogInformation("FFProbe: {0}", FFProbePath ?? "not found");
 | 
					
 | 
				
			||||||
 | 
					            foreach (var path in values.Split(Path.PathSeparator))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                var candidatePath = GetEncoderPathFromDirectory(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (ValidatePath("Found on PATH", candidatePath))
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    return candidatePath;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private EncodingOptions GetEncodingOptions()
 | 
					        private void LogPaths()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
 | 
					            _logger.LogInformation("FFMpeg: {0}", FFmpegPath ?? "not found");
 | 
				
			||||||
 | 
					            _logger.LogInformation("FFProbe: {0}", FFprobePath ?? "not found");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        private List<string> _encoders = new List<string>();
 | 
					        private List<string> _encoders = new List<string>();
 | 
				
			||||||
@ -412,12 +389,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            return true;
 | 
					            return true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					 | 
				
			||||||
        /// Gets the encoder path.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <value>The encoder path.</value>
 | 
					 | 
				
			||||||
        public string EncoderPath => FFMpegPath;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the media info.
 | 
					        /// Gets the media info.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
@ -489,7 +460,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
 | 
					                // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
 | 
				
			||||||
                RedirectStandardOutput = true,
 | 
					                RedirectStandardOutput = true,
 | 
				
			||||||
                FileName = FFProbePath,
 | 
					                FileName = FFprobePath,
 | 
				
			||||||
                Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(),
 | 
					                Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                IsHidden = true,
 | 
					                IsHidden = true,
 | 
				
			||||||
@ -691,7 +662,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                CreateNoWindow = true,
 | 
					                CreateNoWindow = true,
 | 
				
			||||||
                UseShellExecute = false,
 | 
					                UseShellExecute = false,
 | 
				
			||||||
                FileName = FFMpegPath,
 | 
					                FileName = FFmpegPath,
 | 
				
			||||||
                Arguments = args,
 | 
					                Arguments = args,
 | 
				
			||||||
                IsHidden = true,
 | 
					                IsHidden = true,
 | 
				
			||||||
                ErrorDialog = false,
 | 
					                ErrorDialog = false,
 | 
				
			||||||
@ -814,7 +785,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                CreateNoWindow = true,
 | 
					                CreateNoWindow = true,
 | 
				
			||||||
                UseShellExecute = false,
 | 
					                UseShellExecute = false,
 | 
				
			||||||
                FileName = FFMpegPath,
 | 
					                FileName = FFmpegPath,
 | 
				
			||||||
                Arguments = args,
 | 
					                Arguments = args,
 | 
				
			||||||
                IsHidden = true,
 | 
					                IsHidden = true,
 | 
				
			||||||
                ErrorDialog = false,
 | 
					                ErrorDialog = false,
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,8 @@ namespace MediaBrowser.Model.Configuration
 | 
				
			|||||||
        public bool EnableThrottling { get; set; }
 | 
					        public bool EnableThrottling { get; set; }
 | 
				
			||||||
        public int ThrottleDelaySeconds { get; set; }
 | 
					        public int ThrottleDelaySeconds { get; set; }
 | 
				
			||||||
        public string HardwareAccelerationType { get; set; }
 | 
					        public string HardwareAccelerationType { get; set; }
 | 
				
			||||||
        public string EncoderAppPath { get; set; }
 | 
					        public string EncoderAppPathCustom { get; set; } // FFmpeg path as set by the user via the UI
 | 
				
			||||||
 | 
					        public string EncoderAppPath { get; set; } // The current FFmpeg path being used by the system
 | 
				
			||||||
        public string VaapiDevice { get; set; }
 | 
					        public string VaapiDevice { get; set; }
 | 
				
			||||||
        public int H264Crf { get; set; }
 | 
					        public int H264Crf { get; set; }
 | 
				
			||||||
        public string H264Preset { get; set; }
 | 
					        public string H264Preset { get; set; }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user