From 66eabcdd3906cb04dfc60767dea8328267a6f134 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 29 Jan 2019 19:30:52 +0100 Subject: [PATCH 1/3] Minor changes to encoding code * Don't wait in intervals of 100ms for the file to exist --- .../Diagnostics/CommonProcess.cs | 2 +- .../MediaEncoding/EncodingJobInfo.cs | 124 ++++++------------ .../Encoder/BaseEncoder.cs | 81 ++++++++---- .../Encoder/EncodingJob.cs | 2 - .../Encoder/EncodingJobFactory.cs | 106 ++------------- 5 files changed, 110 insertions(+), 205 deletions(-) diff --git a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs index 55539eafcf..78b22bda3f 100644 --- a/Emby.Server.Implementations/Diagnostics/CommonProcess.cs +++ b/Emby.Server.Implementations/Diagnostics/CommonProcess.cs @@ -105,7 +105,7 @@ namespace Emby.Server.Implementations.Diagnostics { return _process.WaitForExit(timeMs); } - + public Task WaitForExitAsync(int timeMs) { //Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true. diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 1fe8856ccb..916d691b80 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -10,19 +10,13 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Session; -using Microsoft.Extensions.Logging; -using System.IO; using MediaBrowser.Model.Net; -using MediaBrowser.Controller.Library; -using System.Threading.Tasks; namespace MediaBrowser.Controller.MediaEncoding { // For now, a common base class until the API and MediaEncoding classes are unified public class EncodingJobInfo { - protected readonly IMediaSourceManager MediaSourceManager; - public MediaStream VideoStream { get; set; } public VideoType VideoType { get; set; } public Dictionary RemoteHttpHeaders { get; set; } @@ -49,7 +43,6 @@ namespace MediaBrowser.Controller.MediaEncoding public string OutputFilePath { get; set; } public string MimeType { get; set; } - public long? EncodingDurationTicks { get; set; } public string GetMimeType(string outputPath, bool enableStreamDefault = true) { @@ -68,7 +61,12 @@ namespace MediaBrowser.Controller.MediaEncoding { if (_transcodeReasons == null) { - _transcodeReasons = (BaseRequest.TranscodeReasons ?? string.Empty) + if (BaseRequest.TranscodeReasons == null) + { + return Array.Empty(); + } + + _transcodeReasons = BaseRequest.TranscodeReasons .Split(',') .Where(i => !string.IsNullOrEmpty(i)) .Select(v => (TranscodeReason)Enum.Parse(typeof(TranscodeReason), v, true)) @@ -98,7 +96,8 @@ namespace MediaBrowser.Controller.MediaEncoding get { // For live tv + in progress recordings - if (string.Equals(InputContainer, "mpegts", StringComparison.OrdinalIgnoreCase) || string.Equals(InputContainer, "ts", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(InputContainer, "mpegts", StringComparison.OrdinalIgnoreCase) + || string.Equals(InputContainer, "ts", StringComparison.OrdinalIgnoreCase)) { if (!MediaSource.RunTimeTicks.HasValue) { @@ -155,15 +154,7 @@ namespace MediaBrowser.Controller.MediaEncoding } } - if (forceDeinterlaceIfSourceIsInterlaced) - { - if (isInputInterlaced) - { - return true; - } - } - - return false; + return forceDeinterlaceIfSourceIsInterlaced && isInputInterlaced; } public string[] GetRequestedProfiles(string codec) @@ -211,7 +202,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { var value = BaseRequest.GetOption(codec, "maxrefframes"); - if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (!string.IsNullOrEmpty(value) + && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -230,7 +222,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { var value = BaseRequest.GetOption(codec, "videobitdepth"); - if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (!string.IsNullOrEmpty(value) + && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -249,7 +242,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { var value = BaseRequest.GetOption(codec, "audiobitdepth"); - if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (!string.IsNullOrEmpty(value) + && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -264,6 +258,7 @@ namespace MediaBrowser.Controller.MediaEncoding { return BaseRequest.MaxAudioChannels; } + if (BaseRequest.AudioChannels.HasValue) { return BaseRequest.AudioChannels; @@ -272,7 +267,8 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { var value = BaseRequest.GetOption(codec, "audiochannels"); - if (!string.IsNullOrEmpty(value) && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (!string.IsNullOrEmpty(value) + && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -294,7 +290,8 @@ namespace MediaBrowser.Controller.MediaEncoding SupportedSubtitleCodecs = Array.Empty(); } - public bool IsSegmentedLiveStream => TranscodingType != TranscodingJobType.Progressive && !RunTimeTicks.HasValue; + public bool IsSegmentedLiveStream + => TranscodingType != TranscodingJobType.Progressive && !RunTimeTicks.HasValue; public bool EnableBreakOnNonKeyFrames(string videoCodec) { @@ -428,11 +425,12 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.Level; + return VideoStream?.Level; } var level = GetRequestedLevel(ActualOutputVideoCodec); - if (!string.IsNullOrEmpty(level) && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) + if (!string.IsNullOrEmpty(level) + && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var result)) { return result; } @@ -450,7 +448,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.BitDepth; + return VideoStream?.BitDepth; } return null; @@ -467,7 +465,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.RefFrames; + return VideoStream?.RefFrames; } return null; @@ -494,13 +492,14 @@ namespace MediaBrowser.Controller.MediaEncoding { get { - var defaultValue = string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ? + if (BaseRequest.Static) + { + return InputTimestamp; + } + + return string.Equals(OutputContainer, "m2ts", StringComparison.OrdinalIgnoreCase) ? TransportStreamTimestamp.Valid : TransportStreamTimestamp.None; - - return !BaseRequest.Static - ? defaultValue - : InputTimestamp; } } @@ -513,7 +512,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.PacketLength; + return VideoStream?.PacketLength; } return null; @@ -529,7 +528,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.Profile; + return VideoStream?.Profile; } var requestedProfile = GetRequestedProfiles(ActualOutputVideoCodec).FirstOrDefault(); @@ -542,42 +541,13 @@ namespace MediaBrowser.Controller.MediaEncoding } } - /// - /// Predicts the audio sample rate that will be in the output stream - /// - public string TargetVideoRange - { - get - { - if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return VideoStream == null ? null : VideoStream.VideoRange; - } - - return "SDR"; - } - } - - public string TargetAudioProfile - { - get - { - if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return AudioStream == null ? null : AudioStream.Profile; - } - - return null; - } - } - public string TargetVideoCodecTag { get { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.CodecTag; + return VideoStream?.CodecTag; } return null; @@ -590,7 +560,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.IsAnamorphic; + return VideoStream?.IsAnamorphic; } return false; @@ -605,14 +575,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) { - var stream = VideoStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; + return VideoStream?.Codec; } return codec; @@ -627,14 +590,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) { - var stream = AudioStream; - - if (stream != null) - { - return stream.Codec; - } - - return null; + return AudioStream?.Codec; } return codec; @@ -647,7 +603,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? (bool?)null : VideoStream.IsInterlaced; + return VideoStream?.IsInterlaced; } if (DeInterlace(ActualOutputVideoCodec, true)) @@ -655,7 +611,7 @@ namespace MediaBrowser.Controller.MediaEncoding return false; } - return VideoStream == null ? (bool?)null : VideoStream.IsInterlaced; + return VideoStream?.IsInterlaced; } } @@ -665,7 +621,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - return VideoStream == null ? null : VideoStream.IsAVC; + return VideoStream?.IsAVC; } return false; diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index 81f7c16d3e..b231938b5e 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -1,5 +1,5 @@ using System; -using System.Globalization; +using System.Diagnostics; using System.IO; using System.Text; using System.Threading; @@ -31,11 +31,10 @@ namespace MediaBrowser.MediaEncoding.Encoder protected readonly IMediaSourceManager MediaSourceManager; protected IProcessFactory ProcessFactory; - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - protected EncodingHelper EncodingHelper; - protected BaseEncoder(MediaEncoder mediaEncoder, + protected BaseEncoder( + MediaEncoder mediaEncoder, ILogger logger, IServerConfigurationManager configurationManager, IFileSystem fileSystem, @@ -43,7 +42,8 @@ namespace MediaBrowser.MediaEncoding.Encoder ILibraryManager libraryManager, ISessionManager sessionManager, ISubtitleEncoder subtitleEncoder, - IMediaSourceManager mediaSourceManager, IProcessFactory processFactory) + IMediaSourceManager mediaSourceManager, + IProcessFactory processFactory) { MediaEncoder = mediaEncoder; Logger = logger; @@ -59,11 +59,12 @@ namespace MediaBrowser.MediaEncoding.Encoder EncodingHelper = new EncodingHelper(MediaEncoder, FileSystem, SubtitleEncoder); } - public async Task Start(EncodingJobOptions options, + public async Task Start( + EncodingJobOptions options, IProgress progress, CancellationToken cancellationToken) { - var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, ConfigurationManager, MediaEncoder) + var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, MediaEncoder) .CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false); encodingJob.OutputFilePath = GetOutputFilePath(encodingJob); @@ -75,8 +76,9 @@ namespace MediaBrowser.MediaEncoding.Encoder var commandLineArgs = GetCommandLineArguments(encodingJob); - var process = ProcessFactory.Create(new ProcessOptions + Process process = Process.Start(new ProcessStartInfo { + WindowStyle = ProcessWindowStyle.Hidden, CreateNoWindow = true, UseShellExecute = false, @@ -88,11 +90,11 @@ namespace MediaBrowser.MediaEncoding.Encoder FileName = MediaEncoder.EncoderPath, Arguments = commandLineArgs, - IsHidden = true, - ErrorDialog = false, - EnableRaisingEvents = true + ErrorDialog = false }); + process.EnableRaisingEvents = true; + var workingDirectory = GetWorkingDirectory(options); if (!string.IsNullOrWhiteSpace(workingDirectory)) { @@ -128,29 +130,60 @@ namespace MediaBrowser.MediaEncoding.Encoder throw; } - cancellationToken.Register(() => Cancel(process, encodingJob)); + cancellationToken.Register(async () => await Cancel(process, encodingJob)); - // MUST read both stdout and stderr asynchronously or a deadlock may occurr + // MUST read both stdout and stderr asynchronously or a deadlock may occur //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 (!File.Exists(encodingJob.OutputFilePath) && !encodingJob.HasExited) - { - await Task.Delay(100, cancellationToken).ConfigureAwait(false); - } + // Wait for the file to or for the process to stop + Task file = WaitForFileAsync(encodingJob.OutputFilePath); + await Task.WhenAny(encodingJob.TaskCompletionSource.Task, file).ConfigureAwait(false); return encodingJob; } - private void Cancel(IProcess process, EncodingJob job) + public static Task WaitForFileAsync(string path) + { + if (File.Exists(path)) + { + return Task.CompletedTask; + } + + var tcs = new TaskCompletionSource(); + FileSystemWatcher watcher = new FileSystemWatcher(Path.GetDirectoryName(path)); + + watcher.Created += (s, e) => + { + if (e.Name == Path.GetFileName(path)) + { + watcher.Dispose(); + tcs.TrySetResult(true); + } + }; + + watcher.Renamed += (s, e) => + { + if (e.Name == Path.GetFileName(path)) + { + watcher.Dispose(); + tcs.TrySetResult(true); + } + }; + + watcher.EnableRaisingEvents = true; + + return tcs.Task; + } + + private async Task Cancel(Process process, EncodingJob job) { Logger.LogInformation("Killing ffmpeg process for {0}", job.OutputFilePath); //process.Kill(); - process.StandardInput.WriteLine("q"); + await process.StandardInput.WriteLineAsync("q"); job.IsCancelled = true; } @@ -160,28 +193,28 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// The process. /// The job. - private void OnFfMpegProcessExited(IProcess process, EncodingJob job) + private void OnFfMpegProcessExited(Process process, EncodingJob job) { job.HasExited = true; Logger.LogDebug("Disposing stream resources"); job.Dispose(); - var isSuccesful = false; + var isSuccessful = false; try { var exitCode = process.ExitCode; Logger.LogInformation("FFMpeg exited with code {0}", exitCode); - isSuccesful = exitCode == 0; + isSuccessful = exitCode == 0; } catch (Exception ex) { Logger.LogError(ex, "FFMpeg exited with an error."); } - if (isSuccesful && !job.IsCancelled) + if (isSuccessful && !job.IsCancelled) { job.TaskCompletionSource.TrySetResult(true); } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index d4040cd317..cd7de94ce1 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; namespace MediaBrowser.MediaEncoding.Encoder @@ -40,7 +39,6 @@ namespace MediaBrowser.MediaEncoding.Encoder _mediaSourceManager = mediaSourceManager; Id = Guid.NewGuid(); - _logger = logger; TaskCompletionSource = new TaskCompletionSource(); } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs index 95454c4477..5f84a03223 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; using Microsoft.Extensions.Logging; @@ -19,17 +16,13 @@ namespace MediaBrowser.MediaEncoding.Encoder 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) + public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) { _logger = logger; _libraryManager = libraryManager; _mediaSourceManager = mediaSourceManager; - _config = config; _mediaEncoder = mediaEncoder; } @@ -118,11 +111,11 @@ namespace MediaBrowser.MediaEncoding.Encoder if (state.OutputVideoBitrate.HasValue) { var resolution = ResolutionNormalizer.Normalize( - state.VideoStream == null ? (int?)null : state.VideoStream.BitRate, - state.VideoStream == null ? (int?)null : state.VideoStream.Width, - state.VideoStream == null ? (int?)null : state.VideoStream.Height, + state.VideoStream?.BitRate, + state.VideoStream?.Width, + state.VideoStream?.Height, state.OutputVideoBitrate.Value, - state.VideoStream == null ? null : state.VideoStream.Codec, + state.VideoStream?.Codec, state.OutputVideoCodec, videoRequest.MaxWidth, videoRequest.MaxHeight); @@ -144,40 +137,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return state; } - protected EncodingOptions GetEncodingOptions() - { - return _config.GetConfiguration("encoding"); - } - - /// - /// Infers the video codec. - /// - /// The container. - /// System.Nullable{VideoCodecs}. - 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); @@ -186,31 +145,19 @@ namespace MediaBrowser.MediaEncoding.Encoder { return "mp3"; } - if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) { return "aac"; } - if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase)) + else 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)) + else if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) + || string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase) + || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase) + || string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase) + || string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase)) { return "vorbis"; } @@ -218,35 +165,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return "copy"; } - /// - /// Determines whether the specified stream is H264. - /// - /// The stream. - /// true if the specified stream is H264; otherwise, false. - 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 - { - "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; From 95ee3c72e3465ec8d958b87e6c339d94db4db45a Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 29 Jan 2019 21:39:12 +0100 Subject: [PATCH 2/3] Properly dispose Tasks --- MediaBrowser.Api/ApiEntryPoint.cs | 32 +++---- .../UserLibrary/PlaystateService.cs | 16 ++-- .../Encoder/BaseEncoder.cs | 84 +++++++++---------- 3 files changed, 64 insertions(+), 68 deletions(-) diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 763535129a..a212d3f3a7 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -462,7 +462,7 @@ namespace MediaBrowser.Api Logger.LogInformation("Transcoding kill timer stopped for JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); - KillTranscodingJob(job, true, path => true); + KillTranscodingJob(job, true, path => true).GetAwaiter().GetResult(); } /// @@ -472,9 +472,9 @@ namespace MediaBrowser.Api /// The play session identifier. /// The delete files. /// Task. - internal void KillTranscodingJobs(string deviceId, string playSessionId, Func deleteFiles) + internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func deleteFiles) { - KillTranscodingJobs(j => + return KillTranscodingJobs(j => { if (!string.IsNullOrWhiteSpace(playSessionId)) { @@ -492,7 +492,7 @@ namespace MediaBrowser.Api /// The kill job. /// The delete files. /// Task. - private void KillTranscodingJobs(Func killJob, Func deleteFiles) + private Task KillTranscodingJobs(Func killJob, Func deleteFiles) { var jobs = new List(); @@ -505,13 +505,18 @@ namespace MediaBrowser.Api if (jobs.Count == 0) { - return; + return Task.CompletedTask; } - foreach (var job in jobs) + IEnumerable GetKillJobs() { - KillTranscodingJob(job, false, deleteFiles); + foreach (var job in jobs) + { + yield return KillTranscodingJob(job, false, deleteFiles); + } } + + return Task.WhenAll(GetKillJobs()); } /// @@ -520,7 +525,7 @@ namespace MediaBrowser.Api /// The job. /// if set to true [close live stream]. /// The delete. - private async void KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func delete) + private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func delete) { job.DisposeKillTimer(); @@ -577,7 +582,7 @@ namespace MediaBrowser.Api if (delete(job.Path)) { - DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); + await DeletePartialStreamFiles(job.Path, job.Type, 0, 1500).ConfigureAwait(false); } if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) @@ -588,12 +593,12 @@ namespace MediaBrowser.Api } catch (Exception ex) { - Logger.LogError(ex, "Error closing live stream for {path}", job.Path); + Logger.LogError(ex, "Error closing live stream for {Path}", job.Path); } } } - private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) + private async Task DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs) { if (retryCount >= 10) { @@ -623,7 +628,7 @@ namespace MediaBrowser.Api { Logger.LogError(ex, "Error deleting partial stream file(s) {path}", path); - DeletePartialStreamFiles(path, jobType, retryCount + 1, 500); + await DeletePartialStreamFiles(path, jobType, retryCount + 1, 500).ConfigureAwait(false); } catch (Exception ex) { @@ -650,8 +655,7 @@ namespace MediaBrowser.Api var name = Path.GetFileNameWithoutExtension(outputFilePath); var filesToDelete = _fileSystem.GetFilePaths(directory) - .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1) - .ToList(); + .Where(f => f.IndexOf(name, StringComparison.OrdinalIgnoreCase) != -1); Exception e = null; diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs index 72a9430921..5240144f11 100644 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -366,9 +366,9 @@ namespace MediaBrowser.Api.UserLibrary /// Posts the specified request. /// /// The request. - public void Delete(OnPlaybackStopped request) + public Task Delete(OnPlaybackStopped request) { - Post(new ReportPlaybackStopped + return Post(new ReportPlaybackStopped { ItemId = new Guid(request.Id), PositionTicks = request.PositionTicks, @@ -379,20 +379,18 @@ namespace MediaBrowser.Api.UserLibrary }); } - public void Post(ReportPlaybackStopped request) + public async Task Post(ReportPlaybackStopped request) { Logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", request.PlaySessionId ?? string.Empty); if (!string.IsNullOrWhiteSpace(request.PlaySessionId)) { - ApiEntryPoint.Instance.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true); + await ApiEntryPoint.Instance.KillTranscodingJobs(_authContext.GetAuthorizationInfo(Request).DeviceId, request.PlaySessionId, s => true); } request.SessionId = GetSession(_sessionContext).Id; - var task = _sessionManager.OnPlaybackStopped(request); - - Task.WaitAll(task); + await _sessionManager.OnPlaybackStopped(request); } /// @@ -403,10 +401,10 @@ namespace MediaBrowser.Api.UserLibrary { var task = MarkUnplayed(request); - return ToOptimizedResult(task.Result); + return ToOptimizedResult(task); } - private async Task MarkUnplayed(MarkUnplayedItem request) + private UserItemDataDto MarkUnplayed(MarkUnplayedItem request) { var user = _userManager.GetUserById(request.UserId); diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index b231938b5e..cf239671fa 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -76,24 +76,26 @@ namespace MediaBrowser.MediaEncoding.Encoder var commandLineArgs = GetCommandLineArguments(encodingJob); - Process process = Process.Start(new ProcessStartInfo + Process process = new Process { - WindowStyle = ProcessWindowStyle.Hidden, - CreateNoWindow = true, - UseShellExecute = false, + StartInfo = new ProcessStartInfo + { + WindowStyle = ProcessWindowStyle.Hidden, + CreateNoWindow = true, + UseShellExecute = false, - // Must consume both stdout and stderr or deadlocks may occur - //RedirectStandardOutput = true, - RedirectStandardError = true, - RedirectStandardInput = true, + // Must consume both stdout and stderr or deadlocks may occur + //RedirectStandardOutput = true, + RedirectStandardError = true, + RedirectStandardInput = true, - FileName = MediaEncoder.EncoderPath, - Arguments = commandLineArgs, + FileName = MediaEncoder.EncoderPath, + Arguments = commandLineArgs, - ErrorDialog = false - }); - - process.EnableRaisingEvents = true; + ErrorDialog = false + }, + EnableRaisingEvents = true + }; var workingDirectory = GetWorkingDirectory(options); if (!string.IsNullOrWhiteSpace(workingDirectory)) @@ -132,50 +134,42 @@ namespace MediaBrowser.MediaEncoding.Encoder cancellationToken.Register(async () => await Cancel(process, encodingJob)); - // MUST read both stdout and stderr asynchronously or a deadlock may occur - //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 or for the process to stop - Task file = WaitForFileAsync(encodingJob.OutputFilePath); - await Task.WhenAny(encodingJob.TaskCompletionSource.Task, file).ConfigureAwait(false); + Logger.LogInformation("test0"); - return encodingJob; - } - - public static Task WaitForFileAsync(string path) - { - if (File.Exists(path)) + if (File.Exists(encodingJob.OutputFilePath)) { - return Task.CompletedTask; + return encodingJob; } - var tcs = new TaskCompletionSource(); - FileSystemWatcher watcher = new FileSystemWatcher(Path.GetDirectoryName(path)); + Logger.LogInformation("test1"); - watcher.Created += (s, e) => + using (var watcher = new FileSystemWatcher(Path.GetDirectoryName(encodingJob.OutputFilePath))) { - if (e.Name == Path.GetFileName(path)) + var tcs = new TaskCompletionSource(); + string fileName = Path.GetFileName(encodingJob.OutputFilePath); + + watcher.Created += (s, e) => { - watcher.Dispose(); - tcs.TrySetResult(true); - } - }; + if (e.Name == fileName) + { + tcs.TrySetResult(true); + } + }; - watcher.Renamed += (s, e) => - { - if (e.Name == Path.GetFileName(path)) - { - watcher.Dispose(); - tcs.TrySetResult(true); - } - }; + watcher.EnableRaisingEvents = true; - watcher.EnableRaisingEvents = true; + Logger.LogInformation("test2"); - return tcs.Task; + // Wait for the file to or for the process to stop + await Task.WhenAny(encodingJob.TaskCompletionSource.Task, tcs.Task).ConfigureAwait(false); + + Logger.LogInformation("test3"); + + return encodingJob; + } } private async Task Cancel(Process process, EncodingJob job) From 1cdcace0618658d1e99e98c3f539baf3ae72ab14 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 29 Jan 2019 22:06:04 +0100 Subject: [PATCH 3/3] Remove dead code --- .../MediaEncoding/IMediaEncoder.cs | 22 - .../Encoder/AudioEncoder.cs | 58 --- .../Encoder/BaseEncoder.cs | 399 ------------------ .../Encoder/EncodingJob.cs | 147 ------- .../Encoder/EncodingJobFactory.cs | 225 ---------- .../Encoder/MediaEncoder.cs | 43 -- .../Encoder/VideoEncoder.cs | 59 --- 7 files changed, 953 deletions(-) delete mode 100644 MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs delete mode 100644 MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs delete mode 100644 MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs delete mode 100644 MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs delete mode 100644 MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 48055a37e6..057e439104 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -82,28 +82,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// System.String. string GetTimeParameter(long ticks); - /// - /// Encodes the audio. - /// - /// The options. - /// The progress. - /// The cancellation token. - /// Task. - Task EncodeAudio(EncodingJobOptions options, - IProgress progress, - CancellationToken cancellationToken); - - /// - /// Encodes the video. - /// - /// The options. - /// The progress. - /// The cancellation token. - /// Task<System.String>. - Task EncodeVideo(EncodingJobOptions options, - IProgress progress, - CancellationToken cancellationToken); - Task ConvertImage(string inputPath, string outputPath); /// diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs deleted file mode 100644 index d5773fe315..0000000000 --- a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; - -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 => false; - } -} diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs deleted file mode 100644 index cf239671fa..0000000000 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ /dev/null @@ -1,399 +0,0 @@ -using System; -using System.Diagnostics; -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 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 Start( - EncodingJobOptions options, - IProgress progress, - CancellationToken cancellationToken) - { - var encodingJob = await new EncodingJobFactory(Logger, LibraryManager, MediaSourceManager, MediaEncoder) - .CreateJob(options, EncodingHelper, IsVideoEncoder, progress, cancellationToken).ConfigureAwait(false); - - encodingJob.OutputFilePath = GetOutputFilePath(encodingJob); - Directory.CreateDirectory(Path.GetDirectoryName(encodingJob.OutputFilePath)); - - encodingJob.ReadInputAtNativeFramerate = options.ReadInputAtNativeFramerate; - - await AcquireResources(encodingJob, cancellationToken).ConfigureAwait(false); - - var commandLineArgs = GetCommandLineArguments(encodingJob); - - Process process = new Process - { - StartInfo = new ProcessStartInfo - { - WindowStyle = ProcessWindowStyle.Hidden, - 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, - - 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"); - Directory.CreateDirectory(Path.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(async () => await Cancel(process, encodingJob)); - - // 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); - - Logger.LogInformation("test0"); - - if (File.Exists(encodingJob.OutputFilePath)) - { - return encodingJob; - } - - Logger.LogInformation("test1"); - - using (var watcher = new FileSystemWatcher(Path.GetDirectoryName(encodingJob.OutputFilePath))) - { - var tcs = new TaskCompletionSource(); - string fileName = Path.GetFileName(encodingJob.OutputFilePath); - - watcher.Created += (s, e) => - { - if (e.Name == fileName) - { - tcs.TrySetResult(true); - } - }; - - watcher.EnableRaisingEvents = true; - - Logger.LogInformation("test2"); - - // Wait for the file to or for the process to stop - await Task.WhenAny(encodingJob.TaskCompletionSource.Task, tcs.Task).ConfigureAwait(false); - - Logger.LogInformation("test3"); - - return encodingJob; - } - } - - private async Task Cancel(Process process, EncodingJob job) - { - Logger.LogInformation("Killing ffmpeg process for {0}", job.OutputFilePath); - - //process.Kill(); - await process.StandardInput.WriteLineAsync("q"); - - job.IsCancelled = true; - } - - /// - /// Processes the exited. - /// - /// The process. - /// The job. - private void OnFfMpegProcessExited(Process process, EncodingJob job) - { - job.HasExited = true; - - Logger.LogDebug("Disposing stream resources"); - job.Dispose(); - - var isSuccessful = false; - - try - { - var exitCode = process.ExitCode; - Logger.LogInformation("FFMpeg exited with code {0}", exitCode); - - isSuccessful = exitCode == 0; - } - catch (Exception ex) - { - Logger.LogError(ex, "FFMpeg exited with an error."); - } - - if (isSuccessful && !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("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).ToLowerInvariant(); - return Path.Combine(folder, filename); - } - - protected virtual string GetOutputFileExtension(EncodingJob state) - { - if (!string.IsNullOrWhiteSpace(state.Options.Container)) - { - return "." + state.Options.Container; - } - - return null; - } - - /// - /// Gets the name of the output video codec - /// - /// The state. - /// System.String. - 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.ToLowerInvariant()) - { - 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/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs deleted file mode 100644 index cd7de94ce1..0000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.IO; -using System.Threading.Tasks; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dlna; -using Microsoft.Extensions.Logging; - -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 TaskCompletionSource TaskCompletionSource; - - public EncodingJobOptions Options - { - get => (EncodingJobOptions)BaseRequest; - set => BaseRequest = value; - } - - public Guid Id { get; set; } - - public bool EstimateContentLength { get; set; } - public TranscodeSeekInfo TranscodeSeekInfo { get; set; } - - public string ItemType { get; set; } - - private readonly ILogger _logger; - private readonly IMediaSourceManager _mediaSourceManager; - - public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) : - base(TranscodingJobType.Progressive) - { - _logger = logger; - _mediaSourceManager = mediaSourceManager; - Id = Guid.NewGuid(); - - TaskCompletionSource = new TaskCompletionSource(); - } - - public override void Dispose() - { - DisposeLiveStream(); - DisposeLogStream(); - DisposeIsoMount(); - } - - private void DisposeLogStream() - { - if (LogFileStream != null) - { - try - { - LogFileStream.Dispose(); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error disposing log stream"); - } - - 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.LogError(ex, "Error closing media source"); - } - } - } - - - private void DisposeIsoMount() - { - if (IsoMount != null) - { - try - { - IsoMount.Dispose(); - } - catch (Exception ex) - { - _logger.LogError("Error disposing iso mount", ex); - } - - IsoMount = null; - } - } - - public 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 5f84a03223..0000000000 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Model.Dlna; -using MediaBrowser.Model.Entities; -using Microsoft.Extensions.Logging; - -namespace MediaBrowser.MediaEncoding.Encoder -{ - public class EncodingJobFactory - { - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly IMediaSourceManager _mediaSourceManager; - private readonly IMediaEncoder _mediaEncoder; - - public EncodingJobFactory(ILogger logger, ILibraryManager libraryManager, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder) - { - _logger = logger; - _libraryManager = libraryManager; - _mediaSourceManager = mediaSourceManager; - _mediaEncoder = mediaEncoder; - } - - public async Task CreateJob(EncodingJobOptions options, EncodingHelper encodingHelper, bool isVideoRequest, IProgress progress, CancellationToken cancellationToken) - { - var request = options; - - if (string.IsNullOrEmpty(request.AudioCodec)) - { - request.AudioCodec = InferAudioCodec(request.Container); - } - - 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)).ToArray(); - request.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.AudioCodec)) - { - state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(); - } - - if (!string.IsNullOrWhiteSpace(request.SubtitleCodec)) - { - state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(); - request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToSubtitleCodec(i)) - ?? state.SupportedSubtitleCodecs.FirstOrDefault(); - } - - var item = _libraryManager.GetItemById(request.Id); - state.ItemType = item.GetType().Name; - - state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); - - // TODO - // 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; - // } - - // TODO network path substition useful ? - var mediaSources = await _mediaSourceManager.GetPlayackMediaSources(item, null, true, true, 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, 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?.BitRate, - state.VideoStream?.Width, - state.VideoStream?.Height, - state.OutputVideoBitrate.Value, - 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; - } - - private string InferAudioCodec(string container) - { - var ext = "." + (container ?? string.Empty); - - if (string.Equals(ext, ".mp3", StringComparison.OrdinalIgnoreCase)) - { - return "mp3"; - } - else if (string.Equals(ext, ".aac", StringComparison.OrdinalIgnoreCase)) - { - return "aac"; - } - else if (string.Equals(ext, ".wma", StringComparison.OrdinalIgnoreCase)) - { - return "wma"; - } - else if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) - || string.Equals(ext, ".oga", StringComparison.OrdinalIgnoreCase) - || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase) - || string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase) - || string.Equals(ext, ".webma", StringComparison.OrdinalIgnoreCase)) - { - return "vorbis"; - } - - return "copy"; - } - - 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.Container; - - 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/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 54344424de..d922f1068a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -878,49 +878,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - public async Task EncodeAudio(EncodingJobOptions options, - IProgress 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 EncodeVideo(EncodingJobOptions options, - IProgress progress, - CancellationToken cancellationToken) - { - _logger.LogError("EncodeVideo"); - 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(); diff --git a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs deleted file mode 100644 index bf23a73bd4..0000000000 --- a/MediaBrowser.MediaEncoding/Encoder/VideoEncoder.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.MediaEncoding; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Diagnostics; -using MediaBrowser.Model.IO; -using Microsoft.Extensions.Logging; - -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 => true; - } -}