Make probesize and analyzeduration configurable and simplify circular

dependencies

Makes the probesize and analyzeduration configurable with env args.
(`JELLYFIN_FFmpeg_probesize` and `FFmpeg_analyzeduration`)
This commit is contained in:
Bond_009
2019-10-26 22:53:53 +02:00
committed by Bond-009
parent e7098f1997
commit cc5acf37f7
28 changed files with 395 additions and 319 deletions

View File

@@ -3,13 +3,13 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Configuration;
@@ -19,9 +19,9 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.System;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
namespace MediaBrowser.MediaEncoding.Encoder
{
@@ -31,55 +31,60 @@ namespace MediaBrowser.MediaEncoding.Encoder
public class MediaEncoder : IMediaEncoder, IDisposable
{
/// <summary>
/// Gets the encoder path.
/// The default image extraction timeout in milliseconds.
/// </summary>
/// <value>The encoder path.</value>
public string EncoderPath => FFmpegPath;
/// <summary>
/// The location of the discovered FFmpeg tool.
/// </summary>
public FFmpegLocation EncoderLocation { get; private set; }
internal const int DefaultImageExtractionTimeout = 5000;
private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
private string FFmpegPath;
private string FFprobePath;
protected readonly IServerConfigurationManager ConfigurationManager;
protected readonly IFileSystem FileSystem;
protected readonly Func<ISubtitleEncoder> SubtitleEncoder;
protected readonly Func<IMediaSourceManager> MediaSourceManager;
private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem;
private readonly IProcessFactory _processFactory;
private readonly int DefaultImageExtractionTimeoutMs;
private readonly string StartupOptionFFmpegPath;
private readonly ILocalizationManager _localization;
private readonly Func<ISubtitleEncoder> _subtitleEncoder;
private readonly IConfiguration _configuration;
private readonly string _startupOptionFFmpegPath;
private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
private readonly object _runningProcessesLock = new object();
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
private readonly ILocalizationManager _localization;
private EncodingHelper _encodingHelper;
private string _ffmpegPath;
private string _ffprobePath;
public MediaEncoder(
ILoggerFactory loggerFactory,
IJsonSerializer jsonSerializer,
string startupOptionsFFmpegPath,
ILogger<MediaEncoder> logger,
IServerConfigurationManager configurationManager,
IFileSystem fileSystem,
Func<ISubtitleEncoder> subtitleEncoder,
Func<IMediaSourceManager> mediaSourceManager,
IProcessFactory processFactory,
int defaultImageExtractionTimeoutMs,
ILocalizationManager localization)
ILocalizationManager localization,
Func<ISubtitleEncoder> subtitleEncoder,
IConfiguration configuration,
string startupOptionsFFmpegPath)
{
_logger = loggerFactory.CreateLogger(nameof(MediaEncoder));
_jsonSerializer = jsonSerializer;
StartupOptionFFmpegPath = startupOptionsFFmpegPath;
ConfigurationManager = configurationManager;
FileSystem = fileSystem;
SubtitleEncoder = subtitleEncoder;
_logger = logger;
_configurationManager = configurationManager;
_fileSystem = fileSystem;
_processFactory = processFactory;
DefaultImageExtractionTimeoutMs = defaultImageExtractionTimeoutMs;
_localization = localization;
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
_subtitleEncoder = subtitleEncoder;
_configuration = configuration;
}
private EncodingHelper EncodingHelper
=> LazyInitializer.EnsureInitialized(
ref _encodingHelper,
() => new EncodingHelper(this, _fileSystem, _subtitleEncoder(), _configuration));
/// <inheritdoc />
public string EncoderPath => _ffmpegPath;
/// <inheritdoc />
public FFmpegLocation EncoderLocation { get; private set; }
/// <summary>
/// Run at startup or if the user removes a Custom path from transcode page.
/// Sets global variables FFmpegPath.
@@ -88,39 +93,39 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetFFmpegPath()
{
// 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
if (!ValidatePath(ConfigurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom))
if (!ValidatePath(_configurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom))
{
// 2) Check if the --ffmpeg CLI switch has been given
if (!ValidatePath(StartupOptionFFmpegPath, FFmpegLocation.SetByArgument))
if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument))
{
// 3) Search system $PATH environment variable for valid FFmpeg
if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System))
{
EncoderLocation = FFmpegLocation.NotFound;
FFmpegPath = null;
_ffmpegPath = null;
}
}
}
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
config.EncoderAppPathDisplay = FFmpegPath ?? string.Empty;
ConfigurationManager.SaveConfiguration("encoding", config);
var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding");
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
_configurationManager.SaveConfiguration("encoding", config);
// Only if mpeg path is set, try and set path to probe
if (FFmpegPath != null)
if (_ffmpegPath != null)
{
// Determine a probe path from the mpeg path
FFprobePath = Regex.Replace(FFmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1");
_ffprobePath = Regex.Replace(_ffmpegPath, @"[^\/\\]+?(\.[^\/\\\n.]+)?$", @"ffprobe$1");
// Interrogate to understand what coders are supported
var validator = new EncoderValidator(_logger, FFmpegPath);
var validator = new EncoderValidator(_logger, _ffmpegPath);
SetAvailableDecoders(validator.GetDecoders());
SetAvailableEncoders(validator.GetEncoders());
}
_logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, FFmpegPath ?? string.Empty);
_logger.LogInformation("FFmpeg: {0}: {1}", EncoderLocation, _ffmpegPath ?? string.Empty);
}
/// <summary>
@@ -160,9 +165,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Write the new ffmpeg path to the xml as <EncoderAppPath>
// This ensures its not lost on next startup
var config = ConfigurationManager.GetConfiguration<EncodingOptions>("encoding");
var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding");
config.EncoderAppPath = newPath;
ConfigurationManager.SaveConfiguration("encoding", config);
_configurationManager.SaveConfiguration("encoding", config);
// Trigger SetFFmpegPath so we validate the new path and setup probe path
SetFFmpegPath();
@@ -193,7 +198,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// ToDo - Enable the ffmpeg validator. At the moment any version can be used.
rc = true;
FFmpegPath = path;
_ffmpegPath = path;
EncoderLocation = location;
}
else
@@ -209,7 +214,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
try
{
var files = FileSystem.GetFilePaths(path);
var files = _fileSystem.GetFilePaths(path);
var excludeExtensions = new[] { ".c" };
@@ -304,7 +309,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
var extractChapters = request.MediaType == DlnaProfileType.Video && request.ExtractChapters;
var inputFiles = MediaEncoderHelpers.GetInputArgument(FileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames);
var inputFiles = MediaEncoderHelpers.GetInputArgument(_fileSystem, request.MediaSource.Path, request.MountedIso, request.PlayableStreamFileNames);
var probeSize = EncodingHelper.GetProbeSizeArgument(inputFiles.Length);
string analyzeDuration;
@@ -365,7 +370,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
RedirectStandardOutput = true,
FileName = FFprobePath,
FileName = _ffprobePath,
Arguments = args,
@@ -383,7 +388,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
}
using (var processWrapper = new ProcessWrapper(process, this, _logger))
using (var processWrapper = new ProcessWrapper(process, this))
{
_logger.LogDebug("Starting ffprobe with args {Args}", args);
StartProcess(processWrapper);
@@ -391,7 +396,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
InternalMediaInfoResult result;
try
{
result = await _jsonSerializer.DeserializeFromStreamAsync<InternalMediaInfoResult>(
result = await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(
process.StandardOutput.BaseStream).ConfigureAwait(false);
}
catch
@@ -423,7 +428,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
return new ProbeResultNormalizer(_logger, FileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
return new ProbeResultNormalizer(_logger, _fileSystem, _localization).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
}
}
@@ -486,7 +491,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
throw new ArgumentNullException(nameof(inputPath));
}
var tempExtractPath = Path.Combine(ConfigurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
// apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar then scale to width 600.
@@ -545,7 +550,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
args = string.Format("-ss {0} ", GetTimeParameter(offset.Value)) + args;
}
var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder());
if (videoStream != null)
{
/* fix
@@ -559,7 +563,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrWhiteSpace(container))
{
var inputFormat = encodinghelper.GetInputFormat(container);
var inputFormat = EncodingHelper.GetInputFormat(container);
if (!string.IsNullOrWhiteSpace(inputFormat))
{
args = "-f " + inputFormat + " " + args;
@@ -570,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = FFmpegPath,
FileName = _ffmpegPath,
Arguments = args,
IsHidden = true,
ErrorDialog = false,
@@ -579,7 +583,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
using (var processWrapper = new ProcessWrapper(process, this, _logger))
using (var processWrapper = new ProcessWrapper(process, this))
{
bool ranToCompletion;
@@ -588,10 +592,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
StartProcess(processWrapper);
var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs;
var timeoutMs = _configurationManager.Configuration.ImageExtractionTimeoutMs;
if (timeoutMs <= 0)
{
timeoutMs = DefaultImageExtractionTimeoutMs;
timeoutMs = DefaultImageExtractionTimeout;
}
ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
@@ -607,7 +611,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
var file = FileSystem.GetFileInfo(tempExtractPath);
var file = _fileSystem.GetFileInfo(tempExtractPath);
if (exitCode == -1 || !file.Exists || file.Length == 0)
{
@@ -675,7 +679,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
args = analyzeDurationArgument + " " + args;
}
var encodinghelper = new EncodingHelper(this, FileSystem, SubtitleEncoder());
if (videoStream != null)
{
/* fix
@@ -689,7 +692,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrWhiteSpace(container))
{
var inputFormat = encodinghelper.GetInputFormat(container);
var inputFormat = EncodingHelper.GetInputFormat(container);
if (!string.IsNullOrWhiteSpace(inputFormat))
{
args = "-f " + inputFormat + " " + args;
@@ -700,7 +703,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = FFmpegPath,
FileName = _ffmpegPath,
Arguments = args,
IsHidden = true,
ErrorDialog = false,
@@ -713,7 +716,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
bool ranToCompletion = false;
using (var processWrapper = new ProcessWrapper(process, this, _logger))
using (var processWrapper = new ProcessWrapper(process, this))
{
try
{
@@ -736,10 +739,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
cancellationToken.ThrowIfCancellationRequested();
var jpegCount = FileSystem.GetFilePaths(targetDirectory)
var jpegCount = _fileSystem.GetFilePaths(targetDirectory)
.Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
isResponsive = (jpegCount > lastCount);
isResponsive = jpegCount > lastCount;
lastCount = jpegCount;
}
@@ -770,7 +773,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
process.Process.Start();
lock (_runningProcesses)
lock (_runningProcessesLock)
{
_runningProcesses.Add(process);
}
@@ -804,7 +807,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private void StopProcesses()
{
List<ProcessWrapper> proceses;
lock (_runningProcesses)
lock (_runningProcessesLock)
{
proceses = _runningProcesses.ToList();
_runningProcesses.Clear();
@@ -827,12 +830,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
return path.Replace('\\', '/').Replace(":", "\\:").Replace("'", "'\\\\\\''");
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
@@ -852,11 +854,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
throw new NotImplementedException();
}
public string[] GetPlayableStreamFileNames(string path, VideoType videoType)
{
throw new NotImplementedException();
}
public IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, IIsoMount isoMount, uint? titleNumber)
{
throw new NotImplementedException();
@@ -870,21 +867,24 @@ namespace MediaBrowser.MediaEncoding.Encoder
private class ProcessWrapper : IDisposable
{
public readonly IProcess Process;
public bool HasExited;
public int? ExitCode;
private readonly MediaEncoder _mediaEncoder;
private readonly ILogger _logger;
public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder, ILogger logger)
private bool _disposed = false;
public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder)
{
Process = process;
_mediaEncoder = mediaEncoder;
_logger = logger;
Process.Exited += Process_Exited;
Process.Exited += OnProcessExited;
}
void Process_Exited(object sender, EventArgs e)
public IProcess Process { get; }
public bool HasExited { get; private set; }
public int? ExitCode { get; private set; }
void OnProcessExited(object sender, EventArgs e)
{
var process = (IProcess)sender;
@@ -903,7 +903,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private void DisposeProcess(IProcess process)
{
lock (_mediaEncoder._runningProcesses)
lock (_mediaEncoder._runningProcessesLock)
{
_mediaEncoder._runningProcesses.Remove(this);
}
@@ -917,23 +917,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
private bool _disposed;
private readonly object _syncLock = new object();
public void Dispose()
{
lock (_syncLock)
if (!_disposed)
{
if (!_disposed)
if (Process != null)
{
if (Process != null)
{
Process.Exited -= Process_Exited;
DisposeProcess(Process);
}
Process.Exited -= OnProcessExited;
DisposeProcess(Process);
}
_disposed = true;
}
_disposed = true;
}
}
}

View File

@@ -5,7 +5,7 @@ using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles
{
/// <summary>
/// Interface ISubtitleWriter
/// Interface ISubtitleWriter.
/// </summary>
public interface ISubtitleWriter
{

View File

@@ -1,27 +1,39 @@
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.MediaEncoding.Subtitles
{
/// <summary>
/// JSON subtitle writer.
/// </summary>
public class JsonWriter : ISubtitleWriter
{
private readonly IJsonSerializer _json;
public JsonWriter(IJsonSerializer json)
{
_json = json;
}
/// <inheritdoc />
public void Write(SubtitleTrackInfo info, Stream stream, CancellationToken cancellationToken)
{
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
using (var writer = new Utf8JsonWriter(stream))
{
var json = _json.SerializeToString(info);
var trackevents = info.TrackEvents;
writer.WriteStartArray("TrackEvents");
writer.Write(json);
for (int i = 0; i < trackevents.Count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var current = trackevents[i];
writer.WriteStartObject();
writer.WriteString("Id", current.Id);
writer.WriteString("Text", current.Text);
writer.WriteNumber("StartPositionTicks", current.StartPositionTicks);
writer.WriteNumber("EndPositionTicks", current.EndPositionTicks);
writer.WriteEndObject();
}
writer.WriteEndObject();
}
}
}

View File

@@ -14,14 +14,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true))
{
var index = 1;
var trackEvents = info.TrackEvents;
foreach (var trackEvent in info.TrackEvents)
for (int i = 0; i < trackEvents.Count; i++)
{
cancellationToken.ThrowIfCancellationRequested();
writer.WriteLine(index.ToString(CultureInfo.InvariantCulture));
writer.WriteLine(@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}", TimeSpan.FromTicks(trackEvent.StartPositionTicks), TimeSpan.FromTicks(trackEvent.EndPositionTicks));
var trackEvent = trackEvents[i];
writer.WriteLine((i + 1).ToString(CultureInfo.InvariantCulture));
writer.WriteLine(
@"{0:hh\:mm\:ss\,fff} --> {1:hh\:mm\:ss\,fff}",
TimeSpan.FromTicks(trackEvent.StartPositionTicks),
TimeSpan.FromTicks(trackEvent.EndPositionTicks));
var text = trackEvent.Text;
@@ -29,9 +34,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
text = Regex.Replace(text, @"\\n", " ", RegexOptions.IgnoreCase);
writer.WriteLine(text);
writer.WriteLine(string.Empty);
index++;
writer.WriteLine();
}
}
}

View File

@@ -17,7 +17,6 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
using UtfUnknown;
@@ -30,28 +29,25 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem;
private readonly IMediaEncoder _mediaEncoder;
private readonly IJsonSerializer _json;
private readonly IHttpClient _httpClient;
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IProcessFactory _processFactory;
public SubtitleEncoder(
ILibraryManager libraryManager,
ILoggerFactory loggerFactory,
ILogger<SubtitleEncoder> logger,
IApplicationPaths appPaths,
IFileSystem fileSystem,
IMediaEncoder mediaEncoder,
IJsonSerializer json,
IHttpClient httpClient,
IMediaSourceManager mediaSourceManager,
IProcessFactory processFactory)
{
_libraryManager = libraryManager;
_logger = loggerFactory.CreateLogger(nameof(SubtitleEncoder));
_logger = logger;
_appPaths = appPaths;
_fileSystem = fileSystem;
_mediaEncoder = mediaEncoder;
_json = json;
_httpClient = httpClient;
_mediaSourceManager = mediaSourceManager;
_processFactory = processFactory;
@@ -59,7 +55,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
private Stream ConvertSubtitles(Stream stream,
private Stream ConvertSubtitles(
Stream stream,
string inputFormat,
string outputFormat,
long startTimeTicks,
@@ -170,7 +167,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
&& (mediaSource.VideoType.Value == VideoType.BluRay || mediaSource.VideoType.Value == VideoType.Dvd))
{
var mediaSourceItem = (Video)_libraryManager.GetItemById(new Guid(mediaSource.Id));
inputFiles = mediaSourceItem.GetPlayableStreamFileNames(_mediaEncoder);
inputFiles = mediaSourceItem.GetPlayableStreamFileNames();
}
else
{
@@ -179,32 +176,27 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
var stream = await GetSubtitleStream(fileInfo.Path, subtitleStream.Language, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
return (stream, fileInfo.Format);
}
private async Task<Stream> GetSubtitleStream(string path, string language, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
private async Task<Stream> GetSubtitleStream(string path, MediaProtocol protocol, bool requiresCharset, CancellationToken cancellationToken)
{
if (requiresCharset)
{
var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false);
var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
_logger.LogDebug("charset {CharSet} detected for {Path}", charset ?? "null", path);
if (!string.IsNullOrEmpty(charset))
using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
{
// Make sure we have all the code pages we can get
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
using (var inputStream = new MemoryStream(bytes))
using (var reader = new StreamReader(inputStream, Encoding.GetEncoding(charset)))
var result = CharsetDetector.DetectFromStream(stream).Detected;
if (result != null)
{
_logger.LogDebug("charset {CharSet} detected for {Path}", result.EncodingName, path);
using var reader = new StreamReader(stream, result.Encoding);
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
bytes = Encoding.UTF8.GetBytes(text);
return new MemoryStream(bytes);
return new MemoryStream(Encoding.UTF8.GetBytes(text));
}
}
}
@@ -323,7 +315,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (string.Equals(format, "json", StringComparison.OrdinalIgnoreCase))
{
return new JsonWriter(_json);
return new JsonWriter();
}
if (string.Equals(format, SubtitleFormat.SRT, StringComparison.OrdinalIgnoreCase))
{
@@ -544,7 +536,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
if (!File.Exists(outputPath))
{
await ExtractTextSubtitleInternal(_mediaEncoder.GetInputArgument(inputFiles, protocol), subtitleStreamIndex, outputCodec, outputPath, cancellationToken).ConfigureAwait(false);
await ExtractTextSubtitleInternal(
_mediaEncoder.GetInputArgument(inputFiles, protocol),
subtitleStreamIndex,
outputCodec,
outputPath,
cancellationToken).ConfigureAwait(false);
}
}
finally
@@ -572,8 +569,13 @@ namespace MediaBrowser.MediaEncoding.Subtitles
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
var processArgs = string.Format("-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"", inputPath,
subtitleStreamIndex, outputCodec, outputPath);
var processArgs = string.Format(
CultureInfo.InvariantCulture,
"-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"",
inputPath,
subtitleStreamIndex,
outputCodec,
outputPath);
var process = _processFactory.Create(new ProcessOptions
{
@@ -721,41 +723,38 @@ namespace MediaBrowser.MediaEncoding.Subtitles
}
}
/// <inheritdoc />
public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken)
{
var bytes = await GetBytes(path, protocol, cancellationToken).ConfigureAwait(false);
using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
{
var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName;
var charset = CharsetDetector.DetectFromBytes(bytes).Detected?.EncodingName;
_logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
_logger.LogDebug("charset {0} detected for {Path}", charset ?? "null", path);
return charset;
return charset;
}
}
private async Task<byte[]> GetBytes(string path, MediaProtocol protocol, CancellationToken cancellationToken)
private Task<Stream> GetStream(string path, MediaProtocol protocol, CancellationToken cancellationToken)
{
if (protocol == MediaProtocol.Http)
switch (protocol)
{
var opts = new HttpRequestOptions()
{
Url = path,
CancellationToken = cancellationToken
};
using (var file = await _httpClient.Get(opts).ConfigureAwait(false))
using (var memoryStream = new MemoryStream())
{
await file.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;
case MediaProtocol.Http:
var opts = new HttpRequestOptions()
{
Url = path,
CancellationToken = cancellationToken,
BufferContent = true
};
return memoryStream.ToArray();
}
}
if (protocol == MediaProtocol.File)
{
return File.ReadAllBytes(path);
}
return _httpClient.Get(opts);
throw new ArgumentOutOfRangeException(nameof(protocol));
case MediaProtocol.File:
return Task.FromResult<Stream>(File.OpenRead(path));
default:
throw new ArgumentOutOfRangeException(nameof(protocol));
}
}
}
}

View File

@@ -49,12 +49,5 @@ namespace MediaBrowser.MediaEncoding.Subtitles
writer.WriteLine("</tt>");
}
}
private string FormatTime(long ticks)
{
var time = TimeSpan.FromTicks(ticks);
return string.Format(@"{0:hh\:mm\:ss\,fff}", time);
}
}
}