mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-15 23:58:57 +00:00
channel fixes
This commit is contained in:
@@ -1,91 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,77 +64,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
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;
|
||||
@@ -157,19 +86,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
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>
|
||||
@@ -201,35 +117,5 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
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, 2);
|
||||
}
|
||||
|
||||
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 ? Math.Max(Environment.ProcessorCount - 1, 2) : 0;
|
||||
default:
|
||||
throw new Exception("Unrecognized MediaEncodingQuality value.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
public class ImageEncoder
|
||||
{
|
||||
private readonly string _ffmpegPath;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
private static readonly SemaphoreSlim ResourcePool = new SemaphoreSlim(10, 10);
|
||||
|
||||
public ImageEncoder(string ffmpegPath, ILogger logger, IFileSystem fileSystem, IApplicationPaths appPaths)
|
||||
{
|
||||
_ffmpegPath = ffmpegPath;
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_appPaths = appPaths;
|
||||
}
|
||||
|
||||
public async Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
ValidateInput(options);
|
||||
|
||||
await ResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
return await EncodeImageInternal(options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ResourcePool.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Stream> EncodeImageInternal(ImageEncodingOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
ValidateInput(options);
|
||||
|
||||
var inputPath = options.InputPath;
|
||||
var filename = Path.GetFileName(inputPath);
|
||||
|
||||
if (HasDiacritics(filename))
|
||||
{
|
||||
inputPath = GetTempFile(inputPath);
|
||||
filename = Path.GetFileName(inputPath);
|
||||
}
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = _ffmpegPath,
|
||||
Arguments = GetArguments(options, filename),
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
WorkingDirectory = Path.GetDirectoryName(inputPath)
|
||||
}
|
||||
};
|
||||
|
||||
_logger.Debug("ffmpeg " + process.StartInfo.Arguments);
|
||||
|
||||
process.Start();
|
||||
|
||||
var memoryStream = new MemoryStream();
|
||||
|
||||
#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.StandardOutput.BaseStream.CopyToAsync(memoryStream);
|
||||
#pragma warning restore 4014
|
||||
|
||||
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
var ranToCompletion = process.WaitForExit(5000);
|
||||
|
||||
if (!ranToCompletion)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.Info("Killing ffmpeg process");
|
||||
|
||||
process.Kill();
|
||||
|
||||
process.WaitForExit(1000);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error killing process", ex);
|
||||
}
|
||||
}
|
||||
|
||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||
|
||||
process.Dispose();
|
||||
|
||||
if (exitCode == -1 || memoryStream.Length == 0)
|
||||
{
|
||||
memoryStream.Dispose();
|
||||
|
||||
var msg = string.Format("ffmpeg image encoding failed for {0}", options.InputPath);
|
||||
|
||||
_logger.Error(msg);
|
||||
|
||||
throw new ApplicationException(msg);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
return memoryStream;
|
||||
}
|
||||
|
||||
private string GetTempFile(string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path) ?? string.Empty;
|
||||
|
||||
var tempPath = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N") + extension);
|
||||
|
||||
File.Copy(path, tempPath);
|
||||
|
||||
return tempPath;
|
||||
}
|
||||
|
||||
private string GetArguments(ImageEncodingOptions options, string inputFilename)
|
||||
{
|
||||
var vfScale = GetFilterGraph(options);
|
||||
var outputFormat = GetOutputFormat(options.Format);
|
||||
|
||||
var quality = (options.Quality ?? 100) * .3;
|
||||
quality = 31 - quality;
|
||||
var qualityValue = Convert.ToInt32(Math.Max(quality, 1));
|
||||
|
||||
return string.Format("-f image2 -i file:\"{3}\" -q:v {0} {1} -f image2pipe -vcodec {2} -",
|
||||
qualityValue.ToString(_usCulture),
|
||||
vfScale,
|
||||
outputFormat,
|
||||
inputFilename);
|
||||
}
|
||||
|
||||
private string GetFilterGraph(ImageEncodingOptions options)
|
||||
{
|
||||
if (!options.Width.HasValue &&
|
||||
!options.Height.HasValue &&
|
||||
!options.MaxHeight.HasValue &&
|
||||
!options.MaxWidth.HasValue)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var widthScale = "-1";
|
||||
var heightScale = "-1";
|
||||
|
||||
if (options.MaxWidth.HasValue)
|
||||
{
|
||||
widthScale = "min(iw\\," + options.MaxWidth.Value.ToString(_usCulture) + ")";
|
||||
}
|
||||
else if (options.Width.HasValue)
|
||||
{
|
||||
widthScale = options.Width.Value.ToString(_usCulture);
|
||||
}
|
||||
|
||||
if (options.MaxHeight.HasValue)
|
||||
{
|
||||
heightScale = "min(ih\\," + options.MaxHeight.Value.ToString(_usCulture) + ")";
|
||||
}
|
||||
else if (options.Height.HasValue)
|
||||
{
|
||||
heightScale = options.Height.Value.ToString(_usCulture);
|
||||
}
|
||||
|
||||
var scaleMethod = "lanczos";
|
||||
|
||||
return string.Format("-vf scale=\"{0}:{1}\"",
|
||||
widthScale,
|
||||
heightScale);
|
||||
}
|
||||
|
||||
private string GetOutputFormat(string format)
|
||||
{
|
||||
if (string.Equals(format, "jpeg", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(format, "jpg", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "mjpeg";
|
||||
}
|
||||
return format;
|
||||
}
|
||||
|
||||
private void ValidateInput(ImageEncodingOptions options)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified text has diacritics.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <returns><c>true</c> if the specified text has diacritics; otherwise, <c>false</c>.</returns>
|
||||
private bool HasDiacritics(string text)
|
||||
{
|
||||
return !String.Equals(text, RemoveDiacritics(text), StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the diacritics.
|
||||
/// </summary>
|
||||
/// <param name="text">The text.</param>
|
||||
/// <returns>System.String.</returns>
|
||||
private string RemoveDiacritics(string text)
|
||||
{
|
||||
return String.Concat(
|
||||
text.Normalize(NormalizationForm.FormD)
|
||||
.Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) !=
|
||||
UnicodeCategory.NonSpacingMark)
|
||||
).Normalize(NormalizationForm.FormC);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
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) :
|
||||
// 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;
|
||||
|
||||
// if (state.VideoRequest != null && string.IsNullOrWhiteSpace(state.VideoRequest.VideoProfile))
|
||||
// {
|
||||
// state.VideoRequest.VideoProfile = transcodingProfile.VideoProfile;
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -853,7 +853,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
public Task<Stream> EncodeImage(ImageEncodingOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
return new ImageEncoder(FFMpegPath, _logger, _fileSystem, _appPaths).EncodeImage(options, cancellationToken);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -53,12 +53,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="BdInfo\BdInfoExaminer.cs" />
|
||||
<Compile Include="Encoder\AudioEncoder.cs" />
|
||||
<Compile Include="Encoder\EncodingUtils.cs" />
|
||||
<Compile Include="Encoder\FFMpegProcess.cs" />
|
||||
<Compile Include="Encoder\ImageEncoder.cs" />
|
||||
<Compile Include="Encoder\InternalEncodingTask.cs" />
|
||||
<Compile Include="Encoder\InternalEncodingTaskFactory.cs" />
|
||||
<Compile Include="Encoder\MediaEncoder.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Subtitles\ISubtitleParser.cs" />
|
||||
|
||||
Reference in New Issue
Block a user