mirror of
https://github.com/jellyfin/jellyfin
synced 2024-07-21 09:54:24 +00:00
Merge pull request #613 from MatMaul/mediaencoding-dev
Update MediaEncoding
This commit is contained in:
commit
a1fcf4ea0d
|
@ -9,29 +9,6 @@ namespace MediaBrowser.Api.Playback
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class StreamRequest : BaseEncodingJobOptions
|
public class StreamRequest : BaseEncodingJobOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the id.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The id.</value>
|
|
||||||
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string MediaSourceId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string DeviceId { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "Container", Description = "Container", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
|
||||||
public string Container { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the audio codec.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The audio codec.</value>
|
|
||||||
[ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
|
||||||
public string AudioCodec { get; set; }
|
|
||||||
|
|
||||||
[ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "DeviceProfileId", Description = "Optional. The dlna device profile id to utilize.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
public string DeviceProfileId { get; set; }
|
public string DeviceProfileId { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -92,62 +92,29 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int HlsListSize => 0;
|
|
||||||
|
|
||||||
public string UserAgent { get; set; }
|
public string UserAgent { get; set; }
|
||||||
|
|
||||||
public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger, TranscodingJobType transcodingType)
|
public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger, TranscodingJobType transcodingType)
|
||||||
: base(logger, mediaSourceManager, transcodingType)
|
: base(transcodingType)
|
||||||
{
|
{
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string MimeType { get; set; }
|
|
||||||
|
|
||||||
public bool EstimateContentLength { get; set; }
|
public bool EstimateContentLength { get; set; }
|
||||||
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
||||||
|
|
||||||
public long? EncodingDurationTicks { get; set; }
|
|
||||||
|
|
||||||
public string GetMimeType(string outputPath, bool enableStreamDefault = true)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(MimeType))
|
|
||||||
{
|
|
||||||
return MimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MimeTypes.GetMimeType(outputPath, enableStreamDefault);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool EnableDlnaHeaders { get; set; }
|
public bool EnableDlnaHeaders { get; set; }
|
||||||
|
|
||||||
public void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
DisposeTranscodingThrottler();
|
DisposeTranscodingThrottler();
|
||||||
DisposeLiveStream();
|
|
||||||
DisposeLogStream();
|
DisposeLogStream();
|
||||||
|
DisposeLiveStream();
|
||||||
|
|
||||||
TranscodingJob = null;
|
TranscodingJob = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeLogStream()
|
|
||||||
{
|
|
||||||
if (LogFileStream != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
LogFileStream.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error disposing log stream");
|
|
||||||
}
|
|
||||||
|
|
||||||
LogFileStream = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposeTranscodingThrottler()
|
private void DisposeTranscodingThrottler()
|
||||||
{
|
{
|
||||||
if (TranscodingThrottler != null)
|
if (TranscodingThrottler != null)
|
||||||
|
@ -165,6 +132,23 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
private async void DisposeLiveStream()
|
||||||
{
|
{
|
||||||
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
|
if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
|
||||||
|
@ -180,58 +164,12 @@ namespace MediaBrowser.Api.Playback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string OutputFilePath { get; set; }
|
|
||||||
|
|
||||||
public string ActualOutputVideoCodec
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var codec = OutputVideoCodec;
|
|
||||||
|
|
||||||
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
var stream = VideoStream;
|
|
||||||
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
return stream.Codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return codec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ActualOutputAudioCodec
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var codec = OutputAudioCodec;
|
|
||||||
|
|
||||||
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
var stream = AudioStream;
|
|
||||||
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
return stream.Codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return codec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DeviceProfile DeviceProfile { get; set; }
|
public DeviceProfile DeviceProfile { get; set; }
|
||||||
|
|
||||||
public TranscodingJob TranscodingJob;
|
public TranscodingJob TranscodingJob;
|
||||||
public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
|
public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
|
||||||
{
|
{
|
||||||
ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, 0, percentComplete, 0, bitRate);
|
ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
@ -21,6 +22,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly ISubtitleEncoder _subtitleEncoder;
|
private readonly ISubtitleEncoder _subtitleEncoder;
|
||||||
|
// private readonly IApplicationPaths _appPaths;
|
||||||
|
// private readonly IAssemblyInfo _assemblyInfo;
|
||||||
|
|
||||||
public EncodingHelper(IMediaEncoder mediaEncoder, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder)
|
public EncodingHelper(IMediaEncoder mediaEncoder, IFileSystem fileSystem, ISubtitleEncoder subtitleEncoder)
|
||||||
{
|
{
|
||||||
|
@ -40,56 +43,55 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
{
|
{
|
||||||
var hwType = encodingOptions.HardwareAccelerationType;
|
var hwType = encodingOptions.HardwareAccelerationType;
|
||||||
|
|
||||||
if (!encodingOptions.EnableHardwareEncoding)
|
var codecMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
{
|
{
|
||||||
hwType = null;
|
{"qsv", "h264_qsv"},
|
||||||
}
|
{"h264_qsv", "h264_qsv"},
|
||||||
|
{"nvenc", "h264_nvenc"},
|
||||||
|
{"amf", "h264_amf"},
|
||||||
|
{"omx", "h264_omx"},
|
||||||
|
{"h264_v4l2m2m", "h264_v4l2m2m"},
|
||||||
|
{"mediacodec", "h264_mediacodec"},
|
||||||
|
{"vaapi", "h264_vaapi"}
|
||||||
|
};
|
||||||
|
|
||||||
if (string.Equals(hwType, "qsv", StringComparison.OrdinalIgnoreCase) ||
|
if (!string.IsNullOrEmpty(hwType)
|
||||||
string.Equals(hwType, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
&& encodingOptions.EnableHardwareEncoding && codecMap.ContainsKey(hwType))
|
||||||
{
|
{
|
||||||
return GetAvailableEncoder("h264_qsv", defaultEncoder);
|
if (CheckVaapi(state, hwType, encodingOptions))
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(hwType, "nvenc", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return GetAvailableEncoder("h264_nvenc", defaultEncoder);
|
|
||||||
}
|
|
||||||
if (string.Equals(hwType, "amf", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return GetAvailableEncoder("h264_amf", defaultEncoder);
|
|
||||||
}
|
|
||||||
if (string.Equals(hwType, "omx", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return GetAvailableEncoder("h264_omx", defaultEncoder);
|
|
||||||
}
|
|
||||||
if (string.Equals(hwType, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return GetAvailableEncoder("h264_v4l2m2m", defaultEncoder);
|
|
||||||
}
|
|
||||||
if (string.Equals(hwType, "mediacodec", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return GetAvailableEncoder("h264_mediacodec", defaultEncoder);
|
|
||||||
}
|
|
||||||
if (string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrEmpty(encodingOptions.VaapiDevice))
|
|
||||||
{
|
|
||||||
if (IsVaapiSupported(state))
|
|
||||||
{
|
{
|
||||||
return GetAvailableEncoder("h264_vaapi", defaultEncoder);
|
var preferredEncoder = codecMap[hwType];
|
||||||
|
|
||||||
|
if (_mediaEncoder.SupportsEncoder(preferredEncoder))
|
||||||
|
{
|
||||||
|
return preferredEncoder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid performing a second attempt when the first one
|
||||||
|
// hasn't tried hardware encoding anyway.
|
||||||
|
encodingOptions.EnableHardwareEncoding = false;
|
||||||
return defaultEncoder;
|
return defaultEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetAvailableEncoder(string preferredEncoder, string defaultEncoder)
|
private bool CheckVaapi(EncodingJobInfo state, string hwType, EncodingOptions encodingOptions)
|
||||||
{
|
{
|
||||||
if (_mediaEncoder.SupportsEncoder(preferredEncoder))
|
if (!string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return preferredEncoder;
|
// No vaapi requested, return OK.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return defaultEncoder;
|
|
||||||
|
if (string.IsNullOrEmpty(encodingOptions.VaapiDevice))
|
||||||
|
{
|
||||||
|
// No device specified, return OK.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IsVaapiSupported(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsVaapiSupported(EncodingJobInfo state)
|
private bool IsVaapiSupported(EncodingJobInfo state)
|
||||||
|
@ -340,7 +342,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
|
||||||
public int GetVideoProfileScore(string profile)
|
public int GetVideoProfileScore(string profile)
|
||||||
{
|
{
|
||||||
string[] list =
|
var list = new[]
|
||||||
{
|
{
|
||||||
"ConstrainedBaseline",
|
"ConstrainedBaseline",
|
||||||
"Baseline",
|
"Baseline",
|
||||||
|
@ -538,14 +540,54 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
? string.Empty
|
? string.Empty
|
||||||
: string.Format(",setpts=PTS -{0}/TB", seconds.ToString(_usCulture));
|
: string.Format(",setpts=PTS -{0}/TB", seconds.ToString(_usCulture));
|
||||||
|
|
||||||
string fallbackFontParam = string.Empty;
|
// TODO
|
||||||
|
// var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf");
|
||||||
|
// string fallbackFontParam = string.Empty;
|
||||||
|
|
||||||
|
// if (!_fileSystem.FileExists(fallbackFontPath))
|
||||||
|
// {
|
||||||
|
// _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(fallbackFontPath));
|
||||||
|
// using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), GetType().Namespace + ".DroidSansFallback.ttf"))
|
||||||
|
// {
|
||||||
|
// using (var fileStream = _fileSystem.GetFileStream(fallbackFontPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||||
|
// {
|
||||||
|
// stream.CopyTo(fileStream);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fallbackFontParam = string.Format(":force_style='FontName=Droid Sans Fallback':fontsdir='{0}'", _mediaEncoder.EscapeSubtitleFilterPath(_fileSystem.GetDirectoryName(fallbackFontPath)));
|
||||||
|
|
||||||
|
if (state.SubtitleStream.IsExternal)
|
||||||
|
{
|
||||||
|
var subtitlePath = state.SubtitleStream.Path;
|
||||||
|
|
||||||
|
var charsetParam = string.Empty;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
|
||||||
|
{
|
||||||
|
var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(subtitlePath, state.SubtitleStream.Language, state.MediaSource.Protocol, CancellationToken.None).Result;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(charenc))
|
||||||
|
{
|
||||||
|
charsetParam = ":charenc=" + charenc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Perhaps also use original_size=1920x800 ??
|
||||||
|
return string.Format("subtitles=filename='{0}'{1}{2}{3}",
|
||||||
|
_mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
|
||||||
|
charsetParam,
|
||||||
|
// fallbackFontParam,
|
||||||
|
setPtsParam);
|
||||||
|
}
|
||||||
|
|
||||||
var mediaPath = state.MediaPath ?? string.Empty;
|
var mediaPath = state.MediaPath ?? string.Empty;
|
||||||
|
|
||||||
return string.Format("subtitles='{0}:si={1}'{2}{3}",
|
return string.Format("subtitles='{0}:si={1}'{2}",
|
||||||
_mediaEncoder.EscapeSubtitleFilterPath(mediaPath),
|
_mediaEncoder.EscapeSubtitleFilterPath(mediaPath),
|
||||||
state.InternalSubtitleStreamOffset.ToString(_usCulture),
|
state.InternalSubtitleStreamOffset.ToString(_usCulture),
|
||||||
fallbackFontParam,
|
// fallbackFontParam,
|
||||||
setPtsParam);
|
setPtsParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -718,14 +760,18 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
|
||||||
var request = state.BaseRequest;
|
var request = state.BaseRequest;
|
||||||
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault();
|
var profile = state.GetRequestedProfiles(targetVideoCodec).FirstOrDefault();
|
||||||
if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
|
||||||
|
// vaapi does not support Baseline profile, force Constrained Baseline in this case,
|
||||||
|
// which is compatible (and ugly)
|
||||||
|
if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
profile != null && profile.ToLower().Contains("baseline"))
|
||||||
{
|
{
|
||||||
param += " -profile:v 578";
|
profile = "constrained_baseline";
|
||||||
}
|
}
|
||||||
else if (!string.IsNullOrEmpty(profile))
|
|
||||||
|
if (!string.IsNullOrEmpty(profile))
|
||||||
{
|
{
|
||||||
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
|
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
|
||||||
!string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) &&
|
|
||||||
!string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
|
!string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// not supported by h264_omx
|
// not supported by h264_omx
|
||||||
|
@ -1039,51 +1085,67 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
{
|
{
|
||||||
var bitrate = request.VideoBitRate;
|
var bitrate = request.VideoBitRate;
|
||||||
|
|
||||||
if (videoStream != null)
|
// If specific values were requested, then force the caller to supply a bitrate as well
|
||||||
|
if (request.Height.HasValue && request.Width.HasValue)
|
||||||
{
|
{
|
||||||
var isUpscaling = request.Height.HasValue && videoStream.Height.HasValue &&
|
return bitrate;
|
||||||
request.Height.Value > videoStream.Height.Value && request.Width.HasValue && videoStream.Width.HasValue &&
|
|
||||||
request.Width.Value > videoStream.Width.Value;
|
|
||||||
|
|
||||||
// Don't allow bitrate increases unless upscaling
|
|
||||||
if (!isUpscaling)
|
|
||||||
{
|
|
||||||
if (bitrate.HasValue && videoStream.BitRate.HasValue)
|
|
||||||
{
|
|
||||||
bitrate = GetMinBitrate(videoStream.BitRate.Value, bitrate.Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bitrate.HasValue)
|
if (videoStream != null)
|
||||||
{
|
{
|
||||||
var inputVideoCodec = videoStream == null ? null : videoStream.Codec;
|
if (bitrate.HasValue)
|
||||||
bitrate = ResolutionNormalizer.ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
|
|
||||||
|
|
||||||
// If a max bitrate was requested, don't let the scaled bitrate exceed it
|
|
||||||
if (request.VideoBitRate.HasValue)
|
|
||||||
{
|
{
|
||||||
bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
|
var inputVideoCodec = videoStream.Codec;
|
||||||
|
bitrate = ScaleBitrate(bitrate.Value, inputVideoCodec, outputVideoCodec);
|
||||||
|
|
||||||
|
// If a max bitrate was requested, don't let the scaled bitrate exceed it
|
||||||
|
if (request.VideoBitRate.HasValue)
|
||||||
|
{
|
||||||
|
bitrate = Math.Min(bitrate.Value, request.VideoBitRate.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitrate;
|
return bitrate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int GetMinBitrate(int sourceBitrate, int requestedBitrate)
|
private static double GetVideoBitrateScaleFactor(string codec)
|
||||||
{
|
{
|
||||||
if (sourceBitrate <= 2000000)
|
if (StringHelper.EqualsIgnoreCase(codec, "h265") ||
|
||||||
|
StringHelper.EqualsIgnoreCase(codec, "hevc") ||
|
||||||
|
StringHelper.EqualsIgnoreCase(codec, "vp9"))
|
||||||
{
|
{
|
||||||
sourceBitrate = Convert.ToInt32(sourceBitrate * 2.5);
|
return .5;
|
||||||
}
|
}
|
||||||
else if (sourceBitrate <= 3000000)
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ScaleBitrate(int bitrate, string inputVideoCodec, string outputVideoCodec)
|
||||||
|
{
|
||||||
|
var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
|
||||||
|
var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
|
||||||
|
var scaleFactor = outputScaleFactor / inputScaleFactor;
|
||||||
|
|
||||||
|
if (bitrate <= 500000)
|
||||||
{
|
{
|
||||||
sourceBitrate = Convert.ToInt32(sourceBitrate * 2);
|
scaleFactor = Math.Max(scaleFactor, 4);
|
||||||
|
}
|
||||||
|
else if (bitrate <= 1000000)
|
||||||
|
{
|
||||||
|
scaleFactor = Math.Max(scaleFactor, 3);
|
||||||
|
}
|
||||||
|
else if (bitrate <= 2000000)
|
||||||
|
{
|
||||||
|
scaleFactor = Math.Max(scaleFactor, 2.5);
|
||||||
|
}
|
||||||
|
else if (bitrate <= 3000000)
|
||||||
|
{
|
||||||
|
scaleFactor = Math.Max(scaleFactor, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bitrate = Math.Min(sourceBitrate, requestedBitrate);
|
var newBitrate = scaleFactor * bitrate;
|
||||||
|
|
||||||
return bitrate;
|
return Convert.ToInt32(newBitrate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream)
|
public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream)
|
||||||
|
@ -1405,7 +1467,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
videoSizeParam);
|
videoSizeParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Tuple<int?, int?> GetFixedOutputSize(int? videoWidth,
|
private ValueTuple<int?, int?> GetFixedOutputSize(int? videoWidth,
|
||||||
int? videoHeight,
|
int? videoHeight,
|
||||||
int? requestedWidth,
|
int? requestedWidth,
|
||||||
int? requestedHeight,
|
int? requestedHeight,
|
||||||
|
@ -1414,11 +1476,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
{
|
{
|
||||||
if (!videoWidth.HasValue && !requestedWidth.HasValue)
|
if (!videoWidth.HasValue && !requestedWidth.HasValue)
|
||||||
{
|
{
|
||||||
return new Tuple<int?, int?>(null, null);
|
return new ValueTuple<int?, int?>(null, null);
|
||||||
}
|
}
|
||||||
if (!videoHeight.HasValue && !requestedHeight.HasValue)
|
if (!videoHeight.HasValue && !requestedHeight.HasValue)
|
||||||
{
|
{
|
||||||
return new Tuple<int?, int?>(null, null);
|
return new ValueTuple<int?, int?>(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
decimal inputWidth = Convert.ToDecimal(videoWidth ?? requestedWidth);
|
decimal inputWidth = Convert.ToDecimal(videoWidth ?? requestedWidth);
|
||||||
|
@ -1438,7 +1500,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
outputWidth = 2 * Math.Truncate(outputWidth / 2);
|
outputWidth = 2 * Math.Truncate(outputWidth / 2);
|
||||||
outputHeight = 2 * Math.Truncate(outputHeight / 2);
|
outputHeight = 2 * Math.Truncate(outputHeight / 2);
|
||||||
|
|
||||||
return new Tuple<int?, int?>(Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight));
|
return new ValueTuple<int?, int?>(Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<string> GetScalingFilters(int? videoWidth,
|
public List<string> GetScalingFilters(int? videoWidth,
|
||||||
|
@ -1669,7 +1731,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
var inputHeight = videoStream == null ? null : videoStream.Height;
|
var inputHeight = videoStream == null ? null : videoStream.Height;
|
||||||
var threeDFormat = state.MediaSource.Video3DFormat;
|
var threeDFormat = state.MediaSource.Video3DFormat;
|
||||||
|
|
||||||
var videoDecoder = GetVideoDecoder(state, options);
|
var videoDecoder = this.GetHardwareAcceleratedVideoDecoder(state, options);
|
||||||
|
|
||||||
filters.AddRange(GetScalingFilters(inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
|
filters.AddRange(GetScalingFilters(inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
|
||||||
|
|
||||||
|
@ -1851,7 +1913,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
inputModifier += " -fflags " + string.Join("", flags.ToArray());
|
inputModifier += " -fflags " + string.Join("", flags.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoDecoder = GetVideoDecoder(state, encodingOptions);
|
var videoDecoder = this.GetHardwareAcceleratedVideoDecoder(state, encodingOptions);
|
||||||
if (!string.IsNullOrEmpty(videoDecoder))
|
if (!string.IsNullOrEmpty(videoDecoder))
|
||||||
{
|
{
|
||||||
inputModifier += " " + videoDecoder;
|
inputModifier += " " + videoDecoder;
|
||||||
|
@ -1893,7 +1955,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
|
||||||
if (state.MediaSource.RequiresLooping)
|
if (state.MediaSource.RequiresLooping)
|
||||||
{
|
{
|
||||||
inputModifier += " -stream_loop -1";
|
inputModifier += " -stream_loop -1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2";
|
||||||
}
|
}
|
||||||
|
|
||||||
return inputModifier;
|
return inputModifier;
|
||||||
|
@ -1989,7 +2051,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(requestedUrl))
|
if (string.IsNullOrEmpty(requestedUrl))
|
||||||
{
|
{
|
||||||
requestedUrl = "test." + videoRequest.OutputContainer;
|
requestedUrl = "test." + videoRequest.Container;
|
||||||
}
|
}
|
||||||
|
|
||||||
videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
|
videoRequest.VideoCodec = InferVideoCodec(requestedUrl);
|
||||||
|
@ -2015,6 +2077,49 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
|
|
||||||
state.MediaSource = mediaSource;
|
state.MediaSource = mediaSource;
|
||||||
|
|
||||||
|
var request = state.BaseRequest;
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
|
||||||
|
{
|
||||||
|
var supportedAudioCodecsList = request.AudioCodec.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||||
|
|
||||||
|
ShiftAudioCodecsIfNeeded(supportedAudioCodecsList, state.AudioStream);
|
||||||
|
|
||||||
|
state.SupportedAudioCodecs = supportedAudioCodecsList.ToArray();
|
||||||
|
|
||||||
|
request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => _mediaEncoder.CanEncodeToAudioCodec(i))
|
||||||
|
?? state.SupportedAudioCodecs.FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShiftAudioCodecsIfNeeded(List<string> audioCodecs, MediaStream audioStream)
|
||||||
|
{
|
||||||
|
// Nothing to do here
|
||||||
|
if (audioCodecs.Count < 2)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputChannels = audioStream == null ? 6 : audioStream.Channels ?? 6;
|
||||||
|
if (inputChannels >= 6)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transcoding to 2ch ac3 almost always causes a playback failure
|
||||||
|
// Keep it in the supported codecs list, but shift it to the end of the list so that if transcoding happens, another codec is used
|
||||||
|
var shiftAudioCodecs = new[] { "ac3", "eac3" };
|
||||||
|
if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparer.OrdinalIgnoreCase)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (shiftAudioCodecs.Contains(audioCodecs[0], StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var removed = shiftAudioCodecs[0];
|
||||||
|
audioCodecs.RemoveAt(0);
|
||||||
|
audioCodecs.Add(removed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NormalizeSubtitleEmbed(EncodingJobInfo state)
|
private void NormalizeSubtitleEmbed(EncodingJobInfo state)
|
||||||
|
@ -2035,17 +2140,17 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the output video codec
|
/// Gets the name of the output video codec
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected string GetVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions)
|
protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions)
|
||||||
{
|
{
|
||||||
if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetVideoDecoder(state.MediaSource.VideoType ?? VideoType.VideoFile, state.VideoStream, encodingOptions);
|
return this.GetHardwareAcceleratedVideoDecoder(state.MediaSource.VideoType ?? VideoType.VideoFile, state.VideoStream, encodingOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetVideoDecoder(VideoType videoType, MediaStream videoStream, EncodingOptions encodingOptions)
|
public string GetHardwareAcceleratedVideoDecoder(VideoType videoType, MediaStream videoStream, EncodingOptions encodingOptions)
|
||||||
{
|
{
|
||||||
// Only use alternative encoders for video files.
|
// 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
|
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
|
||||||
|
@ -2070,6 +2175,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
// qsv decoder does not support 10-bit input
|
// qsv decoder does not support 10-bit input
|
||||||
if ((videoStream.BitDepth ?? 8) > 8)
|
if ((videoStream.BitDepth ?? 8) > 8)
|
||||||
{
|
{
|
||||||
|
encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return "-c:v h264_qsv ";
|
return "-c:v h264_qsv ";
|
||||||
|
@ -2204,6 +2310,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
|
||||||
else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
||||||
|
{
|
||||||
|
return "-hwaccel dxva2";
|
||||||
|
}
|
||||||
|
|
||||||
switch (videoStream.Codec.ToLower())
|
switch (videoStream.Codec.ToLower())
|
||||||
{
|
{
|
||||||
case "avc":
|
case "avc":
|
||||||
|
@ -2223,6 +2334,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Avoid a second attempt if no hardware acceleration is being used
|
||||||
|
encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
|
||||||
|
|
||||||
// leave blank so ffmpeg will decide
|
// leave blank so ffmpeg will decide
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
|
@ -12,13 +11,17 @@ using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.IO;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.MediaEncoding
|
namespace MediaBrowser.Controller.MediaEncoding
|
||||||
{
|
{
|
||||||
// For now, a common base class until the API and MediaEncoding classes are unified
|
// For now, a common base class until the API and MediaEncoding classes are unified
|
||||||
public abstract class EncodingJobInfo
|
public class EncodingJobInfo
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
protected readonly IMediaSourceManager MediaSourceManager;
|
||||||
|
|
||||||
public MediaStream VideoStream { get; set; }
|
public MediaStream VideoStream { get; set; }
|
||||||
public VideoType VideoType { get; set; }
|
public VideoType VideoType { get; set; }
|
||||||
|
@ -43,6 +46,21 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
|
||||||
public bool ReadInputAtNativeFramerate { get; set; }
|
public bool ReadInputAtNativeFramerate { get; set; }
|
||||||
|
|
||||||
|
public string OutputFilePath { get; set; }
|
||||||
|
|
||||||
|
public string MimeType { get; set; }
|
||||||
|
public long? EncodingDurationTicks { get; set; }
|
||||||
|
|
||||||
|
public string GetMimeType(string outputPath, bool enableStreamDefault = true)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(MimeType))
|
||||||
|
{
|
||||||
|
return MimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MimeTypes.GetMimeType(outputPath, enableStreamDefault);
|
||||||
|
}
|
||||||
|
|
||||||
private TranscodeReason[] _transcodeReasons = null;
|
private TranscodeReason[] _transcodeReasons = null;
|
||||||
public TranscodeReason[] TranscodeReasons
|
public TranscodeReason[] TranscodeReasons
|
||||||
{
|
{
|
||||||
|
@ -266,9 +284,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
public bool IsVideoRequest { get; set; }
|
public bool IsVideoRequest { get; set; }
|
||||||
public TranscodingJobType TranscodingType { get; set; }
|
public TranscodingJobType TranscodingType { get; set; }
|
||||||
|
|
||||||
public EncodingJobInfo(ILogger logger, IMediaSourceManager unused, TranscodingJobType jobType)
|
public EncodingJobInfo(TranscodingJobType jobType)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
|
||||||
TranscodingType = jobType;
|
TranscodingType = jobType;
|
||||||
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
PlayableStreamFileNames = Array.Empty<string>();
|
PlayableStreamFileNames = Array.Empty<string>();
|
||||||
|
@ -602,6 +619,28 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ActualOutputAudioCodec
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var codec = OutputAudioCodec;
|
||||||
|
|
||||||
|
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var stream = AudioStream;
|
||||||
|
|
||||||
|
if (stream != null)
|
||||||
|
{
|
||||||
|
return stream.Codec;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return codec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool? IsTargetInterlaced
|
public bool? IsTargetInterlaced
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -657,6 +696,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int HlsListSize
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private int? GetMediaStreamCount(MediaStreamType type, int limit)
|
private int? GetMediaStreamCount(MediaStreamType type, int limit)
|
||||||
{
|
{
|
||||||
var count = MediaSource.GetStreamCount(type);
|
var count = MediaSource.GetStreamCount(type);
|
||||||
|
@ -668,22 +715,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
protected void DisposeIsoMount()
|
|
||||||
{
|
|
||||||
if (IsoMount != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IsoMount.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error disposing iso mount");
|
|
||||||
}
|
|
||||||
|
|
||||||
IsoMount = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IProgress<double> Progress { get; set; }
|
public IProgress<double> Progress { get; set; }
|
||||||
public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
|
public virtual void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
|
||||||
|
|
|
@ -11,9 +11,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
{
|
{
|
||||||
public string OutputDirectory { get; set; }
|
public string OutputDirectory { get; set; }
|
||||||
public string ItemId { get; set; }
|
public string ItemId { get; set; }
|
||||||
public string MediaSourceId { get; set; }
|
|
||||||
public string AudioCodec { get; set; }
|
|
||||||
|
|
||||||
|
public string TempDirectory { get; set; }
|
||||||
public bool ReadInputAtNativeFramerate { get; set; }
|
public bool ReadInputAtNativeFramerate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -22,15 +21,16 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
/// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
|
||||||
public bool HasFixedResolution => Width.HasValue || Height.HasValue;
|
public bool HasFixedResolution => Width.HasValue || Height.HasValue;
|
||||||
|
|
||||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
public DeviceProfile DeviceProfile { get; set; }
|
||||||
|
|
||||||
public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile)
|
public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile)
|
||||||
{
|
{
|
||||||
OutputContainer = info.Container;
|
Container = info.Container;
|
||||||
StartTimeTicks = info.StartPositionTicks;
|
StartTimeTicks = info.StartPositionTicks;
|
||||||
MaxWidth = info.MaxWidth;
|
MaxWidth = info.MaxWidth;
|
||||||
MaxHeight = info.MaxHeight;
|
MaxHeight = info.MaxHeight;
|
||||||
MaxFramerate = info.MaxFramerate;
|
MaxFramerate = info.MaxFramerate;
|
||||||
ItemId = info.ItemId.ToString("");
|
Id = info.ItemId;
|
||||||
MediaSourceId = info.MediaSourceId;
|
MediaSourceId = info.MediaSourceId;
|
||||||
AudioCodec = info.TargetAudioCodec.FirstOrDefault();
|
AudioCodec = info.TargetAudioCodec.FirstOrDefault();
|
||||||
MaxAudioChannels = info.GlobalMaxAudioChannels;
|
MaxAudioChannels = info.GlobalMaxAudioChannels;
|
||||||
|
@ -55,6 +55,29 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
// For now until api and media encoding layers are unified
|
// For now until api and media encoding layers are unified
|
||||||
public class BaseEncodingJobOptions
|
public class BaseEncodingJobOptions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the id.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The id.</value>
|
||||||
|
[ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "MediaSourceId", Description = "The media version id, if playing an alternate version", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||||
|
public string MediaSourceId { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "DeviceId", Description = "The device id of the client requesting. Used to stop encoding processes when needed.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public string DeviceId { get; set; }
|
||||||
|
|
||||||
|
[ApiMember(Name = "Container", Description = "Container", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public string Container { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the audio codec.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The audio codec.</value>
|
||||||
|
[ApiMember(Name = "AudioCodec", Description = "Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||||
|
public string AudioCodec { get; set; }
|
||||||
|
|
||||||
[ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
[ApiMember(Name = "EnableAutoStreamCopy", Description = "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
|
||||||
public bool EnableAutoStreamCopy { get; set; }
|
public bool EnableAutoStreamCopy { get; set; }
|
||||||
|
|
||||||
|
@ -180,7 +203,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
public bool RequireNonAnamorphic { get; set; }
|
public bool RequireNonAnamorphic { get; set; }
|
||||||
public int? TranscodingMaxAudioChannels { get; set; }
|
public int? TranscodingMaxAudioChannels { get; set; }
|
||||||
public int? CpuCoreLimit { get; set; }
|
public int? CpuCoreLimit { get; set; }
|
||||||
public string OutputContainer { get; set; }
|
|
||||||
public string LiveStreamId { get; set; }
|
public string LiveStreamId { get; set; }
|
||||||
|
|
||||||
public bool EnableMpegtsM2TsMode { get; set; }
|
public bool EnableMpegtsM2TsMode { get; set; }
|
||||||
|
@ -244,11 +267,5 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||||
Context = EncodingContext.Streaming;
|
Context = EncodingContext.Streaming;
|
||||||
StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
StreamOptions = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string TempDirectory { get; set; }
|
|
||||||
public string DeviceId { get; set; }
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
public string Container { get; set; }
|
|
||||||
public DeviceProfile DeviceProfile { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
public bool IsCancelled { get; internal set; }
|
public bool IsCancelled { get; internal set; }
|
||||||
|
|
||||||
public Stream LogFileStream { get; set; }
|
public Stream LogFileStream { get; set; }
|
||||||
public IProgress<double> Progress { get; set; }
|
|
||||||
public TaskCompletionSource<bool> TaskCompletionSource;
|
public TaskCompletionSource<bool> TaskCompletionSource;
|
||||||
|
|
||||||
public EncodingJobOptions Options
|
public EncodingJobOptions Options
|
||||||
|
@ -24,34 +23,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
set => BaseRequest = value;
|
set => BaseRequest = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
public string MimeType { get; set; }
|
|
||||||
public bool EstimateContentLength { get; set; }
|
public bool EstimateContentLength { get; set; }
|
||||||
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
||||||
public long? EncodingDurationTicks { get; set; }
|
|
||||||
|
|
||||||
public string ItemType { get; set; }
|
public string ItemType { get; set; }
|
||||||
|
|
||||||
public string GetMimeType(string outputPath)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(MimeType))
|
|
||||||
{
|
|
||||||
return MimeType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return MimeTypes.GetMimeType(outputPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
|
|
||||||
public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) :
|
public EncodingJob(ILogger logger, IMediaSourceManager mediaSourceManager) :
|
||||||
base(logger, mediaSourceManager, TranscodingJobType.Progressive)
|
base(TranscodingJobType.Progressive)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
Id = Guid.NewGuid().ToString("N");
|
Id = Guid.NewGuid();
|
||||||
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
TaskCompletionSource = new TaskCompletionSource<bool>();
|
TaskCompletionSource = new TaskCompletionSource<bool>();
|
||||||
|
@ -61,6 +48,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
{
|
{
|
||||||
DisposeLiveStream();
|
DisposeLiveStream();
|
||||||
DisposeLogStream();
|
DisposeLogStream();
|
||||||
|
DisposeIsoMount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeLogStream()
|
private void DisposeLogStream()
|
||||||
|
@ -95,49 +83,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string OutputFilePath { get; set; }
|
|
||||||
|
|
||||||
public string ActualOutputVideoCodec
|
private void DisposeIsoMount()
|
||||||
{
|
{
|
||||||
get
|
if (IsoMount != null)
|
||||||
{
|
{
|
||||||
var codec = OutputVideoCodec;
|
try
|
||||||
|
|
||||||
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
var stream = VideoStream;
|
IsoMount.Dispose();
|
||||||
|
}
|
||||||
if (stream != null)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return stream.Codec;
|
_logger.LogError("Error disposing iso mount", ex);
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return codec;
|
IsoMount = null;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ActualOutputAudioCodec
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var codec = OutputAudioCodec;
|
|
||||||
|
|
||||||
if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
var stream = AudioStream;
|
|
||||||
|
|
||||||
if (stream != null)
|
|
||||||
{
|
|
||||||
return stream.Codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return codec;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue