mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-15 23:58:57 +00:00
Merge branch 'master' into fix-hwa-video-rotation
This commit is contained in:
@@ -89,15 +89,28 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||
string outputPath,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false))
|
||||
var shouldExtractOneByOne = mediaSource.MediaAttachments.Any(a => a.FileName.Contains('/', StringComparison.OrdinalIgnoreCase) || a.FileName.Contains('\\', StringComparison.OrdinalIgnoreCase));
|
||||
if (shouldExtractOneByOne)
|
||||
{
|
||||
if (!Directory.Exists(outputPath))
|
||||
var attachmentIndexes = mediaSource.MediaAttachments.Select(a => a.Index);
|
||||
foreach (var i in attachmentIndexes)
|
||||
{
|
||||
await ExtractAllAttachmentsInternal(
|
||||
_mediaEncoder.GetInputArgument(inputFile, mediaSource),
|
||||
outputPath,
|
||||
false,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
var newName = Path.Join(outputPath, i.ToString(CultureInfo.InvariantCulture));
|
||||
await ExtractAttachment(inputFile, mediaSource, i, newName, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (!Directory.Exists(outputPath))
|
||||
{
|
||||
await ExtractAllAttachmentsInternal(
|
||||
_mediaEncoder.GetInputArgument(inputFile, mediaSource),
|
||||
outputPath,
|
||||
false,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,7 +260,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||
MediaSourceInfo mediaSource,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var outputFileLocks = new List<AsyncKeyedLockReleaser<string>>();
|
||||
var outputFileLocks = new List<IDisposable>();
|
||||
var extractableAttachmentIds = new List<int>();
|
||||
|
||||
try
|
||||
@@ -256,16 +269,15 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||
{
|
||||
var outputPath = GetAttachmentCachePath(mediaPath, mediaSource, attachment.Index);
|
||||
|
||||
var @outputFileLock = _semaphoreLocks.GetOrAdd(outputPath);
|
||||
await @outputFileLock.SemaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
var releaser = await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(outputPath))
|
||||
{
|
||||
@outputFileLock.Dispose();
|
||||
releaser.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
outputFileLocks.Add(@outputFileLock);
|
||||
outputFileLocks.Add(releaser);
|
||||
extractableAttachmentIds.Add(attachment.Index);
|
||||
}
|
||||
|
||||
@@ -280,10 +292,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var @outputFileLock in outputFileLocks)
|
||||
{
|
||||
@outputFileLock.Dispose();
|
||||
}
|
||||
outputFileLocks.ForEach(x => x.Dispose());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using BDInfo;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
@@ -60,21 +62,20 @@ public class BdInfoExaminer : IBlurayExaminer
|
||||
var sortedStreams = playlist.SortedStreams;
|
||||
var mediaStreams = new List<MediaStream>(sortedStreams.Count);
|
||||
|
||||
foreach (var stream in sortedStreams)
|
||||
for (int i = 0; i < sortedStreams.Count; i++)
|
||||
{
|
||||
var stream = sortedStreams[i];
|
||||
switch (stream)
|
||||
{
|
||||
case TSVideoStream videoStream:
|
||||
AddVideoStream(mediaStreams, videoStream);
|
||||
AddVideoStream(mediaStreams, i, videoStream);
|
||||
break;
|
||||
case TSAudioStream audioStream:
|
||||
AddAudioStream(mediaStreams, audioStream);
|
||||
AddAudioStream(mediaStreams, i, audioStream);
|
||||
break;
|
||||
case TSTextStream textStream:
|
||||
AddSubtitleStream(mediaStreams, textStream);
|
||||
break;
|
||||
case TSGraphicsStream graphicStream:
|
||||
AddSubtitleStream(mediaStreams, graphicStream);
|
||||
case TSTextStream:
|
||||
case TSGraphicsStream:
|
||||
AddSubtitleStream(mediaStreams, i, stream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -86,7 +87,7 @@ public class BdInfoExaminer : IBlurayExaminer
|
||||
if (playlist.StreamClips is not null && playlist.StreamClips.Count > 0)
|
||||
{
|
||||
// Get the files in the playlist
|
||||
outputStream.Files = playlist.StreamClips.Select(i => i.StreamFile.Name).ToArray();
|
||||
outputStream.Files = playlist.StreamClips.Select(i => i.StreamFile.FileInfo.FullName).ToArray();
|
||||
}
|
||||
|
||||
return outputStream;
|
||||
@@ -96,18 +97,19 @@ public class BdInfoExaminer : IBlurayExaminer
|
||||
/// Adds the video stream.
|
||||
/// </summary>
|
||||
/// <param name="streams">The streams.</param>
|
||||
/// <param name="index">The stream index.</param>
|
||||
/// <param name="videoStream">The video stream.</param>
|
||||
private void AddVideoStream(List<MediaStream> streams, TSVideoStream videoStream)
|
||||
private void AddVideoStream(List<MediaStream> streams, int index, TSVideoStream videoStream)
|
||||
{
|
||||
var mediaStream = new MediaStream
|
||||
{
|
||||
BitRate = Convert.ToInt32(videoStream.BitRate),
|
||||
Width = videoStream.Width,
|
||||
Height = videoStream.Height,
|
||||
Codec = videoStream.CodecShortName,
|
||||
Codec = GetNormalizedCodec(videoStream),
|
||||
IsInterlaced = videoStream.IsInterlaced,
|
||||
Type = MediaStreamType.Video,
|
||||
Index = streams.Count
|
||||
Index = index
|
||||
};
|
||||
|
||||
if (videoStream.FrameRateDenominator > 0)
|
||||
@@ -125,17 +127,19 @@ public class BdInfoExaminer : IBlurayExaminer
|
||||
/// Adds the audio stream.
|
||||
/// </summary>
|
||||
/// <param name="streams">The streams.</param>
|
||||
/// <param name="index">The stream index.</param>
|
||||
/// <param name="audioStream">The audio stream.</param>
|
||||
private void AddAudioStream(List<MediaStream> streams, TSAudioStream audioStream)
|
||||
private void AddAudioStream(List<MediaStream> streams, int index, TSAudioStream audioStream)
|
||||
{
|
||||
var stream = new MediaStream
|
||||
{
|
||||
Codec = audioStream.CodecShortName,
|
||||
Codec = GetNormalizedCodec(audioStream),
|
||||
Language = audioStream.LanguageCode,
|
||||
Channels = audioStream.ChannelCount,
|
||||
ChannelLayout = string.Format(CultureInfo.InvariantCulture, "{0:D}.{1:D}", audioStream.ChannelCount, audioStream.LFE),
|
||||
Channels = audioStream.ChannelCount + audioStream.LFE,
|
||||
SampleRate = audioStream.SampleRate,
|
||||
Type = MediaStreamType.Audio,
|
||||
Index = streams.Count
|
||||
Index = index
|
||||
};
|
||||
|
||||
var bitrate = Convert.ToInt32(audioStream.BitRate);
|
||||
@@ -145,11 +149,6 @@ public class BdInfoExaminer : IBlurayExaminer
|
||||
stream.BitRate = bitrate;
|
||||
}
|
||||
|
||||
if (audioStream.LFE > 0)
|
||||
{
|
||||
stream.Channels = audioStream.ChannelCount + 1;
|
||||
}
|
||||
|
||||
streams.Add(stream);
|
||||
}
|
||||
|
||||
@@ -157,31 +156,28 @@ public class BdInfoExaminer : IBlurayExaminer
|
||||
/// Adds the subtitle stream.
|
||||
/// </summary>
|
||||
/// <param name="streams">The streams.</param>
|
||||
/// <param name="textStream">The text stream.</param>
|
||||
private void AddSubtitleStream(List<MediaStream> streams, TSTextStream textStream)
|
||||
/// <param name="index">The stream index.</param>
|
||||
/// <param name="stream">The stream.</param>
|
||||
private void AddSubtitleStream(List<MediaStream> streams, int index, TSStream stream)
|
||||
{
|
||||
streams.Add(new MediaStream
|
||||
{
|
||||
Language = textStream.LanguageCode,
|
||||
Codec = textStream.CodecShortName,
|
||||
Language = stream.LanguageCode,
|
||||
Codec = GetNormalizedCodec(stream),
|
||||
Type = MediaStreamType.Subtitle,
|
||||
Index = streams.Count
|
||||
Index = index
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the subtitle stream.
|
||||
/// </summary>
|
||||
/// <param name="streams">The streams.</param>
|
||||
/// <param name="textStream">The text stream.</param>
|
||||
private void AddSubtitleStream(List<MediaStream> streams, TSGraphicsStream textStream)
|
||||
{
|
||||
streams.Add(new MediaStream
|
||||
private string GetNormalizedCodec(TSStream stream)
|
||||
=> stream.StreamType switch
|
||||
{
|
||||
Language = textStream.LanguageCode,
|
||||
Codec = textStream.CodecShortName,
|
||||
Type = MediaStreamType.Subtitle,
|
||||
Index = streams.Count
|
||||
});
|
||||
}
|
||||
TSStreamType.MPEG1_VIDEO => "mpeg1video",
|
||||
TSStreamType.MPEG2_VIDEO => "mpeg2video",
|
||||
TSStreamType.VC1_VIDEO => "vc1",
|
||||
TSStreamType.AC3_PLUS_AUDIO or TSStreamType.AC3_PLUS_SECONDARY_AUDIO => "eac3",
|
||||
TSStreamType.DTS_AUDIO or TSStreamType.DTS_HD_AUDIO or TSStreamType.DTS_HD_MASTER_AUDIO or TSStreamType.DTS_HD_SECONDARY_AUDIO => "dts",
|
||||
TSStreamType.PRESENTATION_GRAPHICS => "pgssub",
|
||||
_ => stream.CodecShortName
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"msmpeg4",
|
||||
"dca",
|
||||
"ac3",
|
||||
"ac4",
|
||||
"aac",
|
||||
"mp3",
|
||||
"flac",
|
||||
@@ -69,6 +70,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"aac_at",
|
||||
"libfdk_aac",
|
||||
"ac3",
|
||||
"alac",
|
||||
"dca",
|
||||
"libmp3lame",
|
||||
"libopus",
|
||||
@@ -81,6 +83,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"av1_amf",
|
||||
"h264_qsv",
|
||||
"hevc_qsv",
|
||||
"mjpeg_qsv",
|
||||
"av1_qsv",
|
||||
"h264_nvenc",
|
||||
"hevc_nvenc",
|
||||
@@ -88,9 +91,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"h264_vaapi",
|
||||
"hevc_vaapi",
|
||||
"av1_vaapi",
|
||||
"mjpeg_vaapi",
|
||||
"h264_v4l2m2m",
|
||||
"h264_videotoolbox",
|
||||
"hevc_videotoolbox",
|
||||
"mjpeg_videotoolbox",
|
||||
"h264_rkmpp",
|
||||
"hevc_rkmpp"
|
||||
};
|
||||
@@ -100,6 +105,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
// sw
|
||||
"alphasrc",
|
||||
"zscale",
|
||||
"tonemapx",
|
||||
// qsv
|
||||
"scale_qsv",
|
||||
"vpp_qsv",
|
||||
@@ -502,6 +508,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
return output.Contains(keyDesc, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public bool CheckSupportedHwaccelFlag(string flag)
|
||||
{
|
||||
return !string.IsNullOrEmpty(flag) && GetProcessExitCode(_encoderPath, $"-loglevel quiet -hwaccel_flags +{flag} -hide_banner -f lavfi -i nullsrc=s=1x1:d=100 -f null -");
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetCodecs(Codec codec)
|
||||
{
|
||||
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
|
||||
@@ -607,6 +618,31 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
}
|
||||
|
||||
private bool GetProcessExitCode(string path, string arguments)
|
||||
{
|
||||
using var process = new Process();
|
||||
process.StartInfo = new ProcessStartInfo(path, arguments)
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
};
|
||||
_logger.LogDebug("Running {Path} {Arguments}", path, arguments);
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
return process.ExitCode == 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Running {Path} {Arguments} failed with exception {Exception}", path, arguments, ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[GeneratedRegex("^\\s\\S{6}\\s(?<codec>[\\w|-]+)\\s+.+$", RegexOptions.Multiline)]
|
||||
private static partial Regex CodecRegex();
|
||||
|
||||
|
||||
@@ -30,10 +30,8 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Nikse.SubtitleEdit.Core.Common.IfoParser;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
@@ -76,6 +74,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
|
||||
|
||||
private bool _isPkeyPauseSupported = false;
|
||||
private bool _isLowPriorityHwDecodeSupported = false;
|
||||
|
||||
private bool _isVaapiDeviceAmd = false;
|
||||
private bool _isVaapiDeviceInteliHD = false;
|
||||
@@ -196,6 +195,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
_threads = EncodingHelper.GetNumberOfThreads(null, options, null);
|
||||
|
||||
_isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding");
|
||||
_isLowPriorityHwDecodeSupported = validator.CheckSupportedHwaccelFlag("low_priority");
|
||||
|
||||
// Check the Vaapi device vendor
|
||||
if (OperatingSystem.IsLinux()
|
||||
@@ -458,9 +458,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
extraArgs += " -probesize " + ffmpegProbeSize;
|
||||
}
|
||||
|
||||
if (request.MediaSource.RequiredHttpHeaders.TryGetValue("user_agent", out var userAgent))
|
||||
if (request.MediaSource.RequiredHttpHeaders.TryGetValue("User-Agent", out var userAgent))
|
||||
{
|
||||
extraArgs += " -user_agent " + userAgent;
|
||||
extraArgs += $" -user_agent \"{userAgent}\"";
|
||||
}
|
||||
|
||||
if (request.MediaSource.Protocol == MediaProtocol.Rtsp)
|
||||
{
|
||||
extraArgs += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
|
||||
}
|
||||
|
||||
return extraArgs;
|
||||
@@ -616,7 +621,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
ImageFormat? targetFormat,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var inputArgument = GetInputArgument(inputFile, mediaSource);
|
||||
var inputArgument = GetInputPathArgument(inputFile, mediaSource);
|
||||
|
||||
if (!isAudio)
|
||||
{
|
||||
@@ -705,16 +710,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24"));
|
||||
}
|
||||
|
||||
// Use SW tonemap on HDR10/HLG video stream only when the zscale filter is available.
|
||||
// Use SW tonemap on HDR10/HLG video stream only when the zscale or tonemapx filter is available.
|
||||
var enableHdrExtraction = false;
|
||||
|
||||
if ((string.Equals(videoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|
||||
if (string.Equals(videoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream?.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
|
||||
&& SupportsFilter("zscale"))
|
||||
{
|
||||
enableHdrExtraction = true;
|
||||
|
||||
filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p");
|
||||
if (SupportsFilter("tonemapx"))
|
||||
{
|
||||
enableHdrExtraction = true;
|
||||
filters.Add("tonemapx=tonemap=bt2390:desat=0:peak=100:t=bt709:m=bt709:p=bt709:format=yuv420p");
|
||||
}
|
||||
else if (SupportsFilter("zscale"))
|
||||
{
|
||||
enableHdrExtraction = true;
|
||||
filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p");
|
||||
}
|
||||
}
|
||||
|
||||
var vf = string.Join(',', filters);
|
||||
@@ -804,12 +815,28 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
int? threads,
|
||||
int? qualityScale,
|
||||
ProcessPriorityClass? priority,
|
||||
bool enableKeyFrameOnlyExtraction,
|
||||
EncodingHelper encodingHelper,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var options = allowHwAccel ? _configurationManager.GetEncodingOptions() : new EncodingOptions();
|
||||
threads ??= _threads;
|
||||
|
||||
if (allowHwAccel && enableKeyFrameOnlyExtraction)
|
||||
{
|
||||
var supportsKeyFrameOnly = (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdecDecoder)
|
||||
|| (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && OperatingSystem.IsWindows())
|
||||
|| (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwDecoder)
|
||||
|| string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase);
|
||||
if (!supportsKeyFrameOnly)
|
||||
{
|
||||
// Disable hardware acceleration when the hardware decoder does not support keyframe only mode.
|
||||
allowHwAccel = false;
|
||||
options = new EncodingOptions();
|
||||
}
|
||||
}
|
||||
|
||||
// A new EncodingOptions instance must be used as to not disable HW acceleration for all of Jellyfin.
|
||||
// Additionally, we must set a few fields without defaults to prevent null pointer exceptions.
|
||||
if (!allowHwAccel)
|
||||
@@ -819,6 +846,22 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
options.EnableTonemapping = false;
|
||||
}
|
||||
|
||||
if (imageStream.Width is not null && imageStream.Height is not null && !string.IsNullOrEmpty(imageStream.AspectRatio))
|
||||
{
|
||||
// For hardware trickplay encoders, we need to re-calculate the size because they used fixed scale dimensions
|
||||
var darParts = imageStream.AspectRatio.Split(':');
|
||||
var (wa, ha) = (double.Parse(darParts[0], CultureInfo.InvariantCulture), double.Parse(darParts[1], CultureInfo.InvariantCulture));
|
||||
// When dimension / DAR does not equal to 1:1, then the frames are most likely stored stretched.
|
||||
// Note: this might be incorrect for 3D videos as the SAR stored might be per eye instead of per video, but we really can do little about it.
|
||||
var shouldResetHeight = Math.Abs((imageStream.Width.Value * ha) - (imageStream.Height.Value * wa)) > .05;
|
||||
if (shouldResetHeight)
|
||||
{
|
||||
// SAR = DAR * Height / Width
|
||||
// RealHeight = Height / SAR = Height / (DAR * Height / Width) = Width / DAR
|
||||
imageStream.Height = Convert.ToInt32(imageStream.Width.Value * ha / wa);
|
||||
}
|
||||
}
|
||||
|
||||
var baseRequest = new BaseEncodingJobOptions { MaxWidth = maxWidth, MaxFramerate = (float)(1.0 / interval.TotalSeconds) };
|
||||
var jobState = new EncodingJobInfo(TranscodingJobType.Progressive)
|
||||
{
|
||||
@@ -843,7 +886,18 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
inputArg = "-threads " + threads + " " + inputArg; // HW accel args set a different input thread count, only set if disabled
|
||||
}
|
||||
|
||||
var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, jobState.OutputVideoCodec).Trim();
|
||||
if (options.HardwareAccelerationType.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase) && _isLowPriorityHwDecodeSupported)
|
||||
{
|
||||
// VideoToolbox supports low priority decoding, which is useful for trickplay
|
||||
inputArg = "-hwaccel_flags +low_priority " + inputArg;
|
||||
}
|
||||
|
||||
if (enableKeyFrameOnlyExtraction)
|
||||
{
|
||||
inputArg = "-skip_frame nokey " + inputArg;
|
||||
}
|
||||
|
||||
var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, vidEncoder).Trim();
|
||||
if (string.IsNullOrWhiteSpace(filterParam))
|
||||
{
|
||||
throw new InvalidOperationException("EncodingHelper returned empty or invalid filter parameters.");
|
||||
@@ -866,6 +920,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
throw new InvalidOperationException("Empty or invalid input argument.");
|
||||
}
|
||||
|
||||
float? encoderQuality = qualityScale;
|
||||
if (vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// vaapi's mjpeg encoder uses jpeg quality divided by QP2LAMBDA (118) as input, instead of ffmpeg defined qscale
|
||||
// ffmpeg qscale is a value from 1-31, with 1 being best quality and 31 being worst
|
||||
// jpeg quality is a value from 0-100, with 0 being worst quality and 100 being best
|
||||
encoderQuality = (100 - ((qualityScale - 1) * (100 / 30))) / 118;
|
||||
}
|
||||
|
||||
if (vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// videotoolbox's mjpeg encoder uses jpeg quality scaled to QP2LAMBDA (118) instead of ffmpeg defined qscale
|
||||
// ffmpeg qscale is a value from 1-31, with 1 being best quality and 31 being worst
|
||||
// jpeg quality is a value from 0-100, with 0 being worst quality and 100 being best
|
||||
encoderQuality = 118 - ((qualityScale - 1) * (118 / 30));
|
||||
}
|
||||
|
||||
// Output arguments
|
||||
var targetDirectory = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
@@ -874,12 +945,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
// Final command arguments
|
||||
var args = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"-loglevel error {0} -an -sn {1} -threads {2} -c:v {3} {4}-f {5} \"{6}\"",
|
||||
"-loglevel error {0} -an -sn {1} -threads {2} -c:v {3} {4}{5}-f {6} \"{7}\"",
|
||||
inputArg,
|
||||
filterParam,
|
||||
outputThreads.GetValueOrDefault(_threads),
|
||||
vidEncoder,
|
||||
qualityScale.HasValue ? "-qscale:v " + qualityScale.Value.ToString(CultureInfo.InvariantCulture) + " " : string.Empty,
|
||||
qualityScale.HasValue ? "-qscale:v " + encoderQuality.Value.ToString(CultureInfo.InvariantCulture) + " " : string.Empty,
|
||||
vidEncoder.Contains("videotoolbox", StringComparison.InvariantCultureIgnoreCase) ? "-allow_sw 1 " : string.Empty, // allow_sw fallback for some intel macs
|
||||
"image2",
|
||||
outputPath);
|
||||
|
||||
@@ -931,7 +1003,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
var timeoutMs = _configurationManager.Configuration.ImageExtractionTimeoutMs;
|
||||
timeoutMs = timeoutMs <= 0 ? DefaultHdrImageExtractionTimeout : timeoutMs;
|
||||
|
||||
while (isResponsive)
|
||||
while (isResponsive && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -945,8 +1017,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
// We don't actually expect the process to be finished in one timeout span, just that one image has been generated.
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var jpegCount = _fileSystem.GetFilePaths(targetDirectory).Count();
|
||||
|
||||
isResponsive = jpegCount > lastCount;
|
||||
@@ -955,7 +1025,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
if (!ranToCompletion)
|
||||
{
|
||||
_logger.LogInformation("Stopping trickplay extraction due to process inactivity.");
|
||||
if (!isResponsive)
|
||||
{
|
||||
_logger.LogInformation("Trickplay process unresponsive.");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Stopping trickplay extraction.");
|
||||
StopProcess(processWrapper, 1000);
|
||||
}
|
||||
}
|
||||
@@ -1118,19 +1193,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<string> GetPrimaryPlaylistM2tsFiles(string path)
|
||||
=> _blurayExaminer.GetDiscInfo(path).Files;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetInputPathArgument(EncodingJobInfo state)
|
||||
=> GetInputPathArgument(state.MediaPath, state.MediaSource);
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetInputPathArgument(string path, MediaSourceInfo mediaSource)
|
||||
{
|
||||
// Get all playable .m2ts files
|
||||
var validPlaybackFiles = _blurayExaminer.GetDiscInfo(path).Files;
|
||||
|
||||
// Get all files from the BDMV/STREAMING directory
|
||||
var directoryFiles = _fileSystem.GetFiles(Path.Join(path, "BDMV", "STREAM"));
|
||||
|
||||
// Only return playable local .m2ts files
|
||||
return directoryFiles
|
||||
.Where(f => validPlaybackFiles.Contains(f.Name, StringComparer.OrdinalIgnoreCase))
|
||||
.Select(f => f.FullName)
|
||||
.Order()
|
||||
.ToList();
|
||||
return mediaSource.VideoType switch
|
||||
{
|
||||
VideoType.Dvd => GetInputArgument(GetPrimaryPlaylistVobFiles(path, null), mediaSource),
|
||||
VideoType.BluRay => GetInputArgument(GetPrimaryPlaylistM2tsFiles(path), mediaSource),
|
||||
_ => GetInputArgument(path, mediaSource)
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1153,6 +1230,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
|
||||
// Generate concat configuration entries for each file and write to file
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(concatFilePath));
|
||||
using StreamWriter sw = new StreamWriter(concatFilePath);
|
||||
foreach (var path in files)
|
||||
{
|
||||
@@ -1172,7 +1250,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds;
|
||||
|
||||
// Add file path stanza to concat configuration
|
||||
sw.WriteLine("file '{0}'", path);
|
||||
sw.WriteLine("file '{0}'", path.Replace("'", @"'\''", StringComparison.Ordinal));
|
||||
|
||||
// Add duration stanza to concat configuration
|
||||
sw.WriteLine("duration {0}", duration);
|
||||
|
||||
@@ -280,8 +280,8 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
splitFormat[i] = "mpeg";
|
||||
}
|
||||
|
||||
// Handle MPEG-2 container
|
||||
else if (string.Equals(splitFormat[i], "mpeg", StringComparison.OrdinalIgnoreCase))
|
||||
// Handle MPEG-TS container
|
||||
else if (string.Equals(splitFormat[i], "mpegts", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
splitFormat[i] = "ts";
|
||||
}
|
||||
@@ -624,15 +624,19 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
{
|
||||
if (string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
codec = "dvbsub";
|
||||
codec = "DVBSUB";
|
||||
}
|
||||
else if ((codec ?? string.Empty).Contains("PGS", StringComparison.OrdinalIgnoreCase))
|
||||
else if (string.Equals(codec, "dvb_teletext", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
codec = "PGSSUB";
|
||||
codec = "DVBTXT";
|
||||
}
|
||||
else if ((codec ?? string.Empty).Contains("DVD", StringComparison.OrdinalIgnoreCase))
|
||||
else if (string.Equals(codec, "dvd_subtitle", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
codec = "DVDSUB";
|
||||
codec = "DVDSUB"; // .sub+.idx
|
||||
}
|
||||
else if (string.Equals(codec, "hdmv_pgs_subtitle", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
codec = "PGSSUB"; // .sup
|
||||
}
|
||||
|
||||
return codec;
|
||||
@@ -717,6 +721,8 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
if (streamInfo.CodecType == CodecType.Audio)
|
||||
{
|
||||
stream.Type = MediaStreamType.Audio;
|
||||
stream.LocalizedDefault = _localization.GetLocalizedString("Default");
|
||||
stream.LocalizedExternal = _localization.GetLocalizedString("External");
|
||||
|
||||
stream.Channels = streamInfo.Channels;
|
||||
|
||||
@@ -779,11 +785,10 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
&& !string.Equals(streamInfo.FieldOrder, "progressive", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (isAudio
|
||||
&& (string.Equals(stream.Codec, "bmp", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(stream.Codec, "webp", StringComparison.OrdinalIgnoreCase)))
|
||||
|| string.Equals(stream.Codec, "bmp", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(stream.Codec, "gif", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(stream.Codec, "png", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(stream.Codec, "webp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stream.Type = MediaStreamType.EmbeddedImage;
|
||||
}
|
||||
@@ -1320,23 +1325,38 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
// These support multiple values, but for now we only store the first.
|
||||
var mb = GetMultipleMusicBrainzId(tags.GetValueOrDefault("MusicBrainz Album Artist Id"))
|
||||
?? GetMultipleMusicBrainzId(tags.GetValueOrDefault("MUSICBRAINZ_ALBUMARTISTID"));
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, mb);
|
||||
if (!string.IsNullOrEmpty(mb))
|
||||
{
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, mb);
|
||||
}
|
||||
|
||||
mb = GetMultipleMusicBrainzId(tags.GetValueOrDefault("MusicBrainz Artist Id"))
|
||||
?? GetMultipleMusicBrainzId(tags.GetValueOrDefault("MUSICBRAINZ_ARTISTID"));
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzArtist, mb);
|
||||
if (!string.IsNullOrEmpty(mb))
|
||||
{
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzArtist, mb);
|
||||
}
|
||||
|
||||
mb = GetMultipleMusicBrainzId(tags.GetValueOrDefault("MusicBrainz Album Id"))
|
||||
?? GetMultipleMusicBrainzId(tags.GetValueOrDefault("MUSICBRAINZ_ALBUMID"));
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, mb);
|
||||
if (!string.IsNullOrEmpty(mb))
|
||||
{
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, mb);
|
||||
}
|
||||
|
||||
mb = GetMultipleMusicBrainzId(tags.GetValueOrDefault("MusicBrainz Release Group Id"))
|
||||
?? GetMultipleMusicBrainzId(tags.GetValueOrDefault("MUSICBRAINZ_RELEASEGROUPID"));
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, mb);
|
||||
if (!string.IsNullOrEmpty(mb))
|
||||
{
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, mb);
|
||||
}
|
||||
|
||||
mb = GetMultipleMusicBrainzId(tags.GetValueOrDefault("MusicBrainz Release Track Id"))
|
||||
?? GetMultipleMusicBrainzId(tags.GetValueOrDefault("MUSICBRAINZ_RELEASETRACKID"));
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzTrack, mb);
|
||||
if (!string.IsNullOrEmpty(mb))
|
||||
{
|
||||
audio.SetProviderId(MetadataProvider.MusicBrainzTrack, mb);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetMultipleMusicBrainzId(string value)
|
||||
@@ -1346,9 +1366,8 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.Split('/', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(i => i.Trim())
|
||||
.FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
|
||||
return value.Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1357,17 +1376,13 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
/// <param name="val">The val.</param>
|
||||
/// <param name="allowCommaDelimiter">if set to <c>true</c> [allow comma delimiter].</param>
|
||||
/// <returns>System.String[][].</returns>
|
||||
private IEnumerable<string> Split(string val, bool allowCommaDelimiter)
|
||||
private string[] Split(string val, bool allowCommaDelimiter)
|
||||
{
|
||||
// Only use the comma as a delimiter if there are no slashes or pipes.
|
||||
// We want to be careful not to split names that have commas in them
|
||||
var delimiter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.Contains(i, StringComparison.Ordinal)) ?
|
||||
_nameDelimiters :
|
||||
new[] { ',' };
|
||||
|
||||
return val.Split(delimiter, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.Select(i => i.Trim());
|
||||
return !allowCommaDelimiter || _nameDelimiters.Any(i => val.Contains(i, StringComparison.Ordinal)) ?
|
||||
val.Split(_nameDelimiters, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) :
|
||||
val.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
}
|
||||
|
||||
private IEnumerable<string> SplitDistinctArtists(string val, char[] delimiters, bool splitFeaturing)
|
||||
@@ -1391,9 +1406,7 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
}
|
||||
}
|
||||
|
||||
var artists = val.Split(delimiters, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.Select(i => i.Trim());
|
||||
var artists = val.Split(delimiters, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
artistsFound.AddRange(artists);
|
||||
return artistsFound.DistinctNames();
|
||||
@@ -1518,15 +1531,12 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
|
||||
if (tags.TryGetValue("WM/Genre", out var genres) && !string.IsNullOrWhiteSpace(genres))
|
||||
{
|
||||
var genreList = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.Select(i => i.Trim())
|
||||
.ToList();
|
||||
var genreList = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
// If this is empty then don't overwrite genres that might have been fetched earlier
|
||||
if (genreList.Count > 0)
|
||||
if (genreList.Length > 0)
|
||||
{
|
||||
video.Genres = genreList.ToArray();
|
||||
video.Genres = genreList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1537,10 +1547,9 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
|
||||
if (tags.TryGetValue("WM/MediaCredits", out var people) && !string.IsNullOrEmpty(people))
|
||||
{
|
||||
video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonKind.Actor })
|
||||
.ToArray();
|
||||
video.People = Array.ConvertAll(
|
||||
people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries),
|
||||
i => new BaseItemPerson { Name = i, Type = PersonKind.Actor });
|
||||
}
|
||||
|
||||
if (tags.TryGetValue("WM/OriginalReleaseTime", out var year) && int.TryParse(year, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear))
|
||||
|
||||
@@ -198,10 +198,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await ExtractAllTextSubtitles(mediaSource, cancellationToken).ConfigureAwait(false);
|
||||
await ExtractAllExtractableSubtitles(mediaSource, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var outputFormat = GetTextSubtitleFormat(subtitleStream);
|
||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
|
||||
var outputFileExtension = GetExtractableSubtitleFileExtension(subtitleStream);
|
||||
var outputFormat = GetExtractableSubtitleFormat(subtitleStream);
|
||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFileExtension);
|
||||
|
||||
return new SubtitleInfo()
|
||||
{
|
||||
@@ -215,6 +216,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
var currentFormat = (Path.GetExtension(subtitleStream.Path) ?? subtitleStream.Codec)
|
||||
.TrimStart('.');
|
||||
|
||||
// Handle PGS subtitles as raw streams for the client to render
|
||||
if (MediaStream.IsPgsFormat(currentFormat))
|
||||
{
|
||||
return new SubtitleInfo()
|
||||
{
|
||||
Path = subtitleStream.Path,
|
||||
Protocol = _mediaSourceManager.GetPathProtocol(subtitleStream.Path),
|
||||
Format = "pgssub",
|
||||
IsExternal = true
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback to ffmpeg conversion
|
||||
if (!_subtitleParser.SupportsFileExtension(currentFormat))
|
||||
{
|
||||
@@ -428,10 +441,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
_logger.LogInformation("ffmpeg subtitle conversion succeeded for {Path}", inputPath);
|
||||
}
|
||||
|
||||
private string GetTextSubtitleFormat(MediaStream subtitleStream)
|
||||
private string GetExtractableSubtitleFormat(MediaStream subtitleStream)
|
||||
{
|
||||
if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(subtitleStream.Codec, "pgssub", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return subtitleStream.Codec;
|
||||
}
|
||||
@@ -441,50 +455,63 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
}
|
||||
}
|
||||
|
||||
private string GetExtractableSubtitleFileExtension(MediaStream subtitleStream)
|
||||
{
|
||||
// Using .pgssub as file extension is not allowed by ffmpeg. The file extension for pgs subtitles is .sup.
|
||||
if (string.Equals(subtitleStream.Codec, "pgssub", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "sup";
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetExtractableSubtitleFormat(subtitleStream);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|| string.Equals(codec, "subrip", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "pgssub", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all text subtitles.
|
||||
/// Extracts all extractable subtitles (text and pgs).
|
||||
/// </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)
|
||||
private async Task ExtractAllExtractableSubtitles(MediaSourceInfo mediaSource, CancellationToken cancellationToken)
|
||||
{
|
||||
var locks = new List<AsyncKeyedLockReleaser<string>>();
|
||||
var locks = new List<IDisposable>();
|
||||
var extractableStreams = new List<MediaStream>();
|
||||
|
||||
try
|
||||
{
|
||||
var subtitleStreams = mediaSource.MediaStreams
|
||||
.Where(stream => stream.IsTextSubtitleStream && stream.SupportsExternalStream);
|
||||
.Where(stream => stream is { IsExtractableSubtitleStream: true, SupportsExternalStream: true, IsExternal: false });
|
||||
|
||||
foreach (var subtitleStream in subtitleStreams)
|
||||
{
|
||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream));
|
||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetExtractableSubtitleFileExtension(subtitleStream));
|
||||
|
||||
var @lock = _semaphoreLocks.GetOrAdd(outputPath);
|
||||
await @lock.SemaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
var releaser = await _semaphoreLocks.LockAsync(outputPath, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(outputPath))
|
||||
{
|
||||
@lock.Dispose();
|
||||
releaser.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
locks.Add(@lock);
|
||||
locks.Add(releaser);
|
||||
extractableStreams.Add(subtitleStream);
|
||||
}
|
||||
|
||||
if (extractableStreams.Count > 0)
|
||||
{
|
||||
await ExtractAllTextSubtitlesInternal(mediaSource, extractableStreams, cancellationToken).ConfigureAwait(false);
|
||||
await ExtractAllExtractableSubtitlesInternal(mediaSource, extractableStreams, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -493,14 +520,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var @lock in locks)
|
||||
{
|
||||
@lock.Dispose();
|
||||
}
|
||||
locks.ForEach(x => x.Dispose());
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExtractAllTextSubtitlesInternal(
|
||||
private async Task ExtractAllExtractableSubtitlesInternal(
|
||||
MediaSourceInfo mediaSource,
|
||||
List<MediaStream> subtitleStreams,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -514,7 +538,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
|
||||
foreach (var subtitleStream in subtitleStreams)
|
||||
{
|
||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream));
|
||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetExtractableSubtitleFileExtension(subtitleStream));
|
||||
var outputCodec = IsCodecCopyable(subtitleStream.Codec) ? "copy" : "srt";
|
||||
var streamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream);
|
||||
|
||||
|
||||
@@ -235,15 +235,6 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
|
||||
if (delete(job.Path!))
|
||||
{
|
||||
await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false);
|
||||
if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay)
|
||||
{
|
||||
var concatFilePath = Path.Join(_serverConfigurationManager.GetTranscodePath(), job.MediaSource.Id + ".concat");
|
||||
if (File.Exists(concatFilePath))
|
||||
{
|
||||
_logger.LogInformation("Deleting ffmpeg concat configuration at {Path}", concatFilePath);
|
||||
File.Delete(concatFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId))
|
||||
@@ -419,7 +410,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
|
||||
var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id);
|
||||
if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
|
||||
{
|
||||
var concatPath = Path.Join(_serverConfigurationManager.GetTranscodePath(), state.MediaSource.Id + ".concat");
|
||||
var concatPath = Path.Join(_appPaths.CachePath, "concat", state.MediaSource.Id + ".concat");
|
||||
await _attachmentExtractor.ExtractAllAttachments(concatPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
@@ -479,6 +470,11 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
|
||||
: "FFmpeg.DirectStream-";
|
||||
}
|
||||
|
||||
if (state.VideoRequest is null && EncodingHelper.IsCopyCodec(state.OutputAudioCodec))
|
||||
{
|
||||
logFilePrefix = "FFmpeg.Remux-";
|
||||
}
|
||||
|
||||
var logFilePath = Path.Combine(
|
||||
_serverConfigurationManager.ApplicationPaths.LogDirectoryPath,
|
||||
$"{logFilePrefix}{DateTime.Now:yyyy-MM-dd_HH-mm-ss}_{state.Request.MediaSourceId}_{Guid.NewGuid().ToString()[..8]}.log");
|
||||
@@ -492,12 +488,11 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
|
||||
IODefaults.FileStreamBufferSize,
|
||||
FileOptions.Asynchronous);
|
||||
|
||||
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
||||
await JsonSerializer.SerializeAsync(logStream, state.MediaSource, cancellationToken: cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(
|
||||
JsonSerializer.Serialize(state.MediaSource)
|
||||
Environment.NewLine
|
||||
+ Environment.NewLine
|
||||
+ Environment.NewLine
|
||||
+ commandLineLogMessage
|
||||
+ process.StartInfo.FileName + " " + process.StartInfo.Arguments
|
||||
+ Environment.NewLine
|
||||
+ Environment.NewLine);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user