mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-03-21 01:26:41 +00:00
Merge changes
This commit is contained in:
@@ -45,7 +45,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"mpeg4_cuvid",
|
||||
"vp8_cuvid",
|
||||
"vp9_cuvid",
|
||||
"av1_cuvid"
|
||||
"av1_cuvid",
|
||||
"h264_rkmpp",
|
||||
"hevc_rkmpp",
|
||||
"mpeg1_rkmpp",
|
||||
"mpeg2_rkmpp",
|
||||
"mpeg4_rkmpp",
|
||||
"vp8_rkmpp",
|
||||
"vp9_rkmpp",
|
||||
"av1_rkmpp"
|
||||
};
|
||||
|
||||
private static readonly string[] _requiredEncoders = new[]
|
||||
@@ -82,7 +90,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"av1_vaapi",
|
||||
"h264_v4l2m2m",
|
||||
"h264_videotoolbox",
|
||||
"hevc_videotoolbox"
|
||||
"hevc_videotoolbox",
|
||||
"h264_rkmpp",
|
||||
"hevc_rkmpp"
|
||||
};
|
||||
|
||||
private static readonly string[] _requiredFilters = new[]
|
||||
@@ -116,9 +126,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"libplacebo",
|
||||
"scale_vulkan",
|
||||
"overlay_vulkan",
|
||||
"hwupload_vaapi",
|
||||
// videotoolbox
|
||||
"yadif_videotoolbox"
|
||||
"yadif_videotoolbox",
|
||||
// rkrga
|
||||
"scale_rkrga",
|
||||
"vpp_rkrga",
|
||||
"overlay_rkrga"
|
||||
};
|
||||
|
||||
private static readonly Dictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
|
||||
|
||||
@@ -904,8 +904,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
bool ranToCompletion = false;
|
||||
|
||||
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
using (await _thumbnailResourcePool.LockAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
StartProcess(processWrapper);
|
||||
|
||||
@@ -959,10 +958,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
StopProcess(processWrapper, 1000);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_thumbnailResourcePool.Release();
|
||||
}
|
||||
|
||||
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
|
||||
|
||||
|
||||
@@ -742,6 +742,10 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
stream.LocalizedExternal = _localization.GetLocalizedString("External");
|
||||
stream.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
|
||||
|
||||
// Graphical subtitle may have width and height info
|
||||
stream.Width = streamInfo.Width;
|
||||
stream.Height = streamInfo.Height;
|
||||
|
||||
if (string.IsNullOrEmpty(stream.Title))
|
||||
{
|
||||
// mp4 missing track title workaround: fall back to handler_name if populated and not the default "SubtitleHandler"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
@@ -18,7 +19,6 @@ using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
@@ -198,36 +198,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,
|
||||
@@ -453,6 +428,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 locks = new List<AsyncKeyedLockReleaser<string>>();
|
||||
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 @lock = _semaphoreLocks.GetOrAdd(outputPath);
|
||||
await @lock.SemaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(outputPath))
|
||||
{
|
||||
@lock.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
locks.Add(@lock);
|
||||
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 @lock in locks)
|
||||
{
|
||||
@lock.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AsyncKeyedLock;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
@@ -401,7 +402,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
|
||||
|
||||
if (state.VideoRequest is not null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
||||
{
|
||||
var user = userId.Equals(default) ? null : _userManager.GetUserById(userId);
|
||||
var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
|
||||
if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
|
||||
{
|
||||
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
|
||||
|
||||
Reference in New Issue
Block a user