aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.MediaEncoding/Encoder
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.MediaEncoding/Encoder')
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs91
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs233
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs168
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs95
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs323
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs64
6 files changed, 913 insertions, 61 deletions
diff --git a/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
new file mode 100644
index 000000000..08b7fbe49
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/AudioEncoder.cs
@@ -0,0 +1,91 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class AudioEncoder
+ {
+ private readonly string _ffmpegPath;
+ private readonly ILogger _logger;
+ private readonly IFileSystem _fileSystem;
+ private readonly IApplicationPaths _appPaths;
+ private readonly IIsoManager _isoManager;
+ private readonly ILiveTvManager _liveTvManager;
+
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+ public AudioEncoder(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths, IIsoManager isoManager, ILiveTvManager liveTvManager)
+ {
+ _ffmpegPath = ffmpegPath;
+ _logger = logger;
+ _fileSystem = fileSystem;
+ _appPaths = appPaths;
+ _isoManager = isoManager;
+ _liveTvManager = liveTvManager;
+ }
+
+ public Task BeginEncoding(InternalEncodingTask task)
+ {
+ return new FFMpegProcess(_ffmpegPath, _logger, _fileSystem, _appPaths, _isoManager, _liveTvManager).Start(task, GetArguments);
+ }
+
+ private string GetArguments(InternalEncodingTask task, string mountedPath)
+ {
+ var options = task.Request;
+
+ return string.Format("{0} -i {1} {2} -id3v2_version 3 -write_id3v1 1 \"{3}\"",
+ GetInputModifier(task),
+ GetInputArgument(task),
+ GetOutputModifier(task),
+ options.OutputPath).Trim();
+ }
+
+ private string GetInputModifier(InternalEncodingTask task)
+ {
+ return EncodingUtils.GetInputModifier(task);
+ }
+
+ private string GetInputArgument(InternalEncodingTask task)
+ {
+ return EncodingUtils.GetInputArgument(new List<string> { task.MediaPath }, task.IsInputRemote);
+ }
+
+ private string GetOutputModifier(InternalEncodingTask task)
+ {
+ var options = task.Request;
+
+ var audioTranscodeParams = new List<string>
+ {
+ "-threads " + EncodingUtils.GetNumberOfThreads(task, false).ToString(_usCulture),
+ "-vn"
+ };
+
+ var bitrate = EncodingUtils.GetAudioBitrateParam(task);
+
+ if (bitrate.HasValue)
+ {
+ audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(_usCulture));
+ }
+
+ var channels = EncodingUtils.GetNumAudioChannelsParam(options, task.AudioStream);
+
+ if (channels.HasValue)
+ {
+ audioTranscodeParams.Add("-ac " + channels.Value);
+ }
+
+ if (options.AudioSampleRate.HasValue)
+ {
+ audioTranscodeParams.Add("-ar " + options.AudioSampleRate.Value);
+ }
+
+ return string.Join(" ", audioTranscodeParams.ToArray());
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
new file mode 100644
index 000000000..79d512dc1
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/EncodingUtils.cs
@@ -0,0 +1,233 @@
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public static class EncodingUtils
+ {
+ private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+ public static string GetInputArgument(List<string> inputFiles, bool isRemote)
+ {
+ if (isRemote)
+ {
+ return GetHttpInputArgument(inputFiles);
+ }
+
+ 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(List<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);
+
+ 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)
+ {
+ return string.Format("file:\"{0}\"", path);
+ }
+
+ /// <summary>
+ /// Gets the HTTP input argument.
+ /// </summary>
+ /// <param name="inputFiles">The input files.</param>
+ /// <returns>System.String.</returns>
+ private static string GetHttpInputArgument(IEnumerable<string> inputFiles)
+ {
+ var url = inputFiles.First();
+
+ return string.Format("\"{0}\"", url);
+ }
+
+ public static string GetAudioInputModifier(InternalEncodingTask options)
+ {
+ return GetCommonInputModifier(options);
+ }
+
+ public static string GetInputModifier(InternalEncodingTask options)
+ {
+ var inputModifier = GetCommonInputModifier(options);
+
+ //if (state.VideoRequest != null)
+ //{
+ // inputModifier += " -fflags genpts";
+ //}
+
+ //if (!string.IsNullOrEmpty(state.InputVideoCodec))
+ //{
+ // inputModifier += " -vcodec " + state.InputVideoCodec;
+ //}
+
+ //if (!string.IsNullOrEmpty(state.InputVideoSync))
+ //{
+ // inputModifier += " -vsync " + state.InputVideoSync;
+ //}
+
+ return inputModifier;
+ }
+
+ private static string GetCommonInputModifier(InternalEncodingTask options)
+ {
+ var inputModifier = string.Empty;
+
+ if (options.EnableDebugLogging)
+ {
+ inputModifier += "-loglevel debug";
+ }
+
+ var probeSize = GetProbeSizeArgument(options.InputVideoType.HasValue && options.InputVideoType.Value == VideoType.Dvd);
+ inputModifier += " " + probeSize;
+ inputModifier = inputModifier.Trim();
+
+ if (!string.IsNullOrWhiteSpace(options.UserAgent))
+ {
+ inputModifier += " -user-agent \"" + options.UserAgent + "\"";
+ }
+
+ inputModifier += " " + GetFastSeekValue(options.Request);
+ inputModifier = inputModifier.Trim();
+
+ if (!string.IsNullOrEmpty(options.InputFormat))
+ {
+ inputModifier += " -f " + options.InputFormat;
+ }
+
+ if (!string.IsNullOrEmpty(options.InputAudioCodec))
+ {
+ inputModifier += " -acodec " + options.InputAudioCodec;
+ }
+
+ if (!string.IsNullOrEmpty(options.InputAudioSync))
+ {
+ inputModifier += " -async " + options.InputAudioSync;
+ }
+
+ if (options.ReadInputAtNativeFramerate)
+ {
+ inputModifier += " -re";
+ }
+
+ return inputModifier;
+ }
+
+ private static string GetFastSeekValue(EncodingOptions options)
+ {
+ var time = options.StartTimeTicks;
+
+ if (time.HasValue)
+ {
+ var seconds = TimeSpan.FromTicks(time.Value).TotalSeconds;
+
+ if (seconds > 0)
+ {
+ return string.Format("-ss {0}", seconds.ToString(UsCulture));
+ }
+ }
+
+ return string.Empty;
+ }
+
+ public static string GetProbeSizeArgument(bool isDvd)
+ {
+ return isDvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
+ }
+
+ public static int? GetAudioBitrateParam(InternalEncodingTask task)
+ {
+ if (task.Request.AudioBitRate.HasValue)
+ {
+ // Make sure we don't request a bitrate higher than the source
+ var currentBitrate = task.AudioStream == null ? task.Request.AudioBitRate.Value : task.AudioStream.BitRate ?? task.Request.AudioBitRate.Value;
+
+ return Math.Min(currentBitrate, task.Request.AudioBitRate.Value);
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Gets the number of audio channels to specify on the command line
+ /// </summary>
+ /// <param name="request">The request.</param>
+ /// <param name="audioStream">The audio stream.</param>
+ /// <returns>System.Nullable{System.Int32}.</returns>
+ public static int? GetNumAudioChannelsParam(EncodingOptions request, MediaStream audioStream)
+ {
+ if (audioStream != null)
+ {
+ if (audioStream.Channels > 2 && string.Equals(request.AudioCodec, "wma", StringComparison.OrdinalIgnoreCase))
+ {
+ // wmav2 currently only supports two channel output
+ return 2;
+ }
+ }
+
+ if (request.MaxAudioChannels.HasValue)
+ {
+ if (audioStream != null && audioStream.Channels.HasValue)
+ {
+ return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value);
+ }
+
+ return request.MaxAudioChannels.Value;
+ }
+
+ return request.AudioChannels;
+ }
+
+ public static int GetNumberOfThreads(InternalEncodingTask state, bool isWebm)
+ {
+ // Use more when this is true. -re will keep cpu usage under control
+ if (state.ReadInputAtNativeFramerate)
+ {
+ if (isWebm)
+ {
+ return Math.Max(Environment.ProcessorCount - 1, 1);
+ }
+
+ return 0;
+ }
+
+ // Webm: http://www.webmproject.org/docs/encoder-parameters/
+ // The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads
+ // for the coefficient data if the encoder selected --token-parts > 0 at encode time.
+
+ switch (state.QualitySetting)
+ {
+ case EncodingQuality.HighSpeed:
+ return 2;
+ case EncodingQuality.HighQuality:
+ return 2;
+ case EncodingQuality.MaxQuality:
+ return isWebm ? 2 : 0;
+ default:
+ throw new Exception("Unrecognized MediaEncodingQuality value.");
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs b/MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs
new file mode 100644
index 000000000..05733aef0
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/FFMpegProcess.cs
@@ -0,0 +1,168 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class FFMpegProcess : IDisposable
+ {
+ private readonly string _ffmpegPath;
+ private readonly ILogger _logger;
+ private readonly IFileSystem _fileSystem;
+ private readonly IApplicationPaths _appPaths;
+ private readonly IIsoManager _isoManager;
+ private readonly ILiveTvManager _liveTvManager;
+
+ private Stream _logFileStream;
+ private InternalEncodingTask _task;
+ private IIsoMount _isoMount;
+
+ public FFMpegProcess(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths, IIsoManager isoManager, ILiveTvManager liveTvManager)
+ {
+ _ffmpegPath = ffmpegPath;
+ _logger = logger;
+ _fileSystem = fileSystem;
+ _appPaths = appPaths;
+ _isoManager = isoManager;
+ _liveTvManager = liveTvManager;
+ }
+
+ public async Task Start(InternalEncodingTask task, Func<InternalEncodingTask,string,string> argumentsFactory)
+ {
+ _task = task;
+ if (!File.Exists(_ffmpegPath))
+ {
+ throw new InvalidOperationException("ffmpeg was not found at " + _ffmpegPath);
+ }
+
+ Directory.CreateDirectory(Path.GetDirectoryName(task.Request.OutputPath));
+
+ string mountedPath = null;
+ if (task.InputVideoType.HasValue && task.InputVideoType == VideoType.Iso && task.IsoType.HasValue)
+ {
+ if (_isoManager.CanMount(task.MediaPath))
+ {
+ _isoMount = await _isoManager.Mount(task.MediaPath, CancellationToken.None).ConfigureAwait(false);
+ mountedPath = _isoMount.MountedPath;
+ }
+ }
+
+ var process = new Process
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ CreateNoWindow = true,
+ UseShellExecute = false,
+
+ // Must consume both stdout and stderr or deadlocks may occur
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+
+ FileName = _ffmpegPath,
+ WorkingDirectory = Path.GetDirectoryName(_ffmpegPath),
+ Arguments = argumentsFactory(task, mountedPath),
+
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false
+ },
+
+ EnableRaisingEvents = true
+ };
+
+ _logger.Info(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
+
+ var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-" + task.Id + ".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.
+ _logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
+
+ process.Exited += process_Exited;
+
+ try
+ {
+ process.Start();
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error starting ffmpeg", ex);
+
+ task.OnError();
+
+ DisposeLogFileStream();
+
+ process.Dispose();
+
+ throw;
+ }
+
+ task.OnBegin();
+
+ // MUST read both stdout and stderr asynchronously or a deadlock may occurr
+ process.BeginOutputReadLine();
+
+#pragma warning disable 4014
+ // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
+ process.StandardError.BaseStream.CopyToAsync(_logFileStream);
+#pragma warning restore 4014
+ }
+
+ async void process_Exited(object sender, EventArgs e)
+ {
+ var process = (Process)sender;
+
+ if (_isoMount != null)
+ {
+ _isoMount.Dispose();
+ _isoMount = null;
+ }
+
+ DisposeLogFileStream();
+
+ try
+ {
+ _logger.Info("FFMpeg exited with code {0} for {1}", process.ExitCode, _task.Request.OutputPath);
+ }
+ catch
+ {
+ _logger.Info("FFMpeg exited with an error for {0}", _task.Request.OutputPath);
+ }
+
+ _task.OnCompleted();
+
+ if (!string.IsNullOrEmpty(_task.LiveTvStreamId))
+ {
+ try
+ {
+ await _liveTvManager.CloseLiveStream(_task.LiveTvStreamId, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error closing live tv stream", ex);
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ DisposeLogFileStream();
+ }
+
+ private void DisposeLogFileStream()
+ {
+ if (_logFileStream != null)
+ {
+ _logFileStream.Dispose();
+ _logFileStream = null;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs
new file mode 100644
index 000000000..826525aef
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTask.cs
@@ -0,0 +1,95 @@
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+
+namespace MediaBrowser.MediaEncoding.Encoder
+{
+ public class InternalEncodingTask
+ {
+ public string Id { get; set; }
+
+ public CancellationTokenSource CancellationTokenSource { get; set; }
+
+ public double ProgressPercentage { get; set; }
+
+ public EncodingOptions Request { get; set; }
+
+ public VideoEncodingOptions VideoRequest
+ {
+ get { return Request as VideoEncodingOptions; }
+ }
+
+ public string MediaPath { get; set; }
+ public List<string> StreamFileNames { get; set; }
+ public bool IsInputRemote { get; set; }
+
+ public VideoType? InputVideoType { get; set; }
+ public IsoType? IsoType { get; set; }
+ public long? InputRunTimeTicks;
+
+ public string AudioSync = "1";
+ public string VideoSync = "vfr";
+
+ public string InputAudioSync { get; set; }
+ public string InputVideoSync { get; set; }
+
+ public bool DeInterlace { get; set; }
+
+ public bool ReadInputAtNativeFramerate { get; set; }
+
+ public string InputFormat { get; set; }
+
+ public string InputVideoCodec { get; set; }
+
+ public string InputAudioCodec { get; set; }
+
+ public string LiveTvStreamId { get; set; }
+
+ public MediaStream AudioStream { get; set; }
+ public MediaStream VideoStream { get; set; }
+ public MediaStream SubtitleStream { get; set; }
+ public bool HasMediaStreams { get; set; }
+
+ public int SegmentLength = 10;
+ public int HlsListSize;
+
+ public string MimeType { get; set; }
+ public string OrgPn { get; set; }
+ public bool EnableMpegtsM2TsMode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the user agent.
+ /// </summary>
+ /// <value>The user agent.</value>
+ public string UserAgent { get; set; }
+
+ public EncodingQuality QualitySetting { get; set; }
+
+ public InternalEncodingTask()
+ {
+ Id = Guid.NewGuid().ToString("N");
+ CancellationTokenSource = new CancellationTokenSource();
+ StreamFileNames = new List<string>();
+ }
+
+ public bool EnableDebugLogging { get; set; }
+
+ internal void OnBegin()
+ {
+
+ }
+
+ internal void OnCompleted()
+ {
+
+ }
+
+ internal void OnError()
+ {
+
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs
new file mode 100644
index 000000000..fa9b87906
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Encoder/InternalEncodingTaskFactory.cs
@@ -0,0 +1,323 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
+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 InternalEncodingTaskFactory
+ {
+ private readonly ILibraryManager _libraryManager;
+ private readonly ILiveTvManager _liveTvManager;
+ private readonly IItemRepository _itemRepo;
+ private readonly IServerConfigurationManager _config;
+
+ public InternalEncodingTaskFactory(ILibraryManager libraryManager, ILiveTvManager liveTvManager, IItemRepository itemRepo, IServerConfigurationManager config)
+ {
+ _libraryManager = libraryManager;
+ _liveTvManager = liveTvManager;
+ _itemRepo = itemRepo;
+ _config = config;
+ }
+
+ public async Task<InternalEncodingTask> Create(EncodingOptions request, CancellationToken cancellationToken)
+ {
+ ValidateInput(request);
+
+ var state = new InternalEncodingTask
+ {
+ Request = request
+ };
+
+ var item = string.IsNullOrEmpty(request.MediaSourceId) ?
+ _libraryManager.GetItemById(new Guid(request.ItemId)) :
+ _libraryManager.GetItemById(new Guid(request.MediaSourceId));
+
+ if (item is ILiveTvRecording)
+ {
+ var recording = await _liveTvManager.GetInternalRecording(request.ItemId, cancellationToken).ConfigureAwait(false);
+
+ if (string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
+ {
+ state.InputVideoType = VideoType.VideoFile;
+ }
+
+ var path = recording.RecordingInfo.Path;
+ var mediaUrl = recording.RecordingInfo.Url;
+
+ if (string.IsNullOrWhiteSpace(path) && string.IsNullOrWhiteSpace(mediaUrl))
+ {
+ var streamInfo = await _liveTvManager.GetRecordingStream(request.ItemId, cancellationToken).ConfigureAwait(false);
+
+ state.LiveTvStreamId = streamInfo.Id;
+
+ path = streamInfo.Path;
+ mediaUrl = streamInfo.Url;
+ }
+
+ if (!string.IsNullOrEmpty(path) && File.Exists(path))
+ {
+ state.MediaPath = path;
+ state.IsInputRemote = false;
+ }
+ else if (!string.IsNullOrEmpty(mediaUrl))
+ {
+ state.MediaPath = mediaUrl;
+ state.IsInputRemote = true;
+ }
+
+ state.InputRunTimeTicks = recording.RunTimeTicks;
+ if (recording.RecordingInfo.Status == RecordingStatus.InProgress && !state.IsInputRemote)
+ {
+ await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
+ }
+
+ state.ReadInputAtNativeFramerate = recording.RecordingInfo.Status == RecordingStatus.InProgress;
+ state.AudioSync = "1000";
+ state.DeInterlace = true;
+ state.InputVideoSync = "-1";
+ state.InputAudioSync = "1";
+ }
+ else if (item is LiveTvChannel)
+ {
+ var channel = _liveTvManager.GetInternalChannel(request.ItemId);
+
+ if (string.Equals(channel.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
+ {
+ state.InputVideoType = VideoType.VideoFile;
+ }
+
+ var streamInfo = await _liveTvManager.GetChannelStream(request.ItemId, cancellationToken).ConfigureAwait(false);
+
+ state.LiveTvStreamId = streamInfo.Id;
+
+ if (!string.IsNullOrEmpty(streamInfo.Path) && File.Exists(streamInfo.Path))
+ {
+ state.MediaPath = streamInfo.Path;
+ state.IsInputRemote = false;
+
+ await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
+ }
+ else if (!string.IsNullOrEmpty(streamInfo.Url))
+ {
+ state.MediaPath = streamInfo.Url;
+ state.IsInputRemote = true;
+ }
+
+ state.ReadInputAtNativeFramerate = true;
+ state.AudioSync = "1000";
+ state.DeInterlace = true;
+ state.InputVideoSync = "-1";
+ state.InputAudioSync = "1";
+ }
+ else
+ {
+ state.MediaPath = item.Path;
+ state.IsInputRemote = item.LocationType == LocationType.Remote;
+
+ var video = item as Video;
+
+ if (video != null)
+ {
+ state.InputVideoType = video.VideoType;
+ state.IsoType = video.IsoType;
+
+ state.StreamFileNames = video.PlayableStreamFileNames.ToList();
+ }
+
+ state.InputRunTimeTicks = item.RunTimeTicks;
+ }
+
+ var videoRequest = request as VideoEncodingOptions;
+
+ var mediaStreams = _itemRepo.GetMediaStreams(new MediaStreamQuery
+ {
+ ItemId = item.Id
+
+ }).ToList();
+
+ if (videoRequest != null)
+ {
+ state.VideoStream = GetMediaStream(mediaStreams, videoRequest.VideoStreamIndex, MediaStreamType.Video);
+ state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false);
+ state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
+
+ if (state.VideoStream != null && state.VideoStream.IsInterlaced)
+ {
+ state.DeInterlace = true;
+ }
+ }
+ else
+ {
+ state.AudioStream = GetMediaStream(mediaStreams, null, MediaStreamType.Audio, true);
+ }
+
+ state.HasMediaStreams = mediaStreams.Count > 0;
+
+ state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 10;
+ state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440;
+
+ state.QualitySetting = GetQualitySetting();
+
+ ApplyDeviceProfileSettings(state);
+
+ return state;
+ }
+
+ private void ValidateInput(EncodingOptions request)
+ {
+ if (string.IsNullOrWhiteSpace(request.ItemId))
+ {
+ throw new ArgumentException("ItemId is required.");
+ }
+ if (string.IsNullOrWhiteSpace(request.OutputPath))
+ {
+ throw new ArgumentException("OutputPath is required.");
+ }
+ if (string.IsNullOrWhiteSpace(request.Container))
+ {
+ throw new ArgumentException("Container is required.");
+ }
+ if (string.IsNullOrWhiteSpace(request.AudioCodec))
+ {
+ throw new ArgumentException("AudioCodec is required.");
+ }
+
+ var videoRequest = request as VideoEncodingOptions;
+
+ if (videoRequest == null)
+ {
+ return;
+ }
+ }
+
+ /// <summary>
+ /// Determines which stream will be used for playback
+ /// </summary>
+ /// <param name="allStream">All stream.</param>
+ /// <param name="desiredIndex">Index of the desired.</param>
+ /// <param name="type">The type.</param>
+ /// <param name="returnFirstIfNoIndex">if set to <c>true</c> [return first if no index].</param>
+ /// <returns>MediaStream.</returns>
+ private MediaStream GetMediaStream(IEnumerable<MediaStream> allStream, int? desiredIndex, MediaStreamType type, bool returnFirstIfNoIndex = true)
+ {
+ var streams = allStream.Where(s => s.Type == type).OrderBy(i => i.Index).ToList();
+
+ if (desiredIndex.HasValue)
+ {
+ var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
+
+ if (stream != null)
+ {
+ return stream;
+ }
+ }
+
+ if (returnFirstIfNoIndex && type == MediaStreamType.Audio)
+ {
+ return streams.FirstOrDefault(i => i.Channels.HasValue && i.Channels.Value > 0) ??
+ streams.FirstOrDefault();
+ }
+
+ // Just return the first one
+ return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
+ }
+
+ private void ApplyDeviceProfileSettings(InternalEncodingTask state)
+ {
+ var profile = state.Request.DeviceProfile;
+
+ if (profile == null)
+ {
+ // Don't use settings from the default profile.
+ // Only use a specific profile if it was requested.
+ return;
+ }
+
+ var container = state.Request.Container;
+
+ var audioCodec = state.Request.AudioCodec;
+
+ if (string.Equals(audioCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.AudioStream != null)
+ {
+ audioCodec = state.AudioStream.Codec;
+ }
+
+ var videoCodec = state.VideoRequest == null ? null : state.VideoRequest.VideoCodec;
+
+ if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase) && state.VideoStream != null)
+ {
+ videoCodec = state.VideoStream.Codec;
+ }
+
+ var mediaProfile = state.VideoRequest == null ?
+ profile.GetAudioMediaProfile(container, audioCodec, state.AudioStream) :
+ profile.GetVideoMediaProfile(container, audioCodec, videoCodec, state.AudioStream, state.VideoStream);
+
+ if (mediaProfile != null)
+ {
+ state.MimeType = mediaProfile.MimeType;
+ state.OrgPn = mediaProfile.OrgPn;
+ }
+
+ var transcodingProfile = state.VideoRequest == null ?
+ profile.GetAudioTranscodingProfile(container, audioCodec) :
+ profile.GetVideoTranscodingProfile(container, audioCodec, videoCodec);
+
+ if (transcodingProfile != null)
+ {
+ //state.EstimateContentLength = transcodingProfile.EstimateContentLength;
+ state.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode;
+ //state.TranscodeSeekInfo = transcodingProfile.TranscodeSeekInfo;
+
+ foreach (var setting in transcodingProfile.Settings)
+ {
+ switch (setting.Name)
+ {
+ case TranscodingSettingType.VideoProfile:
+ {
+ if (state.VideoRequest != null && string.IsNullOrWhiteSpace(state.VideoRequest.VideoProfile))
+ {
+ state.VideoRequest.VideoProfile = setting.Value;
+ }
+ break;
+ }
+ default:
+ throw new ArgumentException("Unrecognized TranscodingSettingType");
+ }
+ }
+ }
+ }
+
+ private EncodingQuality GetQualitySetting()
+ {
+ var quality = _config.Configuration.MediaEncodingQuality;
+
+ if (quality == EncodingQuality.Auto)
+ {
+ var cpuCount = Environment.ProcessorCount;
+
+ if (cpuCount >= 4)
+ {
+ //return EncodingQuality.HighQuality;
+ }
+
+ return EncodingQuality.HighSpeed;
+ }
+
+ return quality;
+ }
+ }
+}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index fac54ecff..93df0c8b9 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -6,10 +6,10 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Concurrent;
-using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -122,35 +122,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <exception cref="System.ArgumentException">Unrecognized InputType</exception>
public string GetInputArgument(string[] inputFiles, InputType type)
{
- string inputPath;
-
- switch (type)
- {
- case InputType.Bluray:
- case InputType.Dvd:
- case InputType.File:
- inputPath = GetConcatInputArgument(inputFiles);
- break;
- case InputType.Url:
- inputPath = GetHttpInputArgument(inputFiles);
- break;
- default:
- throw new ArgumentException("Unrecognized InputType");
- }
-
- return inputPath;
- }
-
- /// <summary>
- /// Gets the HTTP input argument.
- /// </summary>
- /// <param name="inputFiles">The input files.</param>
- /// <returns>System.String.</returns>
- private string GetHttpInputArgument(string[] inputFiles)
- {
- var url = inputFiles[0];
-
- return string.Format("\"{0}\"", url);
+ return EncodingUtils.GetInputArgument(inputFiles.ToList(), type == InputType.Url);
}
/// <summary>
@@ -160,7 +132,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <returns>System.String.</returns>
public string GetProbeSizeArgument(InputType type)
{
- return type == InputType.Dvd ? "-probesize 1G -analyzeduration 200M" : string.Empty;
+ return EncodingUtils.GetProbeSizeArgument(type == InputType.Dvd);
}
/// <summary>
@@ -879,36 +851,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return memoryStream;
}
- /// <summary>
- /// Gets the file input argument.
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns>System.String.</returns>
- private string GetFileInputArgument(string path)
- {
- return string.Format("file:\"{0}\"", path);
- }
-
- /// <summary>
- /// Gets the concat input argument.
- /// </summary>
- /// <param name="playableStreamFiles">The playable stream files.</param>
- /// <returns>System.String.</returns>
- private string GetConcatInputArgument(string[] playableStreamFiles)
- {
- // Get all streams
- // If there's more than one we'll need to use the concat command
- if (playableStreamFiles.Length > 1)
- {
- var files = string.Join("|", playableStreamFiles);
-
- return string.Format("concat:\"{0}\"", files);
- }
-
- // Determine the input path for video files
- return GetFileInputArgument(playableStreamFiles[0]);
- }
-
public Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
{
return new ImageEncoder(FFMpegPath, _logger, _fileSystem, _appPaths).EncodeImage(options, cancellationToken);