| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 | 
							- using System;
 
- using System.Globalization;
 
- using System.IO;
 
- using System.Text;
 
- using System.Threading;
 
- using System.Threading.Tasks;
 
- 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.Diagnostics;
 
- using MediaBrowser.Model.Entities;
 
- using MediaBrowser.Model.IO;
 
- using MediaBrowser.Model.MediaInfo;
 
- using Microsoft.Extensions.Logging;
 
- 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, 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.LogInformation(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.LogError(ex, "Error starting ffmpeg");
 
-                 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.LogInformation("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.LogDebug("Disposing stream resources");
 
-             job.Dispose();
 
-             var isSuccesful = false;
 
-             try
 
-             {
 
-                 var exitCode = process.ExitCode;
 
-                 Logger.LogInformation("FFMpeg exited with code {0}", exitCode);
 
-                 isSuccesful = exitCode == 0;
 
-             }
 
-             catch (Exception ex)
 
-             {
 
-                 Logger.LogError(ex, "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.LogError("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.TempDirectory) ?
 
-                 ConfigurationManager.ApplicationPaths.TranscodingTempPath :
 
-                 state.Options.TempDirectory;
 
-             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.Container))
 
-             {
 
-                 return "." + state.Options.Container;
 
-             }
 
-             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);
 
-             }
 
-         }
 
-     }
 
- }
 
 
  |