aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding/Encoder
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder')
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs62
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs375
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs219
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs197
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs305
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs70
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs182
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs1089
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs66
9 files changed, 0 insertions, 2565 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
deleted file mode 100644
index 566e7946d..000000000
--- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
+++ /dev/null
@@ -1,62 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using System;
-using MediaBrowser.Model.Diagnostics;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
- public class AudioEncoder : BaseEncoder
- {
- public AudioEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory)
- {
- }
-
- protected override string GetCommandLineArguments(EncodingJob state)
- {
- var encodingOptions = GetEncodingOptions();
-
- return EncodingHelper.GetProgressiveAudioFullCommandLine(state, encodingOptions, state.OutputFilePath);
- }
-
- protected override string GetOutputFileExtension(EncodingJob state)
- {
- var ext = base.GetOutputFileExtension(state);
-
- if (!string.IsNullOrEmpty(ext))
- {
- return ext;
- }
-
- var audioCodec = state.Options.AudioCodec;
-
- if (string.Equals("aac", audioCodec, StringComparison.OrdinalIgnoreCase))
- {
- return ".aac";
- }
- if (string.Equals("mp3", audioCodec, StringComparison.OrdinalIgnoreCase))
- {
- return ".mp3";
- }
- if (string.Equals("vorbis", audioCodec, StringComparison.OrdinalIgnoreCase))
- {
- return ".ogg";
- }
- if (string.Equals("wma", audioCodec, StringComparison.OrdinalIgnoreCase))
- {
- return ".wma";
- }
-
- return null;
- }
-
- protected override bool IsVideoEncoder
- {
- get { return false; }
- }
-
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
deleted file mode 100644
index 3672e4e84..000000000
--- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
+++ /dev/null
@@ -1,375 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.MediaInfo;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Diagnostics;
-using MediaBrowser.Model.Dlna;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
- public abstract class BaseEncoder
- {
- protected readonly MediaEncoder MediaEncoder;
- protected readonly ILogger Logger;
- protected readonly IServerConfigurationManager ConfigurationManager;
- protected readonly IFileSystem FileSystem;
- protected readonly IIsoManager IsoManager;
- protected readonly ILibraryManager LibraryManager;
- protected readonly ISessionManager SessionManager;
- protected readonly ISubtitleEncoder SubtitleEncoder;
- protected readonly IMediaSourceManager MediaSourceManager;
- protected IProcessFactory ProcessFactory;
-
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- protected EncodingHelper EncodingHelper;
-
- protected BaseEncoder(MediaEncoder mediaEncoder,
- ILogger logger,
- IServerConfigurationManager configurationManager,
- IFileSystem fileSystem,
- IIsoManager isoManager,
- ILibraryManager libraryManager,
- ISessionManager sessionManager,
- ISubtitleEncoder subtitleEncoder,
- IMediaSourceManager mediaSourceManager, IProcessFactory processFactory)
- {
- MediaEncoder = mediaEncoder;
- Logger = logger;
- ConfigurationManager = configurationManager;
- FileSystem = fileSystem;
- IsoManager = isoManager;
- LibraryManager = libraryManager;
- SessionManager = sessionManager;
- SubtitleEncoder = subtitleEncoder;
- MediaSourceManager = mediaSourceManager;
- ProcessFactory = processFactory;
-
- EncodingHelper = new EncodingHelper(MediaEncoder, ConfigurationManager, FileSystem, SubtitleEncoder);
- }
-
- public async Task<EncodingJob> Start(EncodingJobOptions options,
- IProgress<double> progress,
- CancellationToken cancellationToken)
- {
- var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager, MediaEncoder)
- .CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false);
-
- encodingJob.OutputFilePath = GetOutputFilePath(encodingJob);
- FileSystem.CreateDirectory(FileSystem.GetDirectoryName(encodingJob.OutputFilePath));
-
- encodingJob.ReadInputAtNativeFramerate = options.ReadInputAtNativeFramerate;
-
- await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false);
-
- var commandLineArgs = GetCommandLineArguments(encodingJob);
-
- var process = ProcessFactory.Create(new ProcessOptions
- {
- CreateNoWindow = true,
- UseShellExecute = false,
-
- // Must consume both stdout and stderr or deadlocks may occur
- //RedirectStandardOutput = true,
- RedirectStandardError = true,
- RedirectStandardInput = true,
-
- FileName = MediaEncoder.EncoderPath,
- Arguments = commandLineArgs,
-
- IsHidden = true,
- ErrorDialog = false,
- EnableRaisingEvents = true
- });
-
- var workingDirectory = GetWorkingDirectory(options);
- if (!string.IsNullOrWhiteSpace(workingDirectory))
- {
- process.StartInfo.WorkingDirectory = workingDirectory;
- }
-
- OnTranscodeBeginning(encodingJob);
-
- var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
- Logger.Info(commandLineLogMessage);
-
- var logFilePath = Path.Combine(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath, "transcode-" + Guid.NewGuid() + ".txt");
- FileSystem.CreateDirectory(FileSystem.GetDirectoryName(logFilePath));
-
- // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- encodingJob.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
-
- var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(commandLineLogMessage + Environment.NewLine + Environment.NewLine);
- await encodingJob.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationToken).ConfigureAwait(false);
-
- process.Exited += (sender, args) => OnFfMpegProcessExited(process, encodingJob);
-
- try
- {
- process.Start();
- }
- catch (Exception ex)
- {
- Logger.ErrorException("Error starting ffmpeg", ex);
-
- OnTranscodeFailedToStart(encodingJob.OutputFilePath, encodingJob);
-
- throw;
- }
-
- cancellationToken.Register(() => Cancel(process, encodingJob));
-
- // MUST read both stdout and stderr asynchronously or a deadlock may occurr
- //process.BeginOutputReadLine();
-
- // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
- new JobLogger(Logger).StartStreamingLog(encodingJob, process.StandardError.BaseStream, encodingJob.LogFileStream);
-
- // Wait for the file to exist before proceeeding
- while (!FileSystem.FileExists(encodingJob.OutputFilePath) && !encodingJob.HasExited)
- {
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
- }
-
- return encodingJob;
- }
-
- private void Cancel(IProcess process, EncodingJob job)
- {
- Logger.Info("Killing ffmpeg process for {0}", job.OutputFilePath);
-
- //process.Kill();
- process.StandardInput.WriteLine("q");
-
- job.IsCancelled = true;
- }
-
- /// <summary>
- /// Processes the exited.
- /// </summary>
- /// <param name="process">The process.</param>
- /// <param name="job">The job.</param>
- private void OnFfMpegProcessExited(IProcess process, EncodingJob job)
- {
- job.HasExited = true;
-
- Logger.Debug("Disposing stream resources");
- job.Dispose();
-
- var isSuccesful = false;
-
- try
- {
- var exitCode = process.ExitCode;
- Logger.Info("FFMpeg exited with code {0}", exitCode);
-
- isSuccesful = exitCode == 0;
- }
- catch
- {
- Logger.Error("FFMpeg exited with an error.");
- }
-
- if (isSuccesful && !job.IsCancelled)
- {
- job.TaskCompletionSource.TrySetResult(true);
- }
- else if (job.IsCancelled)
- {
- try
- {
- DeleteFiles(job);
- }
- catch
- {
- }
- try
- {
- job.TaskCompletionSource.TrySetException(new OperationCanceledException());
- }
- catch
- {
- }
- }
- else
- {
- try
- {
- DeleteFiles(job);
- }
- catch
- {
- }
- try
- {
- job.TaskCompletionSource.TrySetException(new Exception("Encoding failed"));
- }
- catch
- {
- }
- }
-
- // This causes on exited to be called twice:
- //try
- //{
- // // Dispose the process
- // process.Dispose();
- //}
- //catch (Exception ex)
- //{
- // Logger.ErrorException("Error disposing ffmpeg.", ex);
- //}
- }
-
- protected virtual void DeleteFiles(EncodingJob job)
- {
- FileSystem.DeleteFile(job.OutputFilePath);
- }
-
- private void OnTranscodeBeginning(EncodingJob job)
- {
- job.ReportTranscodingProgress(null, null, null, null, null);
- }
-
- private void OnTranscodeFailedToStart(string path, EncodingJob job)
- {
- if (!string.IsNullOrWhiteSpace(job.Options.DeviceId))
- {
- SessionManager.ClearTranscodingInfo(job.Options.DeviceId);
- }
- }
-
- protected abstract bool IsVideoEncoder { get; }
-
- protected virtual string GetWorkingDirectory(EncodingJobOptions options)
- {
- return null;
- }
-
- protected EncodingOptions GetEncodingOptions()
- {
- return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
- }
-
- protected abstract string GetCommandLineArguments(EncodingJob job);
-
- private string GetOutputFilePath(EncodingJob state)
- {
- var folder = string.IsNullOrWhiteSpace(state.Options.OutputDirectory) ?
- ConfigurationManager.ApplicationPaths.TranscodingTempPath :
- state.Options.OutputDirectory;
-
- var outputFileExtension = GetOutputFileExtension(state);
-
- var filename = state.Id + (outputFileExtension ?? string.Empty).ToLower();
- return Path.Combine(folder, filename);
- }
-
- protected virtual string GetOutputFileExtension(EncodingJob state)
- {
- if (!string.IsNullOrWhiteSpace(state.Options.OutputContainer))
- {
- return "." + state.Options.OutputContainer;
- }
-
- return null;
- }
-
- /// <summary>
- /// Gets the name of the output video codec
- /// </summary>
- /// <param name="state">The state.</param>
- /// <returns>System.String.</returns>
- protected string GetVideoDecoder(EncodingJob state)
- {
- if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- return null;
- }
-
- // Only use alternative encoders for video files.
- // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
- // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
- if (state.VideoType != VideoType.VideoFile)
- {
- return null;
- }
-
- if (state.VideoStream != null && !string.IsNullOrWhiteSpace(state.VideoStream.Codec))
- {
- if (string.Equals(GetEncodingOptions().HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
- {
- switch (state.MediaSource.VideoStream.Codec.ToLower())
- {
- case "avc":
- case "h264":
- if (MediaEncoder.SupportsDecoder("h264_qsv"))
- {
- // Seeing stalls and failures with decoding. Not worth it compared to encoding.
- return "-c:v h264_qsv ";
- }
- break;
- case "mpeg2video":
- if (MediaEncoder.SupportsDecoder("mpeg2_qsv"))
- {
- return "-c:v mpeg2_qsv ";
- }
- break;
- case "vc1":
- if (MediaEncoder.SupportsDecoder("vc1_qsv"))
- {
- return "-c:v vc1_qsv ";
- }
- break;
- }
- }
- }
-
- // leave blank so ffmpeg will decide
- return null;
- }
-
- private async Task AcquireResources(EncodingJob state, CancellationToken cancellationToken)
- {
- if (state.VideoType == VideoType.Iso && state.IsoType.HasValue && IsoManager.CanMount(state.MediaPath))
- {
- state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationToken).ConfigureAwait(false);
- }
-
- if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Options.LiveStreamId))
- {
- var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
- {
- OpenToken = state.MediaSource.OpenToken
-
- }, cancellationToken).ConfigureAwait(false);
-
- EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, null);
-
- if (state.IsVideoRequest)
- {
- EncodingHelper.TryStreamCopy(state);
- }
- }
-
- if (state.MediaSource.BufferMs.HasValue)
- {
- await Task.Delay(state.MediaSource.BufferMs.Value, cancellationToken).ConfigureAwait(false);
- }
- }
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
deleted file mode 100644
index 59f3576ec..000000000
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ /dev/null
@@ -1,219 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using MediaBrowser.Model.Diagnostics;
-using MediaBrowser.Model.Logging;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
- public class EncoderValidator
- {
- private readonly ILogger _logger;
- private readonly IProcessFactory _processFactory;
-
- public EncoderValidator(ILogger logger, IProcessFactory processFactory)
- {
- _logger = logger;
- _processFactory = processFactory;
- }
-
- public Tuple<List<string>, List<string>> Validate(string encoderPath)
- {
- _logger.Info("Validating media encoder at {0}", encoderPath);
-
- var decoders = GetDecoders(encoderPath);
- var encoders = GetEncoders(encoderPath);
-
- _logger.Info("Encoder validation complete");
-
- return new Tuple<List<string>, List<string>>(decoders, encoders);
- }
-
- public bool ValidateVersion(string encoderAppPath, bool logOutput)
- {
- string output = string.Empty;
- try
- {
- output = GetProcessOutput(encoderAppPath, "-version");
- }
- catch (Exception ex)
- {
- if (logOutput)
- {
- _logger.ErrorException("Error validating encoder", ex);
- }
- }
-
- if (string.IsNullOrWhiteSpace(output))
- {
- return false;
- }
-
- _logger.Info("ffmpeg info: {0}", output);
-
- if (output.IndexOf("Libav developers", StringComparison.OrdinalIgnoreCase) != -1)
- {
- return false;
- }
-
- output = " " + output + " ";
-
- for (var i = 2013; i <= 2015; i++)
- {
- var yearString = i.ToString(CultureInfo.InvariantCulture);
- if (output.IndexOf(" " + yearString + " ", StringComparison.OrdinalIgnoreCase) != -1)
- {
- return false;
- }
- }
-
- return true;
- }
-
- private List<string> GetDecoders(string encoderAppPath)
- {
- string output = string.Empty;
- try
- {
- output = GetProcessOutput(encoderAppPath, "-decoders");
- }
- catch (Exception )
- {
- //_logger.ErrorException("Error detecting available decoders", ex);
- }
-
- var found = new List<string>();
- var required = new[]
- {
- "mpeg2video",
- "h264_qsv",
- "hevc_qsv",
- "mpeg2_qsv",
- "vc1_qsv",
- "h264_cuvid",
- "hevc_cuvid",
- "dts",
- "ac3",
- "aac",
- "mp3",
- "h264",
- "hevc"
- };
-
- foreach (var codec in required)
- {
- var srch = " " + codec + " ";
-
- if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1)
- {
- _logger.Info("Decoder available: " + codec);
- found.Add(codec);
- }
- }
-
- return found;
- }
-
- private List<string> GetEncoders(string encoderAppPath)
- {
- string output = null;
- try
- {
- output = GetProcessOutput(encoderAppPath, "-encoders");
- }
- catch
- {
- }
-
- var found = new List<string>();
- var required = new[]
- {
- "libx264",
- "libx265",
- "mpeg4",
- "msmpeg4",
- "libvpx",
- "libvpx-vp9",
- "aac",
- "libmp3lame",
- "libopus",
- "libvorbis",
- "srt",
- "h264_nvenc",
- "hevc_nvenc",
- "h264_qsv",
- "hevc_qsv",
- "h264_omx",
- "hevc_omx",
- "h264_vaapi",
- "hevc_vaapi",
- "ac3"
- };
-
- output = output ?? string.Empty;
-
- var index = 0;
-
- foreach (var codec in required)
- {
- var srch = " " + codec + " ";
-
- if (output.IndexOf(srch, StringComparison.OrdinalIgnoreCase) != -1)
- {
- if (index < required.Length - 1)
- {
- _logger.Info("Encoder available: " + codec);
- }
-
- found.Add(codec);
- }
- index++;
- }
-
- return found;
- }
-
- private string GetProcessOutput(string path, string arguments)
- {
- var process = _processFactory.Create(new ProcessOptions
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = path,
- Arguments = arguments,
- IsHidden = true,
- ErrorDialog = false,
- RedirectStandardOutput = true
- });
-
- _logger.Info("Running {0} {1}", path, arguments);
-
- using (process)
- {
- process.Start();
-
- try
- {
- return process.StandardOutput.ReadToEnd();
- }
- catch
- {
- _logger.Info("Killing process {0} {1}", path, arguments);
-
- // Hate having to do this
- try
- {
- process.Kill();
- }
- catch (Exception ex1)
- {
- _logger.ErrorException("Error killing process", ex1);
- }
-
- throw;
- }
- }
- }
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
deleted file mode 100644
index d53701feb..000000000
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs
+++ /dev/null
@@ -1,197 +0,0 @@
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Drawing;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Net;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
- public class EncodingJob : EncodingJobInfo, IDisposable
- {
- public bool HasExited { get; internal set; }
- public bool IsCancelled { get; internal set; }
-
- public Stream LogFileStream { get; set; }
- public IProgress<double> Progress { get; set; }
- public TaskCompletionSource<bool> TaskCompletionSource;
-
- public EncodingJobOptions Options
- {
- get { return (EncodingJobOptions) BaseRequest; }
- set { BaseRequest = value; }
- }
-
- public string Id { get; set; }
-
- public string MimeType { get; set; }
- public bool EstimateContentLength { get; set; }
- public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
- public long? EncodingDurationTicks { get; set; }
-
- public string ItemType { get; set; }
-
- public string GetMimeType(string outputPath)
- {
- if (!string.IsNullOrEmpty(MimeType))
- {
- return MimeType;
- }
-
- return MimeTypes.GetMimeType(outputPath);
- }
-
- private readonly ILogger _logger;
- private readonly IMediaSourceManager _mediaSourceManager;
-
- public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) :
- base(logger, TranscodingJobType.Progressive)
- {
- _logger = logger;
- _mediaSourceManager = mediaSourceManager;
- Id = Guid.NewGuid().ToString("N");
-
- _logger = logger;
- TaskCompletionSource = new TaskCompletionSource<bool>();
- }
-
- public void Dispose()
- {
- DisposeLiveStream();
- DisposeLogStream();
- DisposeIsoMount();
- }
-
- private void DisposeLogStream()
- {
- if (LogFileStream != null)
- {
- try
- {
- LogFileStream.Dispose();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error disposing log stream", ex);
- }
-
- LogFileStream = null;
- }
- }
-
- private async void DisposeLiveStream()
- {
- if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Options.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
- {
- try
- {
- await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error closing media source", ex);
- }
- }
- }
-
- public string OutputFilePath { get; set; }
-
- public string ActualOutputVideoCodec
- {
- get
- {
- var codec = OutputVideoCodec;
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- var stream = VideoStream;
-
- if (stream != null)
- {
- return stream.Codec;
- }
-
- return null;
- }
-
- return codec;
- }
- }
-
- public string ActualOutputAudioCodec
- {
- get
- {
- var codec = OutputAudioCodec;
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
- {
- var stream = AudioStream;
-
- if (stream != null)
- {
- return stream.Codec;
- }
-
- return null;
- }
-
- return codec;
- }
- }
-
- public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
- {
- var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
-
- // job.Framerate = framerate;
-
- if (!percentComplete.HasValue && ticks.HasValue && RunTimeTicks.HasValue)
- {
- var pct = ticks.Value / RunTimeTicks.Value;
- percentComplete = pct * 100;
- }
-
- if (percentComplete.HasValue)
- {
- Progress.Report(percentComplete.Value);
- }
-
- // job.TranscodingPositionTicks = ticks;
- // job.BytesTranscoded = bytesTranscoded;
-
- var deviceId = Options.DeviceId;
-
- if (!string.IsNullOrWhiteSpace(deviceId))
- {
- var audioCodec = ActualOutputVideoCodec;
- var videoCodec = ActualOutputVideoCodec;
-
- // SessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
- // {
- // Bitrate = job.TotalOutputBitrate,
- // AudioCodec = audioCodec,
- // VideoCodec = videoCodec,
- // Container = job.Options.OutputContainer,
- // Framerate = framerate,
- // CompletionPercentage = percentComplete,
- // Width = job.OutputWidth,
- // Height = job.OutputHeight,
- // AudioChannels = job.OutputAudioChannels,
- // IsAudioDirect = string.Equals(job.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase),
- // IsVideoDirect = string.Equals(job.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)
- // });
- }
- }
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
deleted file mode 100644
index 50d3d254a..000000000
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
+++ /dev/null
@@ -1,305 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.MediaInfo;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
- public class EncodingJobFactory
- {
- private readonly ILogger _logger;
- private readonly ILibraryManager _libraryManager;
- private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IConfigurationManager _config;
- private readonly IMediaEncoder _mediaEncoder;
-
- protected static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder)
- {
- _logger = logger;
- _libraryManager = libraryManager;
- _mediaSourceManager = mediaSourceManager;
- _config = config;
- _mediaEncoder = mediaEncoder;
- }
-
- public async Task<EncodingJob> CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress<double> progress, CancellationToken cancellationToken)
- {
- var request = options;
-
- if (string.IsNullOrEmpty(request.AudioCodec))
- {
- request.AudioCodec = InferAudioCodec(request.OutputContainer);
- }
-
- var state = new EncodingJob(_logger, _mediaSourceManager)
- {
- Options = options,
- IsVideoRequest = isVideoRequest,
- Progress = progress
- };
-
- if (!string.IsNullOrWhiteSpace(request.VideoCodec))
- {
- state.SupportedVideoCodecs = request.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
- request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
- }
-
- if (!string.IsNullOrWhiteSpace(request.AudioCodec))
- {
- state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
- request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault();
- }
-
- if (!string.IsNullOrWhiteSpace(request.SubtitleCodec))
- {
- state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
- request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToSubtitleCodec(i))
- ?? state.SupportedSubtitleCodecs.FirstOrDefault();
- }
-
- var item = _libraryManager.GetItemById(request.ItemId);
- state.ItemType = item.GetType().Name;
-
- state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase);
-
- var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ??
- item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null);
-
- if (primaryImage != null)
- {
- state.AlbumCoverPath = primaryImage.Path;
- }
-
- var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(request.ItemId, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false);
-
- var mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
- ? mediaSources.First()
- : mediaSources.First(i => string.Equals(i.Id, request.MediaSourceId));
-
- var videoRequest = state.Options;
-
- encodingHelper.AttachMediaSourceInfo(state, mediaSource, null);
-
- //var container = Path.GetExtension(state.RequestedUrl);
-
- //if (string.IsNullOrEmpty(container))
- //{
- // container = request.Static ?
- // state.InputContainer :
- // (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.');
- //}
-
- //state.OutputContainer = (container ?? string.Empty).TrimStart('.');
-
- state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(state.Options, state.AudioStream);
-
- state.OutputAudioCodec = state.Options.AudioCodec;
-
- state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state.Options, state.AudioStream, state.OutputAudioCodec);
-
- if (videoRequest != null)
- {
- state.OutputVideoCodec = state.Options.VideoCodec;
- state.OutputVideoBitrate = encodingHelper.GetVideoBitrateParamValue(state.Options, state.VideoStream, state.OutputVideoCodec);
-
- if (state.OutputVideoBitrate.HasValue)
- {
- var resolution = ResolutionNormalizer.Normalize(
- state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
- state.OutputVideoBitrate.Value,
- state.VideoStream == null ? null : state.VideoStream.Codec,
- state.OutputVideoCodec,
- videoRequest.MaxWidth,
- videoRequest.MaxHeight);
-
- videoRequest.MaxWidth = resolution.MaxWidth;
- videoRequest.MaxHeight = resolution.MaxHeight;
- }
- }
-
- ApplyDeviceProfileSettings(state);
-
- if (videoRequest != null)
- {
- encodingHelper.TryStreamCopy(state);
- }
-
- //state.OutputFilePath = GetOutputFilePath(state);
-
- return state;
- }
-
- protected EncodingOptions GetEncodingOptions()
- {
- return _config.GetConfiguration<EncodingOptions>("encoding");
- }
-
- /// <summary>
- /// Infers the video codec.
- /// </summary>
- /// <param name="container">The container.</param>
- /// <returns>System.Nullable{VideoCodecs}.</returns>
- private static string InferVideoCodec(string container)
- {
- var ext = "." + (container ?? string.Empty);
-
- if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase))
- {
- return "wmv";
- }
- if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
- {
- return "vpx";
- }
- if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
- {
- return "theora";
- }
- if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase))
- {
- return "h264";
- }
-
- return "copy";
- }
-
- private string InferAudioCodec(string container)
- {
- var ext = "." + (container ?? string.Empty);
-
- if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase))
- {
- return "mp3";
- }
- if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase))
- {
- return "aac";
- }
- if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase))
- {
- return "wma";
- }
- if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
- if (string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
- if (string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
- if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
- if (string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase))
- {
- return "vorbis";
- }
-
- return "copy";
- }
-
- /// <summary>
- /// Determines whether the specified stream is H264.
- /// </summary>
- /// <param name="stream">The stream.</param>
- /// <returns><c>true</c> if the specified stream is H264; otherwise, <c>false</c>.</returns>
- protected bool IsH264(MediaStream stream)
- {
- var codec = stream.Codec ?? string.Empty;
-
- return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 ||
- codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1;
- }
-
- private static int GetVideoProfileScore(string profile)
- {
- var list = new List<string>
- {
- "Constrained Baseline",
- "Baseline",
- "Extended",
- "Main",
- "High",
- "Progressive High",
- "Constrained High"
- };
-
- return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase));
- }
-
- private void ApplyDeviceProfileSettings(EncodingJob state)
- {
- var profile = state.Options.DeviceProfile;
-
- if (profile == null)
- {
- // Don't use settings from the default profile.
- // Only use a specific profile if it was requested.
- return;
- }
-
- var audioCodec = state.ActualOutputAudioCodec;
-
- var videoCodec = state.ActualOutputVideoCodec;
- var outputContainer = state.Options.OutputContainer;
-
- var mediaProfile = state.IsVideoRequest ?
- profile.GetAudioMediaProfile(outputContainer, audioCodec, state.OutputAudioChannels, state.OutputAudioBitrate, state.OutputAudioSampleRate, state.OutputAudioBitDepth) :
- profile.GetVideoMediaProfile(outputContainer,
- audioCodec,
- videoCodec,
- state.OutputWidth,
- state.OutputHeight,
- state.TargetVideoBitDepth,
- state.OutputVideoBitrate,
- state.TargetVideoProfile,
- state.TargetVideoLevel,
- state.TargetFramerate,
- state.TargetPacketLength,
- state.TargetTimestamp,
- state.IsTargetAnamorphic,
- state.IsTargetInterlaced,
- state.TargetRefFrames,
- state.TargetVideoStreamCount,
- state.TargetAudioStreamCount,
- state.TargetVideoCodecTag,
- state.IsTargetAVC);
-
- if (mediaProfile != null)
- {
- state.MimeType = mediaProfile.MimeType;
- }
-
- var transcodingProfile = state.IsVideoRequest ?
- profile.GetAudioTranscodingProfile(outputContainer, audioCodec) :
- profile.GetVideoTranscodingProfile(outputContainer, audioCodec, videoCodec);
-
- if (transcodingProfile != null)
- {
- state.EstimateContentLength = transcodingProfile.EstimateContentLength;
- state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
- state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
-
- state.Options.CopyTimestamps = transcodingProfile.CopyTimestamps;
- }
- }
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
deleted file mode 100644
index dc3cb5f5e..000000000
--- a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using MediaBrowser.Model.MediaInfo;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
- public static class EncodingUtils
- {
- public static string GetInputArgument(List<string> inputFiles, MediaProtocol protocol)
- {
- if (protocol != MediaProtocol.File)
- {
- var url = inputFiles.First();
-
- return string.Format("\"{0}\"", url);
- }
-
- return GetConcatInputArgument(inputFiles);
- }
-
- /// <summary>
- /// Gets the concat input argument.
- /// </summary>
- /// <param name="inputFiles">The input files.</param>
- /// <returns>System.String.</returns>
- private static string GetConcatInputArgument(IReadOnlyList<string> inputFiles)
- {
- // Get all streams
- // If there's more than one we'll need to use the concat command
- if (inputFiles.Count > 1)
- {
- var files = string.Join("|", inputFiles.Select(NormalizePath).ToArray());
-
- return string.Format("concat:\"{0}\"", files);
- }
-
- // Determine the input path for video files
- return GetFileInputArgument(inputFiles[0]);
- }
-
- /// <summary>
- /// Gets the file input argument.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>System.String.</returns>
- private static string GetFileInputArgument(string path)
- {
- if (path.IndexOf("://") != -1)
- {
- return string.Format("\"{0}\"", path);
- }
-
- // Quotes are valid path characters in linux and they need to be escaped here with a leading \
- path = NormalizePath(path);
-
- return string.Format("file:\"{0}\"", path);
- }
-
- /// <summary>
- /// Normalizes the path.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>System.String.</returns>
- private static string NormalizePath(string path)
- {
- // Quotes are valid path characters in linux and they need to be escaped here with a leading \
- return path.Replace("\"", "\\\"");
- }
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs b/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs
deleted file mode 100644
index e21292cbd..000000000
--- a/MediaBrowser.MediaEncoding/Encoder/FontConfigLoader.cs
+++ /dev/null
@@ -1,182 +0,0 @@
-using System;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Common.Configuration;
-
-using MediaBrowser.Common.Net;
-using MediaBrowser.Common.Progress;
-using MediaBrowser.Controller.IO;
-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;
- }
-
- /// <summary>
- /// Extracts the fonts.
- /// </summary>
- /// <param name="targetPath">The target path.</param>
- /// <returns>Task.</returns>
- 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
- var task = Task.Run(async () =>
- {
- await DownloadFontFile(fontsDirectory, fontFilename, new SimpleProgress<double>()).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);
- }
- }
-
- /// <summary>
- /// Downloads the font file.
- /// </summary>
- /// <param name="fontsDirectory">The fonts directory.</param>
- /// <param name="fontFilename">The font filename.</param>
- /// <returns>Task.</returns>
- private async Task DownloadFontFile(string fontsDirectory, string fontFilename, IProgress<double> progress)
- {
- var existingFile = _fileSystem
- .GetFilePaths(_appPaths.ProgramDataPath, true)
- .FirstOrDefault(i => string.Equals(fontFilename, Path.GetFileName(i), StringComparison.OrdinalIgnoreCase));
-
- 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);
- }
-
- /// <summary>
- /// Writes the font config file.
- /// </summary>
- /// <param name="fontsDirectory">The fonts directory.</param>
- /// <returns>Task.</returns>
- 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("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);
-
- var bytes = Encoding.UTF8.GetBytes(contents);
-
- using (var fileStream = _fileSystem.GetFileStream(fontConfigFile, FileOpenMode.Create, FileAccessMode.Write,
- FileShareMode.Read, true))
- {
- await fileStream.WriteAsync(bytes, 0, bytes.Length);
- }
- }
- }
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
deleted file mode 100644
index f416ea417..000000000
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ /dev/null
@@ -1,1089 +0,0 @@
-using MediaBrowser.Controller.Channels;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.MediaEncoding.Probing;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Model.Diagnostics;
-using MediaBrowser.Model.System;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
- /// <summary>
- /// Class MediaEncoder
- /// </summary>
- public class MediaEncoder : IMediaEncoder, IDisposable
- {
- /// <summary>
- /// The _logger
- /// </summary>
- private readonly ILogger _logger;
-
- /// <summary>
- /// Gets the json serializer.
- /// </summary>
- /// <value>The json serializer.</value>
- private readonly IJsonSerializer _jsonSerializer;
-
- /// <summary>
- /// 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 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<IMediaSourceManager> MediaSourceManager;
- private readonly IHttpClient _httpClient;
- private readonly IZipClient _zipClient;
- private readonly IProcessFactory _processFactory;
- private readonly IMemoryStreamFactory _memoryStreamProvider;
-
- 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 bool EnableEncoderFontFile;
-
- private readonly IEnvironmentInfo _environmentInfo;
-
- 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<ISubtitleEncoder> subtitleEncoder, Func<IMediaSourceManager> mediaSourceManager, IHttpClient httpClient, IZipClient zipClient, IMemoryStreamFactory memoryStreamProvider, IProcessFactory processFactory,
- int defaultImageExtractionTimeoutMs,
- bool enableEncoderFontFile, IEnvironmentInfo environmentInfo)
- {
- _logger = logger;
- _jsonSerializer = jsonSerializer;
- ConfigurationManager = configurationManager;
- FileSystem = fileSystem;
- LiveTvManager = liveTvManager;
- IsoManager = isoManager;
- LibraryManager = libraryManager;
- ChannelManager = channelManager;
- SessionManager = sessionManager;
- SubtitleEncoder = subtitleEncoder;
- MediaSourceManager = mediaSourceManager;
- _httpClient = httpClient;
- _zipClient = zipClient;
- _memoryStreamProvider = memoryStreamProvider;
- _processFactory = processFactory;
- DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
- EnableEncoderFontFile = enableEncoderFontFile;
- _environmentInfo = environmentInfo;
- FFProbePath = ffProbePath;
- FFMpegPath = ffMpegPath;
- _originalFFProbePath = ffProbePath;
- _originalFFMpegPath = ffMpegPath;
-
- _hasExternalEncoder = hasExternalEncoder;
-
- SetEnvironmentVariable();
- }
-
- private readonly object _logLock = new object();
- public void SetLogFilename(string name)
- {
- lock (_logLock)
- {
- try
- {
- _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=" + name + ":level=32");
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error setting FFREPORT environment variable", ex);
- }
- }
- }
-
- public void ClearLogFilename()
- {
- lock (_logLock)
- {
- try
- {
- _environmentInfo.SetProcessEnvironmentVariable("FFREPORT", null);
- }
- catch (Exception ex)
- {
- //_logger.ErrorException("Error setting FFREPORT environment variable", ex);
- }
- }
- }
-
- private void SetEnvironmentVariable()
- {
- try
- {
- //_environmentInfo.SetProcessEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32");
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error setting FFREPORT environment variable", ex);
- }
- try
- {
- //_environmentInfo.SetUserEnvironmentVariable("FFREPORT", "file=program-YYYYMMDD-HHMMSS.txt:level=32");
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error setting FFREPORT environment variable", ex);
- }
- }
-
- public string EncoderLocationType
- {
- get
- {
- if (_hasExternalEncoder)
- {
- return "External";
- }
-
- if (string.IsNullOrWhiteSpace(FFMpegPath))
- {
- return null;
- }
-
- if (IsSystemInstalledPath(FFMpegPath))
- {
- return "System";
- }
-
- return "Custom";
- }
- }
-
- private bool IsSystemInstalledPath(string path)
- {
- if (path.IndexOf("/", StringComparison.Ordinal) == -1 && path.IndexOf("\\", StringComparison.Ordinal) == -1)
- {
- return true;
- }
-
- return false;
- }
-
- public async Task Init()
- {
- InitPaths();
-
- if (!string.IsNullOrWhiteSpace(FFMpegPath))
- {
- var result = new EncoderValidator(_logger, _processFactory).Validate(FFMpegPath);
-
- SetAvailableDecoders(result.Item1);
- SetAvailableEncoders(result.Item2);
-
- if (EnableEncoderFontFile)
- {
- var directory = FileSystem.GetDirectoryName(FFMpegPath);
-
- if (!string.IsNullOrWhiteSpace(directory) && FileSystem.ContainsSubPath(ConfigurationManager.ApplicationPaths.ProgramDataPath, directory))
- {
- await new FontConfigLoader(_httpClient, ConfigurationManager.ApplicationPaths, _logger, _zipClient, FileSystem).DownloadFonts(directory).ConfigureAwait(false);
- }
- }
- }
- }
-
- private void InitPaths()
- {
- ConfigureEncoderPaths();
-
- if (_hasExternalEncoder)
- {
- LogPaths();
- return;
- }
-
- // If the path was passed in, save it into config now.
- var encodingOptions = GetEncodingOptions();
- var appPath = encodingOptions.EncoderAppPath;
-
- var valueToSave = FFMpegPath;
-
- if (!string.IsNullOrWhiteSpace(valueToSave))
- {
- // if using system variable, don't save this.
- if (IsSystemInstalledPath(valueToSave) || _hasExternalEncoder)
- {
- valueToSave = null;
- }
- }
-
- if (!string.Equals(valueToSave, appPath, StringComparison.Ordinal))
- {
- encodingOptions.EncoderAppPath = valueToSave;
- ConfigurationManager.SaveConfiguration("encoding", encodingOptions);
- }
- }
-
- public void UpdateEncoderPath(string path, string pathType)
- {
- if (_hasExternalEncoder)
- {
- return;
- }
-
- _logger.Info("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";
-
- newPaths = TestForInstalledVersions();
- }
- else if (string.Equals(pathType, "custom", StringComparison.OrdinalIgnoreCase))
- {
- if (string.IsNullOrWhiteSpace(path))
- {
- throw new ArgumentNullException("path");
- }
-
- if (!FileSystem.FileExists(path) && !FileSystem.DirectoryExists(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 (FileSystem.DirectoryExists(appPath))
- {
- return GetPathsFromDirectory(appPath);
- }
-
- if (FileSystem.FileExists(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) || !FileSystem.FileExists(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)
- {
- return FileSystem.GetFilePaths(FileSystem.GetDirectoryName(appPath))
- .FirstOrDefault(i => string.Equals(Path.GetFileNameWithoutExtension(i), "ffprobe", StringComparison.OrdinalIgnoreCase));
- }
-
- private void LogPaths()
- {
- _logger.Info("FFMpeg: {0}", FFMpegPath ?? "not found");
- _logger.Info("FFProbe: {0}", FFProbePath ?? "not found");
- }
-
- private EncodingOptions GetEncodingOptions()
- {
- return ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
- }
-
- private List<string> _encoders = new List<string>();
- public void SetAvailableEncoders(List<string> list)
- {
- _encoders = list.ToList();
- //_logger.Info("Supported encoders: {0}", string.Join(",", list.ToArray()));
- }
-
- private List<string> _decoders = new List<string>();
- public void SetAvailableDecoders(List<string> list)
- {
- _decoders = list.ToList();
- //_logger.Info("Supported decoders: {0}", string.Join(",", list.ToArray()));
- }
-
- public bool SupportsEncoder(string encoder)
- {
- return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase);
- }
-
- public bool SupportsDecoder(string decoder)
- {
- return _decoders.Contains(decoder, StringComparer.OrdinalIgnoreCase);
- }
-
- public bool CanEncodeToAudioCodec(string codec)
- {
- if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
- {
- codec = "libopus";
- }
- else if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
- {
- codec = "libmp3lame";
- }
-
- return SupportsEncoder(codec);
- }
-
- public bool CanEncodeToSubtitleCodec(string codec)
- {
- // TODO
- return true;
- }
-
- /// <summary>
- /// Gets the encoder path.
- /// </summary>
- /// <value>The encoder path.</value>
- public string EncoderPath
- {
- get { return FFMpegPath; }
- }
-
- /// <summary>
- /// Gets the media info.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- public Task<MediaInfo> GetMediaInfo(MediaInfoRequest request, CancellationToken cancellationToken)
- {
- var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
-
- var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.InputPath, request.Protocol, request.MountedIso, request.PlayableStreamFileNames);
-
- var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length);
- string analyzeDuration;
-
- if (request.AnalyzeDurationMs > 0)
- {
- analyzeDuration = "-analyzeduration " +
- (request.AnalyzeDurationMs * 1000).ToString(CultureInfo.InvariantCulture);
- }
- else
- {
- analyzeDuration = EncodingHelper.GetAnalyzeDurationArgument(inputFiles.Length);
- }
-
- probeSize = probeSize + " " + analyzeDuration;
- probeSize = probeSize.Trim();
-
- var forceEnableLogging = request.Protocol != MediaProtocol.File;
-
- return GetMediaInfoInternal(GetInputArgument(inputFiles, request.Protocol), request.InputPath, request.Protocol, extractChapters,
- probeSize, request.MediaType == DlnaProfileType.Audio, request.VideoType, forceEnableLogging, cancellationToken);
- }
-
- /// <summary>
- /// Gets the input argument.
- /// </summary>
- /// <param name="inputFiles">The input files.</param>
- /// <param name="protocol">The protocol.</param>
- /// <returns>System.String.</returns>
- /// <exception cref="System.ArgumentException">Unrecognized InputType</exception>
- public string GetInputArgument(string[] inputFiles, MediaProtocol protocol)
- {
- return EncodingUtils.GetInputArgument(inputFiles.ToList(), protocol);
- }
-
- /// <summary>
- /// Gets the media info internal.
- /// </summary>
- /// <returns>Task{MediaInfoResult}.</returns>
- private async Task<MediaInfo> GetMediaInfoInternal(string inputPath,
- string primaryPath,
- MediaProtocol protocol,
- bool extractChapters,
- string probeSizeArgument,
- bool isAudio,
- VideoType videoType,
- bool forceEnableLogging,
- CancellationToken cancellationToken)
- {
- var args = extractChapters
- ? "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_chapters -show_format"
- : "{0} -i {1} -threads 0 -v info -print_format json -show_streams -show_format";
-
- var process = _processFactory.Create(new ProcessOptions
- {
- CreateNoWindow = true,
- UseShellExecute = false,
-
- // Must consume both or ffmpeg may hang due to deadlocks. See comments below.
- RedirectStandardOutput = true,
- FileName = FFProbePath,
- Arguments = string.Format(args, probeSizeArgument, inputPath).Trim(),
-
- IsHidden = true,
- ErrorDialog = false,
- EnableRaisingEvents = true
- });
-
- if (forceEnableLogging)
- {
- _logger.Info("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
- }
- else
- {
- _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
- }
-
- using (var processWrapper = new ProcessWrapper(process, this, _logger))
- {
- StartProcess(processWrapper);
-
- try
- {
- //process.BeginErrorReadLine();
-
- var result = _jsonSerializer.DeserializeFromStream<InternalMediaInfoResult>(process.StandardOutput.BaseStream);
-
- if (result == null || (result.streams == null && result.format == null))
- {
- throw new Exception("ffprobe failed - streams and format are both null.");
- }
-
- if (result.streams != null)
- {
- // Normalize aspect ratio if invalid
- foreach (var stream in result.streams)
- {
- if (string.Equals(stream.display_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
- {
- stream.display_aspect_ratio = string.Empty;
- }
- if (string.Equals(stream.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase))
- {
- stream.sample_aspect_ratio = string.Empty;
- }
- }
- }
-
- return new ProbeResultNormalizer(_logger, FileSystem, _memoryStreamProvider).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
- }
- catch
- {
- StopProcess(processWrapper, 100);
-
- throw;
- }
- }
- }
-
- /// <summary>
- /// The us culture
- /// </summary>
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- public Task<string> ExtractAudioImage(string path, int? imageStreamIndex, CancellationToken cancellationToken)
- {
- return ExtractImage(new[] { path }, null, imageStreamIndex, MediaProtocol.File, true, null, null, cancellationToken);
- }
-
- public Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
- {
- return ExtractImage(inputFiles, container, null, protocol, false, threedFormat, offset, cancellationToken);
- }
-
- public Task<string> ExtractVideoImage(string[] inputFiles, string container, MediaProtocol protocol, int? imageStreamIndex, CancellationToken cancellationToken)
- {
- return ExtractImage(inputFiles, container, imageStreamIndex, protocol, false, null, null, cancellationToken);
- }
-
- private async Task<string> ExtractImage(string[] inputFiles, string container, int? imageStreamIndex, MediaProtocol protocol, bool isAudio,
- Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
- {
- var inputArgument = GetInputArgument(inputFiles, protocol);
-
- if (isAudio)
- {
- if (imageStreamIndex.HasValue && imageStreamIndex.Value > 0)
- {
- // It seems for audio files we need to subtract 1 (for the audio stream??)
- imageStreamIndex = imageStreamIndex.Value - 1;
- }
- }
- else
- {
- try
- {
- return await ExtractImageInternal(inputArgument, container, imageStreamIndex, protocol, threedFormat, offset, true, cancellationToken).ConfigureAwait(false);
- }
- catch (ArgumentException)
- {
- throw;
- }
- catch
- {
- _logger.Error("I-frame image extraction failed, will attempt standard way. Input: {0}", inputArgument);
- }
- }
-
- return await ExtractImageInternal(inputArgument, container, imageStreamIndex, protocol, threedFormat, offset, false, cancellationToken).ConfigureAwait(false);
- }
-
- private async Task<string> ExtractImageInternal(string inputPath, string container, int? imageStreamIndex, MediaProtocol protocol, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, CancellationToken cancellationToken)
- {
- if (string.IsNullOrEmpty(inputPath))
- {
- throw new ArgumentNullException("inputPath");
- }
-
- var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
- FileSystem.CreateDirectory(FileSystem.GetDirectoryName(tempExtractPath));
-
- // apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600.
- // This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar
- var vf = "scale=600:trunc(600/dar/2)*2";
-
- if (threedFormat.HasValue)
- {
- switch (threedFormat.Value)
- {
- case Video3DFormat.HalfSideBySide:
- vf = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
- // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
- break;
- case Video3DFormat.FullSideBySide:
- vf = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
- //fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to 600.
- break;
- case Video3DFormat.HalfTopAndBottom:
- vf = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
- //htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to 600
- break;
- case Video3DFormat.FullTopAndBottom:
- vf = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale=600:trunc(600/dar/2)*2";
- // ftab crop heigt in half, set the display aspect,crop out any black bars we may have made the scale width to 600
- break;
- default:
- break;
- }
- }
-
- var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
-
- var enableThumbnail = !new List<string> { "wtv" }.Contains(container ?? string.Empty, StringComparer.OrdinalIgnoreCase);
- // 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 args = useIFrame ? string.Format("-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);
-
- var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
- var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
-
- if (!string.IsNullOrWhiteSpace(probeSizeArgument))
- {
- args = probeSizeArgument + " " + args;
- }
-
- if (!string.IsNullOrWhiteSpace(analyzeDurationArgument))
- {
- args = analyzeDurationArgument + " " + args;
- }
-
- if (offset.HasValue)
- {
- args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args;
- }
-
- var process = _processFactory.Create(new ProcessOptions
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = FFMpegPath,
- Arguments = args,
- IsHidden = true,
- ErrorDialog = false
- });
-
- _logger.Debug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
-
- using (var processWrapper = new ProcessWrapper(process, this, _logger))
- {
- bool ranToCompletion;
-
- StartProcess(processWrapper);
-
- var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs;
- if (timeoutMs <= 0)
- {
- timeoutMs = DefaultImageExtractionTimeoutMs;
- }
-
- ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
-
- if (!ranToCompletion)
- {
- StopProcess(processWrapper, 1000);
- }
-
- var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
- var file = FileSystem.GetFileInfo(tempExtractPath);
-
- if (exitCode == -1 || !file.Exists || file.Length == 0)
- {
- var msg = string.Format("ffmpeg image extraction failed for {0}", inputPath);
-
- _logger.Error(msg);
-
- throw new Exception(msg);
- }
-
- return tempExtractPath;
- }
- }
-
- public string GetTimeParameter(long ticks)
- {
- var time = TimeSpan.FromTicks(ticks);
-
- return GetTimeParameter(time);
- }
-
- public string GetTimeParameter(TimeSpan time)
- {
- return time.ToString(@"hh\:mm\:ss\.fff", UsCulture);
- }
-
- public async Task ExtractVideoImagesOnInterval(string[] inputFiles,
- MediaProtocol protocol,
- Video3DFormat? threedFormat,
- TimeSpan interval,
- string targetDirectory,
- string filenamePrefix,
- int? maxWidth,
- CancellationToken cancellationToken)
- {
- var resourcePool = _thumbnailResourcePool;
-
- var inputArgument = GetInputArgument(inputFiles, protocol);
-
- var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(UsCulture);
-
- if (maxWidth.HasValue)
- {
- var maxWidthParam = maxWidth.Value.ToString(UsCulture);
-
- vf += string.Format(",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam);
- }
-
- FileSystem.CreateDirectory(targetDirectory);
- 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 probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
- var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
-
- if (!string.IsNullOrWhiteSpace(probeSizeArgument))
- {
- args = probeSizeArgument + " " + args;
- }
-
- if (!string.IsNullOrWhiteSpace(analyzeDurationArgument))
- {
- args = analyzeDurationArgument + " " + args;
- }
-
- var process = _processFactory.Create(new ProcessOptions
- {
- CreateNoWindow = true,
- UseShellExecute = false,
- FileName = FFMpegPath,
- Arguments = args,
- IsHidden = true,
- ErrorDialog = false
- });
-
- _logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
-
- await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- bool ranToCompletion = false;
-
- using (var processWrapper = new ProcessWrapper(process, this, _logger))
- {
- try
- {
- StartProcess(processWrapper);
-
- // Need to give ffmpeg enough time to make all the thumbnails, which could be a while,
- // but we still need to detect if the process hangs.
- // Making the assumption that as long as new jpegs are showing up, everything is good.
-
- bool isResponsive = true;
- int lastCount = 0;
-
- while (isResponsive)
- {
- if (await process.WaitForExitAsync(30000).ConfigureAwait(false))
- {
- ranToCompletion = true;
- break;
- }
-
- cancellationToken.ThrowIfCancellationRequested();
-
- var jpegCount = FileSystem.GetFilePaths(targetDirectory)
- .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
-
- isResponsive = (jpegCount > lastCount);
- lastCount = jpegCount;
- }
-
- if (!ranToCompletion)
- {
- StopProcess(processWrapper, 1000);
- }
- }
- finally
- {
- resourcePool.Release();
- }
-
- var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
-
- if (exitCode == -1)
- {
- var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument);
-
- _logger.Error(msg);
-
- throw new Exception(msg);
- }
- }
- }
-
- public async Task<string> EncodeAudio(EncodingJobOptions options,
- IProgress<double> progress,
- CancellationToken cancellationToken)
- {
- var job = await new AudioEncoder(this,
- _logger,
- ConfigurationManager,
- FileSystem,
- IsoManager,
- LibraryManager,
- SessionManager,
- SubtitleEncoder(),
- MediaSourceManager(),
- _processFactory)
- .Start(options, progress, cancellationToken).ConfigureAwait(false);
-
- await job.TaskCompletionSource.Task.ConfigureAwait(false);
-
- return job.OutputFilePath;
- }
-
- public async Task<string> EncodeVideo(EncodingJobOptions options,
- IProgress<double> progress,
- CancellationToken cancellationToken)
- {
- var job = await new VideoEncoder(this,
- _logger,
- ConfigurationManager,
- FileSystem,
- IsoManager,
- LibraryManager,
- SessionManager,
- SubtitleEncoder(),
- MediaSourceManager(),
- _processFactory)
- .Start(options, progress, cancellationToken).ConfigureAwait(false);
-
- await job.TaskCompletionSource.Task.ConfigureAwait(false);
-
- return job.OutputFilePath;
- }
-
- private void StartProcess(ProcessWrapper process)
- {
- process.Process.Start();
-
- lock (_runningProcesses)
- {
- _runningProcesses.Add(process);
- }
- }
- private void StopProcess(ProcessWrapper process, int waitTimeMs)
- {
- try
- {
- if (process.Process.WaitForExit(waitTimeMs))
- {
- return;
- }
- }
- catch (Exception ex)
- {
- _logger.Error("Error in WaitForExit", ex);
- }
-
- try
- {
- _logger.Info("Killing ffmpeg process");
-
- process.Process.Kill();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error killing process", ex);
- }
- }
-
- private void StopProcesses()
- {
- List<ProcessWrapper> proceses;
- lock (_runningProcesses)
- {
- proceses = _runningProcesses.ToList();
- _runningProcesses.Clear();
- }
-
- foreach (var process in proceses)
- {
- if (!process.HasExited)
- {
- StopProcess(process, 500);
- }
- }
- }
-
- public string EscapeSubtitleFilterPath(string path)
- {
- // https://ffmpeg.org/ffmpeg-filters.html#Notes-on-filtergraph-escaping
- // We need to double escape
-
- return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''");
- }
-
- /// <summary>
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- /// </summary>
- public void Dispose()
- {
- Dispose(true);
- }
-
- /// <summary>
- /// Releases unmanaged and - optionally - managed resources.
- /// </summary>
- /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool dispose)
- {
- if (dispose)
- {
- StopProcesses();
- }
- }
-
- private class ProcessWrapper : IDisposable
- {
- public readonly IProcess Process;
- public bool HasExited;
- public int? ExitCode;
- private readonly MediaEncoder _mediaEncoder;
- private readonly ILogger _logger;
-
- public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder, ILogger logger)
- {
- Process = process;
- _mediaEncoder = mediaEncoder;
- _logger = logger;
- Process.Exited += Process_Exited;
- }
-
- void Process_Exited(object sender, EventArgs e)
- {
- var process = (IProcess)sender;
-
- HasExited = true;
-
- try
- {
- ExitCode = process.ExitCode;
- }
- catch
- {
- }
-
- DisposeProcess(process);
- }
-
- private void DisposeProcess(IProcess process)
- {
- lock (_mediaEncoder._runningProcesses)
- {
- _mediaEncoder._runningProcesses.Remove(this);
- }
-
- try
- {
- process.Dispose();
- }
- catch
- {
- }
- }
-
- private bool _disposed;
- private readonly object _syncLock = new object();
- public void Dispose()
- {
- lock (_syncLock)
- {
- if (!_disposed)
- {
- if (Process != null)
- {
- Process.Exited -= Process_Exited;
- DisposeProcess(Process);
- }
- }
-
- _disposed = true;
- }
- }
- }
- }
-} \ No newline at end of file
diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
deleted file mode 100644
index 96c126923..000000000
--- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Session;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using System;
-using System.IO;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Diagnostics;
-
-namespace MediaBrowser.MediaEncoding.Encoder
-{
- public class VideoEncoder : BaseEncoder
- {
- public VideoEncoder(MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IIsoManager isoManager, ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) : base(mediaEncoder, logger, configurationManager, fileSystem, isoManager, libraryManager, sessionManager, subtitleEncoder, mediaSourceManager, processFactory)
- {
- }
-
- protected override string GetCommandLineArguments(EncodingJob state)
- {
- // Get the output codec name
- var encodingOptions = GetEncodingOptions();
-
- return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, state.OutputFilePath, "superfast");
- }
-
- protected override string GetOutputFileExtension(EncodingJob state)
- {
- var ext = base.GetOutputFileExtension(state);
-
- if (!string.IsNullOrEmpty(ext))
- {
- return ext;
- }
-
- var videoCodec = state.Options.VideoCodec;
-
- if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
- {
- return ".ts";
- }
- if (string.Equals(videoCodec, "theora", StringComparison.OrdinalIgnoreCase))
- {
- return ".ogv";
- }
- if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
- {
- return ".webm";
- }
- if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase))
- {
- return ".asf";
- }
-
- return null;
- }
-
- protected override bool IsVideoEncoder
- {
- get { return true; }
- }
-
- }
-} \ No newline at end of file