Merge pull request #10884 from alltilla/extract-all-subtitles

Extract all subtitle streams simultaneously
This commit is contained in:
Cody Robibero 2024-02-02 17:00:01 -07:00 committed by GitHub
commit 81cf4b6c50
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 201 additions and 27 deletions

View File

@ -4,6 +4,7 @@
- [97carmine](https://github.com/97carmine)
- [Abbe98](https://github.com/Abbe98)
- [agrenott](https://github.com/agrenott)
- [alltilla](https://github.com/alltilla)
- [AndreCarvalho](https://github.com/AndreCarvalho)
- [anthonylavado](https://github.com/anthonylavado)
- [Artiume](https://github.com/Artiume)

View File

@ -2,6 +2,7 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
@ -194,36 +195,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{
if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
{
string outputFormat;
string outputCodec;
await ExtractAllTextSubtitles(mediaSource, cancellationToken).ConfigureAwait(false);
if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|| string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)
|| string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase))
{
// Extract
outputCodec = "copy";
outputFormat = subtitleStream.Codec;
}
else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase))
{
// Extract
outputCodec = "copy";
outputFormat = "srt";
}
else
{
// Extract
outputCodec = "srt";
outputFormat = "srt";
}
// Extract
var outputFormat = GetTextSubtitleFormat(subtitleStream);
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath, cancellationToken)
.ConfigureAwait(false);
return new SubtitleInfo()
{
Path = outputPath,
@ -467,6 +443,203 @@ namespace MediaBrowser.MediaEncoding.Subtitles
_logger.LogInformation("ffmpeg subtitle conversion succeeded for {Path}", inputPath);
}
private string GetTextSubtitleFormat(MediaStream subtitleStream)
{
if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|| string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
{
return subtitleStream.Codec;
}
else
{
return "srt";
}
}
private bool IsCodecCopyable(string codec)
{
return string.Equals(codec, "ass", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "ssa", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "srt", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "subrip", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Extracts all text subtitles.
/// </summary>
/// <param name="mediaSource">The mediaSource.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
private async Task ExtractAllTextSubtitles(MediaSourceInfo mediaSource, CancellationToken cancellationToken)
{
var semaphores = new List<SemaphoreSlim>();
var extractableStreams = new List<MediaStream>();
try
{
var subtitleStreams = mediaSource.MediaStreams
.Where(stream => stream.IsTextSubtitleStream && stream.SupportsExternalStream);
foreach (var subtitleStream in subtitleStreams)
{
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream));
var semaphore = GetLock(outputPath);
await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
if (File.Exists(outputPath))
{
semaphore.Release();
continue;
}
semaphores.Add(semaphore);
extractableStreams.Add(subtitleStream);
}
if (extractableStreams.Count > 0)
{
await ExtractAllTextSubtitlesInternal(mediaSource, extractableStreams, cancellationToken).ConfigureAwait(false);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Unable to get streams for File:{File}", mediaSource.Path);
}
finally
{
foreach (var semaphore in semaphores)
{
semaphore.Release();
}
}
}
private async Task ExtractAllTextSubtitlesInternal(
MediaSourceInfo mediaSource,
List<MediaStream> subtitleStreams,
CancellationToken cancellationToken)
{
var inputPath = mediaSource.Path;
var outputPaths = new List<string>();
var args = string.Format(
CultureInfo.InvariantCulture,
"-i {0} -copyts",
inputPath);
foreach (var subtitleStream in subtitleStreams)
{
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream));
var outputCodec = IsCodecCopyable(subtitleStream.Codec) ? "copy" : "srt";
var streamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream);
if (streamIndex == -1)
{
_logger.LogError("Cannot find subtitle stream index for {InputPath} ({Index}), skipping this stream", inputPath, subtitleStream.Index);
continue;
}
Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new FileNotFoundException($"Calculated path ({outputPath}) is not valid."));
outputPaths.Add(outputPath);
args += string.Format(
CultureInfo.InvariantCulture,
" -map 0:{0} -an -vn -c:s {1} \"{2}\"",
streamIndex,
outputCodec,
outputPath);
}
int exitCode;
using (var process = new Process
{
StartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
UseShellExecute = false,
FileName = _mediaEncoder.EncoderPath,
Arguments = args,
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
},
EnableRaisingEvents = true
})
{
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
try
{
process.Start();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error starting ffmpeg");
throw;
}
try
{
await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
exitCode = process.ExitCode;
}
catch (OperationCanceledException)
{
process.Kill(true);
exitCode = -1;
}
}
var failed = false;
if (exitCode == -1)
{
failed = true;
foreach (var outputPath in outputPaths)
{
try
{
_logger.LogWarning("Deleting extracted subtitle due to failure: {Path}", outputPath);
_fileSystem.DeleteFile(outputPath);
}
catch (FileNotFoundException)
{
}
catch (IOException ex)
{
_logger.LogError(ex, "Error deleting extracted subtitle {Path}", outputPath);
}
}
}
else
{
foreach (var outputPath in outputPaths)
{
if (!File.Exists(outputPath))
{
_logger.LogError("ffmpeg subtitle extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath);
failed = true;
continue;
}
if (outputPath.EndsWith("ass", StringComparison.OrdinalIgnoreCase))
{
await SetAssFont(outputPath, cancellationToken).ConfigureAwait(false);
}
_logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
}
}
if (failed)
{
throw new FfmpegException(
string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle extraction failed for {0}", inputPath));
}
}
/// <summary>
/// Extracts the text subtitle.
/// </summary>