From 6f8ccab788e85e025eaa44b67a1487bf419afb53 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sat, 19 Jun 2021 18:02:33 +0200 Subject: Move non-jellyfin extensions to separate project --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 3af618af8..412a95321 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -11,9 +11,9 @@ using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions.Json; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Json; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Probing; -- cgit v1.2.3 From 3beda02d925c74c7a7083eaee733537f3396ec92 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 25 Jul 2021 00:52:16 +0800 Subject: add support for cuda tonemap and overlay --- .../MediaEncoding/EncodingHelper.cs | 385 ++++++++++++++++----- .../MediaEncoding/FilterOptionType.cs | 23 ++ .../MediaEncoding/IMediaEncoder.cs | 14 +- .../Encoder/EncoderValidator.cs | 102 +++++- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 39 ++- .../Probing/ProbeResultNormalizer.cs | 17 + 6 files changed, 477 insertions(+), 103 deletions(-) create mode 100644 MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 257cd5df6..b12cacb6f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -40,6 +40,8 @@ namespace MediaBrowser.Controller.MediaEncoding "ConstrainedHigh" }; + private static readonly Version minVersionForCudaOverlay = new Version(4, 4); + public EncodingHelper( IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder) @@ -109,17 +111,41 @@ namespace MediaBrowser.Controller.MediaEncoding private bool IsCudaSupported() { return _mediaEncoder.SupportsHwaccel("cuda") - && _mediaEncoder.SupportsFilter("scale_cuda", null) - && _mediaEncoder.SupportsFilter("yadif_cuda", null); + && _mediaEncoder.SupportsFilter("scale_cuda") + && _mediaEncoder.SupportsFilter("yadif_cuda") + && _mediaEncoder.SupportsFilter("hwupload_cuda"); } - private bool IsTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + private bool IsOpenclTonemappingSupported(EncodingJobInfo state, EncodingOptions options) { var videoStream = state.VideoStream; - return IsColorDepth10(state) + if (videoStream == null) + { + return false; + } + + return (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + && IsColorDepth10(state) && _mediaEncoder.SupportsHwaccel("opencl") - && options.EnableTonemapping - && string.Equals(videoStream.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase); + && _mediaEncoder.SupportsFilter("tonemap_opencl") + && options.EnableTonemapping; + } + + private bool IsCudaTonemappingSupported(EncodingJobInfo state, EncodingOptions options) + { + var videoStream = state.VideoStream; + if (videoStream == null) + { + return false; + } + + return (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + && IsColorDepth10(state) + && _mediaEncoder.SupportsHwaccel("cuda") + && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName) + && options.EnableTonemapping; } private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options) @@ -135,23 +161,25 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { // Limited to HEVC for now since the filter doesn't accept master data from VP9. - return IsColorDepth10(state) + return string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + && IsColorDepth10(state) && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) && _mediaEncoder.SupportsHwaccel("vaapi") - && options.EnableVppTonemapping - && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase); + && _mediaEncoder.SupportsFilter("tonemap_vaapi") + && options.EnableVppTonemapping; } // Hybrid VPP tonemapping for QSV with VAAPI if (OperatingSystem.IsLinux() && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { // Limited to HEVC for now since the filter doesn't accept master data from VP9. - return IsColorDepth10(state) + return string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) + && IsColorDepth10(state) && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) && _mediaEncoder.SupportsHwaccel("vaapi") + && _mediaEncoder.SupportsFilter("tonemap_vaapi") && _mediaEncoder.SupportsHwaccel("qsv") - && options.EnableVppTonemapping - && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase); + && options.EnableVppTonemapping; } // Native VPP tonemapping may come to QSV in the future. @@ -489,11 +517,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// /// Gets the input argument. /// - public string GetInputArgument(EncodingJobInfo state, EncodingOptions encodingOptions) + public string GetInputArgument(EncodingJobInfo state, EncodingOptions options) { var arg = new StringBuilder(); - var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty; - var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; + var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty; + var outputVideoCodec = GetVideoEncoder(state, options) ?? string.Empty; + var isWindows = OperatingSystem.IsWindows(); + var isLinux = OperatingSystem.IsLinux(); + var isMacOS = OperatingSystem.IsMacOS(); var isSwDecoder = string.IsNullOrEmpty(videoDecoder); var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1; var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1; @@ -502,26 +533,24 @@ namespace MediaBrowser.Controller.MediaEncoding var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase); - var isWindows = OperatingSystem.IsWindows(); - var isLinux = OperatingSystem.IsLinux(); - var isMacOS = OperatingSystem.IsMacOS(); - var isTonemappingSupported = IsTonemappingSupported(state, encodingOptions); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, encodingOptions); + var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); + var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); if (!IsCopyCodec(outputVideoCodec)) { if (state.IsVideoRequest && _mediaEncoder.SupportsHwaccel("vaapi") - && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) + && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) { if (isVaapiDecoder) { - if (isTonemappingSupported && !isVppTonemappingSupported) + if (isOpenclTonemappingSupported && !isVppTonemappingSupported) { arg.Append("-init_hw_device vaapi=va:") - .Append(encodingOptions.VaapiDevice) - .Append(' ') - .Append("-init_hw_device opencl=ocl@va ") + .Append(options.VaapiDevice) + .Append(" -init_hw_device opencl=ocl@va ") .Append("-hwaccel_device va ") .Append("-hwaccel_output_format vaapi ") .Append("-filter_hw_device ocl "); @@ -530,14 +559,14 @@ namespace MediaBrowser.Controller.MediaEncoding { arg.Append("-hwaccel_output_format vaapi ") .Append("-vaapi_device ") - .Append(encodingOptions.VaapiDevice) + .Append(options.VaapiDevice) .Append(' '); } } else if (!isVaapiDecoder && isVaapiEncoder) { arg.Append("-vaapi_device ") - .Append(encodingOptions.VaapiDevice) + .Append(options.VaapiDevice) .Append(' '); } @@ -545,7 +574,7 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) + && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) { var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -581,9 +610,8 @@ namespace MediaBrowser.Controller.MediaEncoding else if (isVaapiDecoder && isVppTonemappingSupported) { arg.Append("-init_hw_device vaapi=va:") - .Append(encodingOptions.VaapiDevice) - .Append(' ') - .Append("-init_hw_device qsv@va ") + .Append(options.VaapiDevice) + .Append(" -init_hw_device qsv@va ") .Append("-hwaccel_output_format vaapi "); } @@ -592,7 +620,7 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) + && string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && isNvdecDecoder) { // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562 @@ -600,22 +628,31 @@ namespace MediaBrowser.Controller.MediaEncoding } if (state.IsVideoRequest - && ((string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) - && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder)) - || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) - && (isD3d11vaDecoder || isSwDecoder)))) + && ((string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) + && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder)))) + { + if (!isCudaTonemappingSupported && isOpenclTonemappingSupported) + { + arg.Append("-init_hw_device opencl=ocl:") + .Append(options.OpenclDevice) + .Append(" -filter_hw_device ocl "); + } + } + + if (state.IsVideoRequest + && string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) + && (isD3d11vaDecoder || isSwDecoder)) { - if (isTonemappingSupported) + if (isOpenclTonemappingSupported) { arg.Append("-init_hw_device opencl=ocl:") - .Append(encodingOptions.OpenclDevice) - .Append(' ') - .Append("-filter_hw_device ocl "); + .Append(options.OpenclDevice) + .Append(" -filter_hw_device ocl "); } } if (state.IsVideoRequest - && string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + && string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) { arg.Append("-hwaccel videotoolbox "); } @@ -1991,14 +2028,18 @@ namespace MediaBrowser.Controller.MediaEncoding var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase); var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase); var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); - var isTonemappingSupported = IsTonemappingSupported(state, options); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); + + var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); // Tonemapping and burn-in graphical subtitles requires overlay_vaapi. // But it's still in ffmpeg mailing list. Disable it for now. - if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported) + if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported) { return GetOutputSizeParam(state, options, outputVideoCodec); } @@ -2024,13 +2065,22 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(videoSizeParam) && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported)) { - // For QSV, feed it into hardware encoder now + // upload graphical subtitle to QSV if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))) { videoSizeParam += ",hwupload=extra_hw_frames=64"; } } + + if (!string.IsNullOrEmpty(videoSizeParam)) + { + // upload graphical subtitle to cuda + if (isNvdecDecoder && isNvencEncoder && isCudaOverlaySupported && isCudaFormatConversionSupported) + { + videoSizeParam += ",hwupload_cuda"; + } + } } var mapPrefix = state.SubtitleStream.IsExternal ? @@ -2043,9 +2093,9 @@ namespace MediaBrowser.Controller.MediaEncoding // Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference) // Always put the scaler before the overlay for better performance - var retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""; + var retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; // When the input may or may not be hardware VAAPI decodable if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) @@ -2056,9 +2106,9 @@ namespace MediaBrowser.Controller.MediaEncoding [sub]: SW scaling subtitle to FixedOutputSize [base][sub]: SW overlay */ - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""; } // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first @@ -2071,9 +2121,9 @@ namespace MediaBrowser.Controller.MediaEncoding [sub]: SW scaling subtitle to FixedOutputSize [base][sub]: SW overlay */ - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""; + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\""; } else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) @@ -2090,16 +2140,25 @@ namespace MediaBrowser.Controller.MediaEncoding } else if (isLinux) { - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""; + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\""; } } else if (isNvdecDecoder && isNvencEncoder) { - retStr = !outputSizeParam.IsEmpty - ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"" - : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""; + if (isCudaOverlaySupported && isCudaFormatConversionSupported) + { + retStr = outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]scale_cuda=format=yuv420p[base];[base][sub]overlay_cuda\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_cuda\""; + } + else + { + retStr = !outputSizeParam.IsEmpty + ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"" + : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""; + } } return string.Format( @@ -2196,11 +2255,11 @@ namespace MediaBrowser.Controller.MediaEncoding var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase); var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase); var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase); - var isTonemappingSupported = IsTonemappingSupported(state, options); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); - var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported)) + var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported)) || (isTonemappingSupportedOnQsv && isVppTonemappingSupported); var outputPixFmt = "format=nv12"; @@ -2251,15 +2310,23 @@ namespace MediaBrowser.Controller.MediaEncoding var outputWidth = width.Value; var outputHeight = height.Value; - var isTonemappingSupported = IsTonemappingSupported(state, options); + var isNvencEncoder = videoEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase); - var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")"); + var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); + var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var outputPixFmt = string.Empty; if (isCudaFormatConversionSupported) { - outputPixFmt = "format=nv12"; - if (isTonemappingSupported && isTonemappingSupportedOnNvenc) + outputPixFmt = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder) + ? "format=yuv420p" + : "format=nv12"; + if ((isOpenclTonemappingSupported || isCudaTonemappingSupported) + && isTonemappingSupportedOnNvenc) { outputPixFmt = "format=p010"; } @@ -2525,16 +2592,21 @@ namespace MediaBrowser.Controller.MediaEncoding var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase); var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase); var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase); + var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase); var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1; var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1; var isLinux = OperatingSystem.IsLinux(); var isColorDepth10 = IsColorDepth10(state); - var isTonemappingSupported = IsTonemappingSupported(state, options); - var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); - var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder); + + var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder); var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder); var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder); var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder); + var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options); + var isVppTonemappingSupported = IsVppTonemappingSupported(state, options); + var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options); + var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion(); + var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= minVersionForCudaOverlay; var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; @@ -2546,19 +2618,25 @@ namespace MediaBrowser.Controller.MediaEncoding var isScalingInAdvance = false; var isCudaDeintInAdvance = false; var isHwuploadCudaRequired = false; + var isNoTonemapFilterApplied = true; var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); // Add OpenCL tonemapping filter for NVENC/AMF/VAAPI. - if (isTonemappingSupportedOnNvenc || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported)) + if ((isTonemappingSupportedOnNvenc && !isCudaTonemappingSupported) || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported)) { - // Currently only with the use of NVENC decoder can we get a decent performance. - // Currently only the HEVC/H265 format is supported with NVDEC decoder. // NVIDIA Pascal and Turing or higher are recommended. // AMD Polaris and Vega or higher are recommended. // Intel Kaby Lake or newer is required. - if (isTonemappingSupported) + if (isOpenclTonemappingSupported) { + isNoTonemapFilterApplied = false; + var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer); + if (!string.IsNullOrEmpty(inputHdrParams)) + { + filters.Add(inputHdrParams); + } + var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}"; if (options.TonemappingParam != 0) @@ -2630,7 +2708,11 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwdownload,format=p010"); } - if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder) + if (isNvdecDecoder + || isCuvidHevcDecoder + || isCuvidVp9Decoder + || isSwDecoder + || isD3d11vaDecoder) { // Upload the HDR10 or HLG data to the OpenCL device, // use tonemap_opencl filter for tone mapping, @@ -2638,6 +2720,14 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwupload"); } + // Fallback to hable if bt2390 is chosen but not supported in tonemap_opencl. + var isBt2390SupportedInOpenclTonemap = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390); + if (string.Equals(options.TonemappingAlgorithm, "bt2390", StringComparison.OrdinalIgnoreCase) + && !isBt2390SupportedInOpenclTonemap) + { + options.TonemappingAlgorithm = "hable"; + } + filters.Add( string.Format( CultureInfo.InvariantCulture, @@ -2649,7 +2739,11 @@ namespace MediaBrowser.Controller.MediaEncoding options.TonemappingParam, options.TonemappingRange)); - if (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder || isD3d11vaDecoder) + if (isNvdecDecoder + || isCuvidHevcDecoder + || isCuvidVp9Decoder + || isSwDecoder + || isD3d11vaDecoder) { filters.Add("hwdownload"); filters.Add("format=nv12"); @@ -2665,12 +2759,18 @@ namespace MediaBrowser.Controller.MediaEncoding // Reverse the data route from opencl to vaapi. filters.Add("hwmap=derive_device=vaapi:reverse=1"); } + + var outputSdrParams = GetOutputSdrParams(options.TonemappingRange); + if (!string.IsNullOrEmpty(outputSdrParams)) + { + filters.Add(outputSdrParams); + } } } // When the input may or may not be hardware VAAPI decodable. if ((isVaapiH264Encoder || isVaapiHevcEncoder) - && !(isTonemappingSupportedOnVaapi && (isTonemappingSupported || isVppTonemappingSupported))) + && !(isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported))) { filters.Add("format=nv12|vaapi"); filters.Add("hwupload"); @@ -2778,6 +2878,61 @@ namespace MediaBrowser.Controller.MediaEncoding request.MaxHeight)); } + // Add Cuda tonemapping filter. + if (isNvdecDecoder && isCudaTonemappingSupported) + { + isNoTonemapFilterApplied = false; + var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer); + if (!string.IsNullOrEmpty(inputHdrParams)) + { + filters.Add(inputHdrParams); + } + + var parameters = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder) + ? "tonemap_cuda=format=yuv420p:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}" + : "tonemap_cuda=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}"; + + if (options.TonemappingParam != 0) + { + parameters += ":param={3}"; + } + + if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase)) + { + parameters += ":range={4}"; + } + + filters.Add( + string.Format( + CultureInfo.InvariantCulture, + parameters, + options.TonemappingAlgorithm, + options.TonemappingPeak, + options.TonemappingDesat, + options.TonemappingParam, + options.TonemappingRange)); + + if (isLibX264Encoder + || isLibX265Encoder + || hasTextSubs + || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)) + { + if (isNvencEncoder) + { + isHwuploadCudaRequired = true; + } + + filters.Add("hwdownload"); + filters.Add("format=nv12"); + } + + var outputSdrParams = GetOutputSdrParams(options.TonemappingRange); + if (!string.IsNullOrEmpty(outputSdrParams)) + { + filters.Add(outputSdrParams); + } + } + // Add VPP tonemapping filter for VAAPI. // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options. if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv) @@ -2787,10 +2942,10 @@ namespace MediaBrowser.Controller.MediaEncoding } // Another case is when using Nvenc decoder. - if (isNvdecDecoder && !isTonemappingSupported) + if (isNvdecDecoder && !isOpenclTonemappingSupported && !isCudaTonemappingSupported) { var codec = videoStream.Codec; - var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilter("scale_cuda", "Output format (default \"same\")"); + var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat); // Assert 10-bit hardware decodable if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase) @@ -2799,7 +2954,10 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isCudaFormatConversionSupported) { - if (isLibX264Encoder || isLibX265Encoder || hasSubs) + if (isLibX264Encoder + || isLibX265Encoder + || hasTextSubs + || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)) { if (isNvencEncoder) { @@ -2826,7 +2984,11 @@ namespace MediaBrowser.Controller.MediaEncoding } // Assert 8-bit hardware decodable - else if (!isColorDepth10 && (isLibX264Encoder || isLibX265Encoder || hasSubs)) + else if (!isColorDepth10 + && (isLibX264Encoder + || isLibX265Encoder + || hasTextSubs + || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))) { if (isNvencEncoder) { @@ -2847,7 +3009,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // Convert hw context from ocl to va. // For tonemapping and text subs burn-in. - if (isTonemappingSupportedOnVaapi && isTonemappingSupported && !isVppTonemappingSupported) + if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported) { filters.Add("scale_vaapi"); } @@ -2893,6 +3055,17 @@ namespace MediaBrowser.Controller.MediaEncoding filters.Add("hwupload_cuda"); } + // If no tonemap filter is applied, + // tag the video range as SDR to prevent the encoder from encoding HDR video. + if (isNoTonemapFilterApplied) + { + var outputSdrParams = GetOutputSdrParams(null); + if (!string.IsNullOrEmpty(outputSdrParams)) + { + filters.Add(outputSdrParams); + } + } + var output = string.Empty; if (filters.Count > 0) { @@ -2905,6 +3078,36 @@ namespace MediaBrowser.Controller.MediaEncoding return output; } + public static string GetInputHdrParams(string colorTransfer) + { + if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase)) + { + // HLG + return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc"; + } + else + { + // HDR10 + return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc"; + } + } + + public static string GetOutputSdrParams(string tonemappingRange) + { + // SDR + if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase)) + { + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv"; + } + + if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase)) + { + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc"; + } + + return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709"; + } + /// /// Gets the number of threads. /// @@ -3371,8 +3574,13 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && IsVppTonemappingSupported(state, encodingOptions)) { - // Since tonemap_vaapi only support HEVC for now, no need to check the codec again. - return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); + var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty; + var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1; + if (isQsvEncoder) + { + // Since tonemap_vaapi only support HEVC for now, no need to check the codec again. + return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10); + } } if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)) @@ -3895,6 +4103,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (videoStream != null) { + if (videoStream.BitDepth.HasValue) + { + return videoStream.BitDepth.Value == 10; + } + if (!string.IsNullOrEmpty(videoStream.PixelFormat)) { result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase); @@ -3914,12 +4127,6 @@ namespace MediaBrowser.Controller.MediaEncoding return true; } } - - result = (videoStream.BitDepth ?? 8) == 10; - if (result) - { - return true; - } } return result; diff --git a/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs new file mode 100644 index 000000000..7ce707b19 --- /dev/null +++ b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs @@ -0,0 +1,23 @@ +namespace MediaBrowser.Controller.MediaEncoding +{ + /// + /// Enum FilterOptionType. + /// + public enum FilterOptionType + { + /// + /// The scale_cuda_format. + /// + ScaleCudaFormat = 0, + + /// + /// The tonemap_cuda_name. + /// + TonemapCudaName = 1, + + /// + /// The tonemap_opencl_bt2390. + /// + TonemapOpenclBt2390 = 2 + } +} diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 76a9fd7c7..31913acef 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -55,9 +55,21 @@ namespace MediaBrowser.Controller.MediaEncoding /// Whether given filter is supported. /// /// The filter. + /// true if the filter is supported, false otherwise. + bool SupportsFilter(string filter); + + /// + /// Whether filter is supported with the given option. + /// /// The option. /// true if the filter is supported, false otherwise. - bool SupportsFilter(string filter, string option); + bool SupportsFilterWithOption(FilterOptionType option); + + /// + /// Get the version of media encoder. + /// + /// The version of media encoder. + Version GetMediaEncoderVersion(); /// /// Extracts the audio image. diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index f782e65bd..1ec159c9a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -89,6 +89,24 @@ namespace MediaBrowser.MediaEncoding.Encoder "hevc_videotoolbox" }; + private static readonly string[] _requiredFilters = new[] + { + "scale_cuda", + "yadif_cuda", + "hwupload_cuda", + "overlay_cuda", + "tonemap_cuda", + "tonemap_opencl", + "tonemap_vaapi", + }; + + private static readonly IReadOnlyDictionary _filterOptionsDict = new Dictionary + { + { 0, new string[] { "scale_cuda", "Output format (default \"same\")" } }, + { 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } }, + { 2, new string[] { "tonemap_opencl", "bt2390" } } + }; + // These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below private static readonly IReadOnlyDictionary _ffmpegMinimumLibraryVersions = new Dictionary { @@ -156,7 +174,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } // Work out what the version under test is - var version = GetFFmpegVersion(versionOutput); + var version = GetFFmpegVersionInternal(versionOutput); _logger.LogInformation("Found ffmpeg version {Version}", version != null ? version.ToString() : "unknown"); @@ -200,6 +218,34 @@ namespace MediaBrowser.MediaEncoding.Encoder public IEnumerable GetHwaccels() => GetHwaccelTypes(); + public IEnumerable GetFilters() => GetFFmpegFilters(); + + public IDictionary GetFiltersWithOption() => GetFFmpegFiltersWithOption(); + + public Version? GetFFmpegVersion() + { + string output; + try + { + output = GetProcessOutput(_encoderPath, "-version"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error validating encoder"); + return null; + } + + if (string.IsNullOrWhiteSpace(output)) + { + _logger.LogError("FFmpeg validation: The process returned no result"); + return null; + } + + _logger.LogDebug("ffmpeg output: {Output}", output); + + return GetFFmpegVersionInternal(output); + } + /// /// Using the output from "ffmpeg -version" work out the FFmpeg version. /// For pre-built binaries the first line should contain a string like "ffmpeg version x.y", which is easy @@ -208,7 +254,7 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// The output from "ffmpeg -version". /// The FFmpeg version. - internal Version? GetFFmpegVersion(string output) + internal Version? GetFFmpegVersionInternal(string output) { // For pre-built binaries the FFmpeg version should be mentioned at the very start of the output var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)"); @@ -297,9 +343,9 @@ namespace MediaBrowser.MediaEncoding.Encoder return found; } - public bool CheckFilter(string filter, string option) + public bool CheckFilterWithOption(string filter, string option) { - if (string.IsNullOrEmpty(filter)) + if (string.IsNullOrEmpty(filter) || string.IsNullOrEmpty(option)) { return false; } @@ -317,11 +363,6 @@ namespace MediaBrowser.MediaEncoding.Encoder if (output.Contains("Filter " + filter, StringComparison.Ordinal)) { - if (string.IsNullOrEmpty(option)) - { - return true; - } - return output.Contains(option, StringComparison.Ordinal); } @@ -362,6 +403,49 @@ namespace MediaBrowser.MediaEncoding.Encoder return found; } + private IEnumerable GetFFmpegFilters() + { + string output; + try + { + output = GetProcessOutput(_encoderPath, "-filters"); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error detecting available filters"); + return Enumerable.Empty(); + } + + if (string.IsNullOrWhiteSpace(output)) + { + return Enumerable.Empty(); + } + + var found = Regex + .Matches(output, @"^\s\S{3}\s(?[\w|-]+)\s+.+$", RegexOptions.Multiline) + .Cast() + .Select(x => x.Groups["filter"].Value) + .Where(x => _requiredFilters.Contains(x)); + + _logger.LogInformation("Available filters: {Filters}", found); + + return found; + } + + private IDictionary GetFFmpegFiltersWithOption() + { + IDictionary dict = new Dictionary(); + for (int i = 0; i < _filterOptionsDict.Count; i++) + { + if (_filterOptionsDict.TryGetValue(i, out var val) && val.Length == 2) + { + dict.Add(i, CheckFilterWithOption(val[0], val[1])); + } + } + + return dict; + } + private string GetProcessOutput(string path, string arguments) { using (var process = new Process() diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 412a95321..238627e96 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -66,7 +66,10 @@ namespace MediaBrowser.MediaEncoding.Encoder private List _encoders = new List(); private List _decoders = new List(); private List _hwaccels = new List(); + private List _filters = new List(); + private IDictionary _filtersWithOption = new Dictionary(); + private Version _ffmpegVersion = null; private string _ffmpegPath = string.Empty; private string _ffprobePath; private int threads; @@ -130,7 +133,11 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableDecoders(validator.GetDecoders()); SetAvailableEncoders(validator.GetEncoders()); + SetAvailableFilters(validator.GetFilters()); + SetAvailableFiltersWithOption(validator.GetFiltersWithOption()); SetAvailableHwaccels(validator.GetHwaccels()); + SetMediaEncoderVersion(validator); + threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null); } @@ -278,6 +285,21 @@ namespace MediaBrowser.MediaEncoding.Encoder _hwaccels = list.ToList(); } + public void SetAvailableFilters(IEnumerable list) + { + _filters = list.ToList(); + } + + public void SetAvailableFiltersWithOption(IDictionary dict) + { + _filtersWithOption = dict; + } + + public void SetMediaEncoderVersion(EncoderValidator validator) + { + _ffmpegVersion = validator.GetFFmpegVersion(); + } + public bool SupportsEncoder(string encoder) { return _encoders.Contains(encoder, StringComparer.OrdinalIgnoreCase); @@ -293,17 +315,26 @@ namespace MediaBrowser.MediaEncoding.Encoder return _hwaccels.Contains(hwaccel, StringComparer.OrdinalIgnoreCase); } - public bool SupportsFilter(string filter, string option) + public bool SupportsFilter(string filter) { - if (_ffmpegPath != null) + return _filters.Contains(filter, StringComparer.OrdinalIgnoreCase); + } + + public bool SupportsFilterWithOption(FilterOptionType option) + { + if (_filtersWithOption.TryGetValue((int)option, out var val)) { - var validator = new EncoderValidator(_logger, _ffmpegPath); - return validator.CheckFilter(filter, option); + return val; } return false; } + public Version GetMediaEncoderVersion() + { + return _ffmpegVersion; + } + public bool CanEncodeToAudioCodec(string codec) { if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index c9ad3c41e..d6799a170 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -739,6 +739,23 @@ namespace MediaBrowser.MediaEncoding.Probing stream.BitDepth = streamInfo.BitsPerRawSample; } + if (!stream.BitDepth.HasValue) + { + if (!string.IsNullOrEmpty(streamInfo.PixelFormat) + && streamInfo.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase)) + { + stream.BitDepth = 10; + } + + if (!string.IsNullOrEmpty(streamInfo.Profile) + && (streamInfo.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase) + || streamInfo.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase) + || streamInfo.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase))) + { + stream.BitDepth = 10; + } + } + // stream.IsAnamorphic = string.Equals(streamInfo.sample_aspect_ratio, "0:1", StringComparison.OrdinalIgnoreCase) || // string.Equals(stream.AspectRatio, "2.35:1", StringComparison.OrdinalIgnoreCase) || // string.Equals(stream.AspectRatio, "2.40:1", StringComparison.OrdinalIgnoreCase); -- cgit v1.2.3 From 2345646ff159f474588e24c29ab94a97a4f41e6d Mon Sep 17 00:00:00 2001 From: boolemancer Date: Sat, 14 Aug 2021 02:55:51 -0700 Subject: Fix explicit stream selection in MediaEncoder.ExtractImageInternal --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 12 ++---------- MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs | 18 +----------------- 2 files changed, 3 insertions(+), 27 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 412a95321..e0a8102f9 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -503,15 +503,7 @@ namespace MediaBrowser.MediaEncoding.Encoder { var inputArgument = GetInputArgument(inputFile, mediaSource); - if (isAudio) - { - if (imageStreamIndex.HasValue && imageStreamIndex.Value > 0) - { - // It seems for audio files we need to subtract 1 (for the audio stream??) - imageStreamIndex = imageStreamIndex.Value - 1; - } - } - else + if (!isAudio) { // The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter. try @@ -582,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _ => string.Empty }; - var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; + var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty; var enableHdrExtraction = allowTonemap && string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase); if (enableHdrExtraction) diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 30af6710a..453938be7 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -88,22 +88,6 @@ namespace MediaBrowser.Providers.MediaInfo if (imageStream != null) { - // Instead of using the raw stream index, we need to use nth video/embedded image stream - var videoIndex = -1; - foreach (var mediaStream in mediaStreams) - { - if (mediaStream.Type == MediaStreamType.Video || - mediaStream.Type == MediaStreamType.EmbeddedImage) - { - videoIndex++; - } - - if (mediaStream == imageStream) - { - break; - } - } - MediaSourceInfo mediaSource = new MediaSourceInfo { VideoType = item.VideoType, @@ -111,7 +95,7 @@ namespace MediaBrowser.Providers.MediaInfo Protocol = item.PathProtocol.Value, }; - extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, imageStream, videoIndex, cancellationToken).ConfigureAwait(false); + extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); } else { -- cgit v1.2.3 From 963ab2dab6a8c4dafadae61312b36ed5fbb1f323 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Sun, 4 Apr 2021 23:02:28 +0200 Subject: Simplify the way we choose our ffmpeg * no longer search $PATH * no longer require a full path * don't fall back --- Emby.Server.Implementations/ApplicationHost.cs | 1 - .../MediaEncoding/IMediaEncoder.cs | 6 -- .../Encoder/EncoderValidator.cs | 4 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 107 ++++++--------------- MediaBrowser.Model/System/SystemInfo.cs | 1 + .../EncoderValidatorTests.cs | 8 +- 6 files changed, 38 insertions(+), 89 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index bf7ddace2..0b5322f39 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -1099,7 +1099,6 @@ namespace Emby.Server.Implementations ServerName = FriendlyName, LocalAddress = GetSmartApiUrl(source), SupportsLibraryMonitor = true, - EncoderLocation = _mediaEncoder.EncoderLocation, SystemArchitecture = RuntimeInformation.OSArchitecture, PackageName = _startupOptions.PackageName }; diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index ff2456070..63308fa09 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -10,7 +10,6 @@ using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.System; namespace MediaBrowser.Controller.MediaEncoding { @@ -19,11 +18,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// public interface IMediaEncoder : ITranscoderSupport { - /// - /// Gets location of the discovered FFmpeg tool. - /// - FFmpegLocation EncoderLocation { get; } - /// /// Gets the encoder path. /// diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index f782e65bd..ef831ab82 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -12,8 +12,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { public class EncoderValidator { - private const string DefaultEncoderPath = "ffmpeg"; - private static readonly string[] _requiredDecoders = new[] { "h264", @@ -106,7 +104,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private readonly string _encoderPath; - public EncoderValidator(ILogger logger, string encoderPath = DefaultEncoderPath) + public EncoderValidator(ILogger logger, string encoderPath) { _logger = logger; _encoderPath = encoderPath; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 412a95321..f8ba78e46 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -23,7 +23,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using MediaBrowser.Model.System; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -69,7 +68,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private string _ffmpegPath = string.Empty; private string _ffprobePath; - private int threads; + private int _threads; public MediaEncoder( ILogger logger, @@ -89,9 +88,6 @@ namespace MediaBrowser.MediaEncoding.Encoder /// public string EncoderPath => _ffmpegPath; - /// - public FFmpegLocation EncoderLocation { get; private set; } - /// /// Run at startup or if the user removes a Custom path from transcode page. /// Sets global variables FFmpegPath. @@ -100,20 +96,23 @@ namespace MediaBrowser.MediaEncoding.Encoder public void SetFFmpegPath() { // 1) Custom path stored in config/encoding xml file under tag takes precedence - if (!ValidatePath(_configurationManager.GetEncodingOptions().EncoderAppPath, FFmpegLocation.Custom)) + var ffmpegPath = _configurationManager.GetEncodingOptions().EncoderAppPath; + if (string.IsNullOrEmpty(ffmpegPath)) { // 2) Check if the --ffmpeg CLI switch has been given - if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument)) + ffmpegPath = _startupOptionFFmpegPath; + if (string.IsNullOrEmpty(ffmpegPath)) { - // 3) Search system $PATH environment variable for valid FFmpeg - if (!ValidatePath(ExistsOnSystemPath("ffmpeg"), FFmpegLocation.System)) - { - EncoderLocation = FFmpegLocation.NotFound; - _ffmpegPath = null; - } + // 3) Check "ffmpeg" + ffmpegPath = "ffmpeg"; } } + if (!ValidatePath(ffmpegPath)) + { + _ffmpegPath = null; + } + // Write the FFmpeg path to the config/encoding.xml file as so it appears in UI var config = _configurationManager.GetEncodingOptions(); config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty; @@ -131,10 +130,10 @@ namespace MediaBrowser.MediaEncoding.Encoder SetAvailableDecoders(validator.GetDecoders()); SetAvailableEncoders(validator.GetEncoders()); SetAvailableHwaccels(validator.GetHwaccels()); - threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null); + _threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null); } - _logger.LogInformation("FFmpeg: {EncoderLocation}: {FfmpegPath}", EncoderLocation, _ffmpegPath ?? string.Empty); + _logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty); } /// @@ -153,15 +152,12 @@ namespace MediaBrowser.MediaEncoding.Encoder { throw new ArgumentException("Unexpected pathType value"); } - else if (string.IsNullOrWhiteSpace(path)) + + if (string.IsNullOrWhiteSpace(path)) { // User had cleared the custom path in UI newPath = string.Empty; } - else if (File.Exists(path)) - { - newPath = path; - } else if (Directory.Exists(path)) { // Given path is directory, so resolve down to filename @@ -169,7 +165,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } else { - throw new ResourceNotFoundException(); + newPath = path; } // Write the new ffmpeg path to the xml as @@ -184,37 +180,26 @@ namespace MediaBrowser.MediaEncoding.Encoder /// /// Validates the supplied FQPN to ensure it is a ffmpeg utility. - /// If checks pass, global variable FFmpegPath and EncoderLocation are updated. + /// If checks pass, global variable FFmpegPath is updated. /// /// FQPN to test. - /// Location (External, Custom, System) of tool. /// true if the version validation succeeded; otherwise, false. - private bool ValidatePath(string path, FFmpegLocation location) + private bool ValidatePath(string path) { - bool rc = false; - - if (!string.IsNullOrEmpty(path)) + if (string.IsNullOrEmpty(path)) { - if (File.Exists(path)) - { - rc = new EncoderValidator(_logger, path).ValidateVersion(); - - if (!rc) - { - _logger.LogWarning("FFmpeg: {Location}: Failed version check: {Path}", location, path); - } + return false; + } - _ffmpegPath = path; - EncoderLocation = location; - return true; - } - else - { - _logger.LogWarning("FFmpeg: {Location}: File not found: {Path}", location, path); - } + bool rc = new EncoderValidator(_logger, path).ValidateVersion(); + if (!rc) + { + _logger.LogWarning("FFmpeg: Failed version check: {Path}", path); + return false; } - return rc; + _ffmpegPath = path; + return true; } private string GetEncoderPathFromDirectory(string path, string filename, bool recursive = false) @@ -235,34 +220,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - /// - /// Search the system $PATH environment variable looking for given filename. - /// - /// The filename. - /// The full path to the file. - private string ExistsOnSystemPath(string fileName) - { - var inJellyfinPath = GetEncoderPathFromDirectory(AppContext.BaseDirectory, fileName, recursive: true); - if (!string.IsNullOrEmpty(inJellyfinPath)) - { - return inJellyfinPath; - } - - var values = Environment.GetEnvironmentVariable("PATH"); - - foreach (var path in values.Split(Path.PathSeparator)) - { - var candidatePath = GetEncoderPathFromDirectory(path, fileName); - - if (!string.IsNullOrEmpty(candidatePath)) - { - return candidatePath; - } - } - - return null; - } - public void SetAvailableEncoders(IEnumerable list) { _encoders = list.ToList(); @@ -394,7 +351,7 @@ namespace MediaBrowser.MediaEncoding.Encoder var args = extractChapters ? "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_chapters -show_format" : "{0} -i {1} -threads {2} -v warning -print_format json -show_streams -show_format"; - args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, threads).Trim(); + args = string.Format(CultureInfo.InvariantCulture, args, probeSizeArgument, inputPath, _threads).Trim(); var process = new Process { @@ -615,7 +572,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads); + var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads); if (offset.HasValue) { @@ -728,7 +685,7 @@ namespace MediaBrowser.MediaEncoding.Encoder Directory.CreateDirectory(targetDirectory); var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg"); - var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, threads); + var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads); if (!string.IsNullOrWhiteSpace(container)) { diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs index e45b2f33a..a82c1c8c0 100644 --- a/MediaBrowser.Model/System/SystemInfo.cs +++ b/MediaBrowser.Model/System/SystemInfo.cs @@ -133,6 +133,7 @@ namespace MediaBrowser.Model.System [Obsolete("This should be handled by the package manager")] public bool HasUpdateAvailable { get; set; } + [Obsolete("This isn't set correctly anymore")] public FFmpegLocation EncoderLocation { get; set; } public Architecture SystemArchitecture { get; set; } diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs index 39fd8afda..cc429b442 100644 --- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs +++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs @@ -9,12 +9,13 @@ namespace Jellyfin.MediaEncoding.Tests { public class EncoderValidatorTests { + private readonly EncoderValidator _encoderValidator = new EncoderValidator(new NullLogger(), "ffmpeg"); + [Theory] [ClassData(typeof(GetFFmpegVersionTestData))] public void GetFFmpegVersionTest(string versionOutput, Version? version) { - var val = new EncoderValidator(new NullLogger()); - Assert.Equal(version, val.GetFFmpegVersion(versionOutput)); + Assert.Equal(version, _encoderValidator.GetFFmpegVersion(versionOutput)); } [Theory] @@ -28,8 +29,7 @@ namespace Jellyfin.MediaEncoding.Tests [InlineData(EncoderValidatorTestsData.FFmpegGitUnknownOutput, false)] public void ValidateVersionInternalTest(string versionOutput, bool valid) { - var val = new EncoderValidator(new NullLogger()); - Assert.Equal(valid, val.ValidateVersionInternal(versionOutput)); + Assert.Equal(valid, _encoderValidator.ValidateVersionInternal(versionOutput)); } private class GetFFmpegVersionTestData : IEnumerable -- cgit v1.2.3 From 52585a865e921196c6e47fe18b19b63107398d8d Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 21 Sep 2021 13:28:34 +0200 Subject: Validate ffmpeg path --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a7bcaf544..4cbd1bbc8 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -165,14 +165,22 @@ namespace MediaBrowser.MediaEncoding.Encoder // User had cleared the custom path in UI newPath = string.Empty; } - else if (Directory.Exists(path)) - { - // Given path is directory, so resolve down to filename - newPath = GetEncoderPathFromDirectory(path, "ffmpeg"); - } else { - newPath = path; + if (Directory.Exists(path)) + { + // Given path is directory, so resolve down to filename + newPath = GetEncoderPathFromDirectory(path, "ffmpeg"); + } + else + { + newPath = path; + } + + if (!new EncoderValidator(_logger, newPath).ValidateVersion()) + { + throw new ResourceNotFoundException(); + } } // Write the new ffmpeg path to the xml as -- cgit v1.2.3 From 95f344722ce34dde75408bce4138fe47b7eef6d0 Mon Sep 17 00:00:00 2001 From: cvium Date: Sat, 11 Sep 2021 16:20:49 +0200 Subject: Don't set ffmpeg path from null to its Display value + add 404 --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4cbd1bbc8..057293b66 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -151,6 +151,16 @@ namespace MediaBrowser.MediaEncoding.Encoder /// The path type. public void UpdateEncoderPath(string path, string pathType) { + var config = _configurationManager.GetEncodingOptions(); + + // Filesystem may not be case insensitive, but EncoderAppPathDisplay should always point to a valid file? + if (string.IsNullOrEmpty(config.EncoderAppPath) + && string.Equals(config.EncoderAppPathDisplay, path, StringComparison.OrdinalIgnoreCase)) + { + _logger.LogDebug("Existing ffmpeg path is empty and the new path is the same as {EncoderAppPathDisplay}. Skipping", nameof(config.EncoderAppPathDisplay)); + return; + } + string newPath; _logger.LogInformation("Attempting to update encoder path to {Path}. pathType: {PathType}", path ?? string.Empty, pathType ?? string.Empty); @@ -183,9 +193,14 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + // Don't save an invalid path + if (!File.Exists(path)) + { + throw new FileNotFoundException(); + } + // Write the new ffmpeg path to the xml as // This ensures its not lost on next startup - var config = _configurationManager.GetEncodingOptions(); config.EncoderAppPath = newPath; _configurationManager.SaveConfiguration("encoding", config); -- cgit v1.2.3 From a353081ea33b3f635e9472371f228bab63669347 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 21 Sep 2021 22:54:11 +0200 Subject: Update MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 6 ------ 1 file changed, 6 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 057293b66..22ed9a8eb 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -193,12 +193,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - // Don't save an invalid path - if (!File.Exists(path)) - { - throw new FileNotFoundException(); - } - // Write the new ffmpeg path to the xml as // This ensures its not lost on next startup config.EncoderAppPath = newPath; -- cgit v1.2.3 From 9234e5bf80194e45acac25c60cb76f401bffaf96 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 26 Sep 2021 08:14:36 -0600 Subject: Remove all instances of en-US culture --- Emby.Dlna/Didl/DidlBuilder.cs | 28 +++++++-------- Emby.Dlna/Eventing/DlnaEventManager.cs | 8 ++--- Emby.Dlna/PlayTo/Device.cs | 12 +++---- Emby.Dlna/PlayTo/PlayToController.cs | 8 ++--- Emby.Dlna/PlayTo/SsdpHttpClient.cs | 8 ++--- Emby.Dlna/Server/DescriptionXmlBuilder.cs | 5 ++- .../MediaEncoder/EncodingManager.cs | 3 +- Jellyfin.Api/Controllers/ImageController.cs | 2 +- Jellyfin.Api/Controllers/SubtitleController.cs | 2 +- MediaBrowser.Controller/Entities/Year.cs | 4 +-- .../MediaEncoding/EncodingHelper.cs | 42 +++++++++++----------- MediaBrowser.Controller/MediaEncoding/JobLogger.cs | 11 +++--- .../Images/LocalImageProvider.cs | 4 +-- .../Parsers/BaseItemXmlParser.cs | 8 ++--- MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs | 12 +++---- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 11 ++---- .../Probing/ProbeResultNormalizer.cs | 27 +++++++------- .../Globalization/ILocalizationManager.cs | 4 +-- MediaBrowser.Providers/Manager/ImageSaver.cs | 18 +++++----- .../Plugins/Omdb/OmdbProvider.cs | 13 ++++--- MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs | 10 +++--- .../Parsers/EpisodeNfoParser.cs | 10 +++--- .../Savers/EpisodeNfoSaver.cs | 18 +++++----- 23 files changed, 116 insertions(+), 152 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs index c00078499..0a84f30c4 100644 --- a/Emby.Dlna/Didl/DidlBuilder.cs +++ b/Emby.Dlna/Didl/DidlBuilder.cs @@ -41,8 +41,6 @@ namespace Emby.Dlna.Didl private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/"; private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/"; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly DeviceProfile _profile; private readonly IImageProcessor _imageProcessor; private readonly string _serverAddress; @@ -317,7 +315,7 @@ namespace Emby.Dlna.Didl if (mediaSource.RunTimeTicks.HasValue) { - writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); + writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture)); } if (filter.Contains("res@size")) @@ -328,7 +326,7 @@ namespace Emby.Dlna.Didl if (size.HasValue) { - writer.WriteAttributeString("size", size.Value.ToString(_usCulture)); + writer.WriteAttributeString("size", size.Value.ToString(CultureInfo.InvariantCulture)); } } } @@ -342,7 +340,7 @@ namespace Emby.Dlna.Didl if (targetChannels.HasValue) { - writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); + writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(CultureInfo.InvariantCulture)); } if (filter.Contains("res@resolution")) @@ -361,12 +359,12 @@ namespace Emby.Dlna.Didl if (targetSampleRate.HasValue) { - writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); + writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(CultureInfo.InvariantCulture)); } if (totalBitrate.HasValue) { - writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(_usCulture)); + writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(CultureInfo.InvariantCulture)); } var mediaProfile = _profile.GetVideoMediaProfile( @@ -552,7 +550,7 @@ namespace Emby.Dlna.Didl if (mediaSource.RunTimeTicks.HasValue) { - writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture)); + writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture)); } if (filter.Contains("res@size")) @@ -563,7 +561,7 @@ namespace Emby.Dlna.Didl if (size.HasValue) { - writer.WriteAttributeString("size", size.Value.ToString(_usCulture)); + writer.WriteAttributeString("size", size.Value.ToString(CultureInfo.InvariantCulture)); } } } @@ -575,17 +573,17 @@ namespace Emby.Dlna.Didl if (targetChannels.HasValue) { - writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture)); + writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(CultureInfo.InvariantCulture)); } if (targetSampleRate.HasValue) { - writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture)); + writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(CultureInfo.InvariantCulture)); } if (targetAudioBitrate.HasValue) { - writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture)); + writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(CultureInfo.InvariantCulture)); } var mediaProfile = _profile.GetAudioMediaProfile( @@ -639,7 +637,7 @@ namespace Emby.Dlna.Didl writer.WriteAttributeString("restricted", "1"); writer.WriteAttributeString("searchable", "1"); - writer.WriteAttributeString("childCount", childCount.ToString(_usCulture)); + writer.WriteAttributeString("childCount", childCount.ToString(CultureInfo.InvariantCulture)); var clientId = GetClientId(folder, stubType); @@ -931,11 +929,11 @@ namespace Emby.Dlna.Didl if (item.IndexNumber.HasValue) { - AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp); + AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), NsUpnp); if (item is Episode) { - AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp); + AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), NsUpnp); } } } diff --git a/Emby.Dlna/Eventing/DlnaEventManager.cs b/Emby.Dlna/Eventing/DlnaEventManager.cs index b39bd5ce9..d17e23871 100644 --- a/Emby.Dlna/Eventing/DlnaEventManager.cs +++ b/Emby.Dlna/Eventing/DlnaEventManager.cs @@ -26,8 +26,6 @@ namespace Emby.Dlna.Eventing private readonly ILogger _logger; private readonly IHttpClientFactory _httpClientFactory; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; @@ -83,7 +81,7 @@ namespace Emby.Dlna.Eventing if (!string.IsNullOrEmpty(header)) { // Starts with SECOND- - if (int.TryParse(header.AsSpan().RightPart('-'), NumberStyles.Integer, _usCulture, out var val)) + if (int.TryParse(header.AsSpan().RightPart('-'), NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return val; } @@ -106,7 +104,7 @@ namespace Emby.Dlna.Eventing var response = new EventSubscriptionResponse(string.Empty, "text/plain"); response.Headers["SID"] = subscriptionId; - response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString; + response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(CultureInfo.InvariantCulture)) : requestedTimeoutString; return response; } @@ -163,7 +161,7 @@ namespace Emby.Dlna.Eventing options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType); options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange"); options.Headers.TryAddWithoutValidation("SID", subscription.Id); - options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(_usCulture)); + options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(CultureInfo.InvariantCulture)); try { diff --git a/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs index 11fcd81cf..0b2288000 100644 --- a/Emby.Dlna/PlayTo/Device.cs +++ b/Emby.Dlna/PlayTo/Device.cs @@ -20,8 +20,6 @@ namespace Emby.Dlna.PlayTo { public class Device : IDisposable { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - private readonly IHttpClientFactory _httpClientFactory; private readonly ILogger _logger; @@ -640,7 +638,7 @@ namespace Emby.Dlna.PlayTo return; } - Volume = int.Parse(volumeValue, UsCulture); + Volume = int.Parse(volumeValue, CultureInfo.InvariantCulture); if (Volume > 0) { @@ -842,7 +840,7 @@ namespace Emby.Dlna.PlayTo if (!string.IsNullOrWhiteSpace(duration) && !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) { - Duration = TimeSpan.Parse(duration, UsCulture); + Duration = TimeSpan.Parse(duration, CultureInfo.InvariantCulture); } else { @@ -854,7 +852,7 @@ namespace Emby.Dlna.PlayTo if (!string.IsNullOrWhiteSpace(position) && !string.Equals(position, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase)) { - Position = TimeSpan.Parse(position, UsCulture); + Position = TimeSpan.Parse(position, CultureInfo.InvariantCulture); } var track = result.Document.Descendants("TrackMetaData").FirstOrDefault(); @@ -1194,8 +1192,8 @@ namespace Emby.Dlna.PlayTo var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")); var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")); - var widthValue = int.Parse(width, NumberStyles.Integer, UsCulture); - var heightValue = int.Parse(height, NumberStyles.Integer, UsCulture); + var widthValue = int.Parse(width, NumberStyles.Integer, CultureInfo.InvariantCulture); + var heightValue = int.Parse(height, NumberStyles.Integer, CultureInfo.InvariantCulture); return new DeviceIcon { diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs index 0e49fd2c0..f25d8017e 100644 --- a/Emby.Dlna/PlayTo/PlayToController.cs +++ b/Emby.Dlna/PlayTo/PlayToController.cs @@ -30,8 +30,6 @@ namespace Emby.Dlna.PlayTo { public class PlayToController : ISessionController, IDisposable { - private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); - private readonly SessionInfo _session; private readonly ISessionManager _sessionManager; private readonly ILibraryManager _libraryManager; @@ -716,7 +714,7 @@ namespace Emby.Dlna.PlayTo case GeneralCommandType.SetAudioStreamIndex: if (command.Arguments.TryGetValue("Index", out string index)) { - if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) + if (int.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return SetAudioStreamIndex(val); } @@ -728,7 +726,7 @@ namespace Emby.Dlna.PlayTo case GeneralCommandType.SetSubtitleStreamIndex: if (command.Arguments.TryGetValue("Index", out index)) { - if (int.TryParse(index, NumberStyles.Integer, _usCulture, out var val)) + if (int.TryParse(index, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val)) { return SetSubtitleStreamIndex(val); } @@ -740,7 +738,7 @@ namespace Emby.Dlna.PlayTo case GeneralCommandType.SetVolume: if (command.Arguments.TryGetValue("Volume", out string vol)) { - if (int.TryParse(vol, NumberStyles.Integer, _usCulture, out var volume)) + if (int.TryParse(vol, NumberStyles.Integer, CultureInfo.InvariantCulture, out var volume)) { return _device.SetVolume(volume, cancellationToken); } diff --git a/Emby.Dlna/PlayTo/SsdpHttpClient.cs b/Emby.Dlna/PlayTo/SsdpHttpClient.cs index 4b92fbff4..cade7b4c2 100644 --- a/Emby.Dlna/PlayTo/SsdpHttpClient.cs +++ b/Emby.Dlna/PlayTo/SsdpHttpClient.cs @@ -20,8 +20,6 @@ namespace Emby.Dlna.PlayTo private const string USERAGENT = "Microsoft-Windows/6.2 UPnP/1.0 Microsoft-DLNA DLNADOC/1.50"; private const string FriendlyName = "Jellyfin"; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly IHttpClientFactory _httpClientFactory; public SsdpHttpClient(IHttpClientFactory httpClientFactory) @@ -80,10 +78,10 @@ namespace Emby.Dlna.PlayTo { using var options = new HttpRequestMessage(new HttpMethod("SUBSCRIBE"), url); options.Headers.UserAgent.ParseAdd(USERAGENT); - options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(_usCulture)); - options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(_usCulture) + ">"); + options.Headers.TryAddWithoutValidation("HOST", ip + ":" + port.ToString(CultureInfo.InvariantCulture)); + options.Headers.TryAddWithoutValidation("CALLBACK", "<" + localIp + ":" + eventport.ToString(CultureInfo.InvariantCulture) + ">"); options.Headers.TryAddWithoutValidation("NT", "upnp:event"); - options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(_usCulture)); + options.Headers.TryAddWithoutValidation("TIMEOUT", "Second-" + timeOut.ToString(CultureInfo.InvariantCulture)); using var response = await _httpClientFactory.CreateClient(NamedClient.Default) .SendAsync(options, HttpCompletionOption.ResponseHeadersRead) diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs index 09525aae4..80a45f2b2 100644 --- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs +++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs @@ -15,7 +15,6 @@ namespace Emby.Dlna.Server { private readonly DeviceProfile _profile; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly string _serverUdn; private readonly string _serverAddress; private readonly string _serverName; @@ -193,10 +192,10 @@ namespace Emby.Dlna.Server .Append(SecurityElement.Escape(icon.MimeType ?? string.Empty)) .Append(""); builder.Append("") - .Append(SecurityElement.Escape(icon.Width.ToString(_usCulture))) + .Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture))) .Append(""); builder.Append("") - .Append(SecurityElement.Escape(icon.Height.ToString(_usCulture))) + .Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture))) .Append(""); builder.Append("") .Append(SecurityElement.Escape(icon.Depth ?? string.Empty)) diff --git a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 8aaa1f7bb..ac6606d39 100644 --- a/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -23,7 +23,6 @@ namespace Emby.Server.Implementations.MediaEncoder { public class EncodingManager : IEncodingManager { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private readonly IMediaEncoder _encoder; @@ -193,7 +192,7 @@ namespace Emby.Server.Implementations.MediaEncoder private string GetChapterImagePath(Video video, long chapterPositionTicks) { - var filename = video.DateModified.Ticks.ToString(_usCulture) + "_" + chapterPositionTicks.ToString(_usCulture) + ".jpg"; + var filename = video.DateModified.Ticks.ToString(CultureInfo.InvariantCulture) + "_" + chapterPositionTicks.ToString(CultureInfo.InvariantCulture) + ".jpg"; return Path.Combine(GetChapterImagesPath(video), filename); } diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index b1c860d61..86933074d 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -2007,7 +2007,7 @@ namespace Jellyfin.Api.Controllers Response.Headers.Add(HeaderNames.CacheControl, "public"); } - Response.Headers.Add(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", new CultureInfo("en-US", false))); + Response.Headers.Add(HeaderNames.LastModified, dateImageModified.ToUniversalTime().ToString("ddd, dd MMM yyyy HH:mm:ss \"GMT\"", CultureInfo.InvariantCulture)); // if the image was not modified since "ifModifiedSinceHeader"-header, return a HTTP status code 304 not modified if (!(dateImageModified > ifModifiedSinceHeader) && cacheDuration.HasValue) diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs index 11f67ee89..1849dd047 100644 --- a/Jellyfin.Api/Controllers/SubtitleController.cs +++ b/Jellyfin.Api/Controllers/SubtitleController.cs @@ -376,7 +376,7 @@ namespace Jellyfin.Api.Controllers var endPositionTicks = Math.Min(runtime, positionTicks + segmentLengthTicks); var url = string.Format( - CultureInfo.CurrentCulture, + CultureInfo.InvariantCulture, "stream.vtt?CopyTimestamps=true&AddVttTimeMap=true&StartPositionTicks={0}&EndPositionTicks={1}&api_key={2}", positionTicks.ToString(CultureInfo.InvariantCulture), endPositionTicks.ToString(CultureInfo.InvariantCulture), diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index 0853200dd..afdaf448b 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -57,9 +57,7 @@ namespace MediaBrowser.Controller.Entities public IList GetTaggedItems(InternalItemsQuery query) { - var usCulture = new CultureInfo("en-US"); - - if (!int.TryParse(Name, NumberStyles.Integer, usCulture, out var year)) + if (!int.TryParse(Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year)) { return new List(); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index bdb379332..5715194b8 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -21,8 +21,6 @@ namespace MediaBrowser.Controller.MediaEncoding { public class EncodingHelper { - private static readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly IMediaEncoder _mediaEncoder; private readonly ISubtitleEncoder _subtitleEncoder; @@ -816,7 +814,7 @@ namespace MediaBrowser.Controller.MediaEncoding public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level) { - if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel)) + if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double requestLevel)) { if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)) @@ -911,7 +909,7 @@ namespace MediaBrowser.Controller.MediaEncoding CultureInfo.InvariantCulture, "subtitles='{0}:si={1}'{2}", _mediaEncoder.EscapeSubtitleFilterPath(mediaPath), - state.InternalSubtitleStreamOffset.ToString(_usCulture), + state.InternalSubtitleStreamOffset.ToString(CultureInfo.InvariantCulture), // fallbackFontParam, setPtsParam); } @@ -1217,7 +1215,7 @@ namespace MediaBrowser.Controller.MediaEncoding param += string.Format( CultureInfo.InvariantCulture, " -speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}", - profileScore.ToString(_usCulture), + profileScore.ToString(CultureInfo.InvariantCulture), crf, qmin, qmax); @@ -1289,7 +1287,7 @@ namespace MediaBrowser.Controller.MediaEncoding var framerate = GetFramerateParam(state); if (framerate.HasValue) { - param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(_usCulture)); + param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.InvariantCulture)); } var targetVideoCodec = state.ActualOutputVideoCodec; @@ -1393,7 +1391,7 @@ namespace MediaBrowser.Controller.MediaEncoding else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)) { // hevc_qsv use -level 51 instead of -level 153. - if (double.TryParse(level, NumberStyles.Any, _usCulture, out double hevcLevel)) + if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double hevcLevel)) { param += " -level " + (hevcLevel / 3); } @@ -1555,7 +1553,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a specific level was requested, the source must match or be less than var level = state.GetRequestedLevel(videoStream.Codec); if (!string.IsNullOrEmpty(level) - && double.TryParse(level, NumberStyles.Any, _usCulture, out var requestLevel)) + && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var requestLevel)) { if (!videoStream.Level.HasValue) { @@ -1803,7 +1801,7 @@ namespace MediaBrowser.Controller.MediaEncoding && state.AudioStream.Channels.Value > 5 && !encodingOptions.DownMixAudioBoost.Equals(1)) { - filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture)); + filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture)); } var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive; @@ -2434,8 +2432,8 @@ namespace MediaBrowser.Controller.MediaEncoding { if (isExynosV4L2) { - var widthParam = requestedWidth.Value.ToString(_usCulture); - var heightParam = requestedHeight.Value.ToString(_usCulture); + var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); + var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); filters.Add( string.Format( @@ -2453,8 +2451,8 @@ namespace MediaBrowser.Controller.MediaEncoding // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size else if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue) { - var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture); - var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture); + var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); + var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -2486,7 +2484,7 @@ namespace MediaBrowser.Controller.MediaEncoding } else { - var widthParam = requestedWidth.Value.ToString(_usCulture); + var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture); filters.Add( string.Format( @@ -2499,7 +2497,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a fixed height was requested else if (requestedHeight.HasValue) { - var heightParam = requestedHeight.Value.ToString(_usCulture); + var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -2522,7 +2520,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a max width was requested else if (requestedMaxWidth.HasValue) { - var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture); + var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -2545,7 +2543,7 @@ namespace MediaBrowser.Controller.MediaEncoding // If a max height was requested else if (requestedMaxHeight.HasValue) { - var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture); + var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); if (isExynosV4L2) { @@ -4122,12 +4120,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue) { - args += " -ab " + bitrate.Value.ToString(_usCulture); + args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); } if (state.OutputAudioSampleRate.HasValue) { - args += " -ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture); + args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture); } args += GetAudioFilterParam(state, encodingOptions); @@ -4143,12 +4141,12 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue) { - audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(_usCulture)); + audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture)); } if (state.OutputAudioChannels.HasValue) { - audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(_usCulture)); + audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); } // opus will fail on 44100 @@ -4156,7 +4154,7 @@ namespace MediaBrowser.Controller.MediaEncoding { if (state.OutputAudioSampleRate.HasValue) { - audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture)); + audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture)); } } diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs index c4ddc5618..933f440ac 100644 --- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.MediaEncoding { public class JobLogger { - private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US")); private readonly ILogger _logger; public JobLogger(ILogger logger) @@ -87,7 +86,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var rate = parts[i + 1]; - if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val)) + if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { framerate = val; } @@ -96,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var rate = part.Split('=', 2)[^1]; - if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val)) + if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { framerate = val; } @@ -106,7 +105,7 @@ namespace MediaBrowser.Controller.MediaEncoding { var time = part.Split('=', 2)[^1]; - if (TimeSpan.TryParse(time, _usCulture, out var val)) + if (TimeSpan.TryParse(time, CultureInfo.InvariantCulture, out var val)) { var currentMs = startMs + val.TotalMilliseconds; @@ -128,7 +127,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (scale.HasValue) { - if (long.TryParse(size, NumberStyles.Any, _usCulture, out var val)) + if (long.TryParse(size, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { bytesTranscoded = val * scale.Value; } @@ -147,7 +146,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (scale.HasValue) { - if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val)) + if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) { bitRate = (int)Math.Ceiling(val * scale.Value); } diff --git a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs index b7398880e..988581df9 100644 --- a/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs +++ b/MediaBrowser.LocalMetadata/Images/LocalImageProvider.cs @@ -60,8 +60,6 @@ namespace MediaBrowser.LocalMetadata.Images private readonly IFileSystem _fileSystem; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - /// /// Initializes a new instance of the class. /// @@ -434,7 +432,7 @@ namespace MediaBrowser.LocalMetadata.Images var seasonMarker = seasonNumber.Value == 0 ? "-specials" - : seasonNumber.Value.ToString("00", _usCulture); + : seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture); // Get this one directly from the file system since we have to go up a level if (!string.Equals(prefix, seasonMarker, StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 7c9e681d6..5a36c1663 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -21,8 +21,6 @@ namespace MediaBrowser.LocalMetadata.Parsers public class BaseItemXmlParser where T : BaseItem { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private Dictionary? _validProviderIds; /// @@ -180,7 +178,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrEmpty(text)) { - if (float.TryParse(text, NumberStyles.Any, _usCulture, out var value)) + if (float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { item.CriticRating = value; } @@ -332,7 +330,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(text)) { - if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, _usCulture, out var runtime)) + if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, CultureInfo.InvariantCulture, out var runtime)) { item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } @@ -1095,7 +1093,7 @@ namespace MediaBrowser.LocalMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (int.TryParse(val, NumberStyles.Integer, _usCulture, out var intVal)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intVal)) { sortOrder = intVal; } diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs index 6a3896eb6..6f66fd61b 100644 --- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs @@ -25,8 +25,6 @@ namespace MediaBrowser.LocalMetadata.Savers /// public const string DateAddedFormat = "yyyy-MM-dd HH:mm:ss"; - private static readonly CultureInfo _usCulture = new CultureInfo("en-US"); - /// /// Initializes a new instance of the class. /// @@ -205,7 +203,7 @@ namespace MediaBrowser.LocalMetadata.Savers if (item.CriticRating.HasValue) { - writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(_usCulture)); + writer.WriteElementString("CriticRating", item.CriticRating.Value.ToString(CultureInfo.InvariantCulture)); } if (!string.IsNullOrEmpty(item.Overview)) @@ -289,12 +287,12 @@ namespace MediaBrowser.LocalMetadata.Savers if (item.CommunityRating.HasValue) { - writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(_usCulture)); + writer.WriteElementString("Rating", item.CommunityRating.Value.ToString(CultureInfo.InvariantCulture)); } if (item.ProductionYear.HasValue && item is not Person) { - writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(_usCulture)); + writer.WriteElementString("ProductionYear", item.ProductionYear.Value.ToString(CultureInfo.InvariantCulture)); } if (item is IHasAspectRatio hasAspectRatio) @@ -322,7 +320,7 @@ namespace MediaBrowser.LocalMetadata.Savers { var timespan = TimeSpan.FromTicks(runTimeTicks.Value); - writer.WriteElementString("RunningTime", Math.Floor(timespan.TotalMinutes).ToString(_usCulture)); + writer.WriteElementString("RunningTime", Math.Floor(timespan.TotalMinutes).ToString(CultureInfo.InvariantCulture)); } if (item.ProviderIds != null) @@ -395,7 +393,7 @@ namespace MediaBrowser.LocalMetadata.Savers if (person.SortOrder.HasValue) { - writer.WriteElementString("SortOrder", person.SortOrder.Value.ToString(_usCulture)); + writer.WriteElementString("SortOrder", person.SortOrder.Value.ToString(CultureInfo.InvariantCulture)); } writer.WriteEndElement(); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4cbd1bbc8..06fe95ce8 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -43,11 +43,6 @@ namespace MediaBrowser.MediaEncoding.Encoder /// internal const int DefaultHdrImageExtractionTimeout = 20000; - /// - /// The us culture. - /// - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - private readonly ILogger _logger; private readonly IServerConfigurationManager _configurationManager; private readonly IFileSystem _fileSystem; @@ -687,7 +682,7 @@ namespace MediaBrowser.MediaEncoding.Encoder public string GetTimeParameter(TimeSpan time) { - return time.ToString(@"hh\:mm\:ss\.fff", _usCulture); + return time.ToString(@"hh\:mm\:ss\.fff", CultureInfo.InvariantCulture); } public async Task ExtractVideoImagesOnInterval( @@ -704,11 +699,11 @@ namespace MediaBrowser.MediaEncoding.Encoder { var inputArgument = GetInputArgument(inputFile, mediaSource); - var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(_usCulture); + var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(CultureInfo.InvariantCulture); if (maxWidth.HasValue) { - var maxWidthParam = maxWidth.Value.ToString(_usCulture); + var maxWidthParam = maxWidth.Value.ToString(CultureInfo.InvariantCulture); vf += string.Format(CultureInfo.InvariantCulture, ",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam); } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 26f629a31..9ed6c264e 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -30,7 +30,6 @@ namespace MediaBrowser.MediaEncoding.Probing private static readonly Regex _performerPattern = new (@"(?.*) \((?.*)\)"); - private readonly CultureInfo _usCulture = new ("en-US"); private readonly ILogger _logger; private readonly ILocalizationManager _localization; @@ -83,7 +82,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrEmpty(data.Format.BitRate)) { - if (int.TryParse(data.Format.BitRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(data.Format.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { info.Bitrate = value; } @@ -191,7 +190,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (data.Format != null && !string.IsNullOrEmpty(data.Format.Duration)) { - info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.Format.Duration, _usCulture)).Ticks; + info.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.Format.Duration, CultureInfo.InvariantCulture)).Ticks; } FetchWtvInfo(info, data); @@ -673,7 +672,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrEmpty(streamInfo.SampleRate)) { - if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { stream.SampleRate = value; } @@ -802,7 +801,7 @@ namespace MediaBrowser.MediaEncoding.Probing if (!string.IsNullOrEmpty(streamInfo.BitRate)) { - if (int.TryParse(streamInfo.BitRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(streamInfo.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { bitrate = value; } @@ -815,7 +814,7 @@ namespace MediaBrowser.MediaEncoding.Probing && (stream.Type == MediaStreamType.Video || (isAudio && stream.Type == MediaStreamType.Audio))) { // If the stream info doesn't have a bitrate get the value from the media format info - if (int.TryParse(formatInfo.BitRate, NumberStyles.Any, _usCulture, out var value)) + if (int.TryParse(formatInfo.BitRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { bitrate = value; } @@ -921,8 +920,8 @@ namespace MediaBrowser.MediaEncoding.Probing var parts = (original ?? string.Empty).Split(':'); if (!(parts.Length == 2 && - int.TryParse(parts[0], NumberStyles.Any, _usCulture, out var width) && - int.TryParse(parts[1], NumberStyles.Any, _usCulture, out var height) && + int.TryParse(parts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var width) && + int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var height) && width > 0 && height > 0)) { @@ -1008,11 +1007,11 @@ namespace MediaBrowser.MediaEncoding.Probing if (parts.Length == 2) { - result = float.Parse(parts[0], _usCulture) / float.Parse(parts[1], _usCulture); + result = float.Parse(parts[0], CultureInfo.InvariantCulture) / float.Parse(parts[1], CultureInfo.InvariantCulture); } else { - result = float.Parse(parts[0], _usCulture); + result = float.Parse(parts[0], CultureInfo.InvariantCulture); } return float.IsNaN(result) ? null : result; @@ -1039,7 +1038,7 @@ namespace MediaBrowser.MediaEncoding.Probing // If we got something, parse it if (!string.IsNullOrEmpty(duration)) { - data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks; + data.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, CultureInfo.InvariantCulture)).Ticks; } } @@ -1101,7 +1100,7 @@ namespace MediaBrowser.MediaEncoding.Probing return; } - info.Size = string.IsNullOrEmpty(data.Format.Size) ? null : long.Parse(data.Format.Size, _usCulture); + info.Size = string.IsNullOrEmpty(data.Format.Size) ? null : long.Parse(data.Format.Size, CultureInfo.InvariantCulture); } private void SetAudioInfoFromTags(MediaInfo audio, IReadOnlyDictionary tags) @@ -1144,7 +1143,7 @@ namespace MediaBrowser.MediaEncoding.Probing { Name = match.Groups["name"].Value, Type = PersonType.Actor, - Role = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value) + Role = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value) }); } } @@ -1443,7 +1442,7 @@ namespace MediaBrowser.MediaEncoding.Probing .ToArray(); } - if (tags.TryGetValue("WM/OriginalReleaseTime", out var year) && int.TryParse(year, NumberStyles.Integer, _usCulture, out var parsedYear)) + if (tags.TryGetValue("WM/OriginalReleaseTime", out var year) && int.TryParse(year, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedYear)) { video.ProductionYear = parsedYear; } diff --git a/MediaBrowser.Model/Globalization/ILocalizationManager.cs b/MediaBrowser.Model/Globalization/ILocalizationManager.cs index b213e7aa0..406d32cde 100644 --- a/MediaBrowser.Model/Globalization/ILocalizationManager.cs +++ b/MediaBrowser.Model/Globalization/ILocalizationManager.cs @@ -56,10 +56,10 @@ namespace MediaBrowser.Model.Globalization IEnumerable GetLocalizationOptions(); /// - /// Returns the correct for the given language. + /// Returns the correct for the given language. /// /// The language. - /// The correct for the given language. + /// The correct for the given language. CultureDto? FindLanguageInfo(string language); } } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 7d259a9d3..4b05edd67 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -29,8 +29,6 @@ namespace MediaBrowser.Providers.Manager /// public class ImageSaver { - private static readonly CultureInfo UsCulture = new CultureInfo("en-US"); - /// /// The _config. /// @@ -377,7 +375,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-landscape" + extension; @@ -400,7 +398,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-banner" + extension; @@ -495,12 +493,12 @@ namespace MediaBrowser.Providers.Manager var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList(); var current = 1; - while (filenames.Contains(numberedIndexPrefix + current.ToString(UsCulture), StringComparer.OrdinalIgnoreCase)) + while (filenames.Contains(numberedIndexPrefix + current.ToString(CultureInfo.InvariantCulture), StringComparer.OrdinalIgnoreCase)) { current++; } - return numberedIndexPrefix + current.ToString(UsCulture); + return numberedIndexPrefix + current.ToString(CultureInfo.InvariantCulture); } /// @@ -539,7 +537,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-fanart" + extension; @@ -556,7 +554,7 @@ namespace MediaBrowser.Providers.Manager if (item.IsInMixedFolder) { - return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) }; + return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(CultureInfo.InvariantCulture), extension) }; } var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", outputIndex); @@ -568,7 +566,7 @@ namespace MediaBrowser.Providers.Manager if (EnableExtraThumbsDuplication) { - list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)); + list.Add(Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(CultureInfo.InvariantCulture) + extension)); } return list.ToArray(); @@ -582,7 +580,7 @@ namespace MediaBrowser.Providers.Manager var seasonMarker = season.IndexNumber.Value == 0 ? "-specials" - : season.IndexNumber.Value.ToString("00", UsCulture); + : season.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture); var imageFilename = "season" + seasonMarker + "-poster" + extension; diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index 479ae0f39..b2bc58eea 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs @@ -26,7 +26,6 @@ namespace MediaBrowser.Providers.Plugins.Omdb private readonly IFileSystem _fileSystem; private readonly IServerConfigurationManager _configurationManager; private readonly IHttpClientFactory _httpClientFactory; - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); private readonly IApplicationHost _appHost; private readonly JsonSerializerOptions _jsonOptions; @@ -79,7 +78,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 - && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year) + && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, CultureInfo.InvariantCulture, out var year) && year >= 0) { item.ProductionYear = year; @@ -93,14 +92,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.imdbVotes) - && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount) + && int.TryParse(result.imdbVotes, NumberStyles.Number, CultureInfo.InvariantCulture, out var voteCount) && voteCount >= 0) { // item.VoteCount = voteCount; } if (!string.IsNullOrEmpty(result.imdbRating) - && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out var imdbRating) + && float.TryParse(result.imdbRating, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbRating) && imdbRating >= 0) { item.CommunityRating = imdbRating; @@ -191,7 +190,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.Year) && result.Year.Length >= 4 - && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, _usCulture, out var year) + && int.TryParse(result.Year.AsSpan().Slice(0, 4), NumberStyles.Number, CultureInfo.InvariantCulture, out var year) && year >= 0) { item.ProductionYear = year; @@ -205,14 +204,14 @@ namespace MediaBrowser.Providers.Plugins.Omdb } if (!string.IsNullOrEmpty(result.imdbVotes) - && int.TryParse(result.imdbVotes, NumberStyles.Number, _usCulture, out var voteCount) + && int.TryParse(result.imdbVotes, NumberStyles.Number, CultureInfo.InvariantCulture, out var voteCount) && voteCount >= 0) { // item.VoteCount = voteCount; } if (!string.IsNullOrEmpty(result.imdbRating) - && float.TryParse(result.imdbRating, NumberStyles.Any, _usCulture, out var imdbRating) + && float.TryParse(result.imdbRating, NumberStyles.Any, CultureInfo.InvariantCulture, out var imdbRating) && imdbRating >= 0) { item.CommunityRating = imdbRating; diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index f7f4ea065..9d558b6ce 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -58,8 +58,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers _directoryService = directoryService; } - protected CultureInfo UsCulture { get; } = new CultureInfo("en-US"); - /// /// Gets the logger. /// @@ -309,7 +307,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrEmpty(text)) { - if (float.TryParse(text, NumberStyles.Any, UsCulture, out var value)) + if (float.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out var value)) { item.CriticRating = value; } @@ -370,7 +368,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers var val = reader.ReadElementContentAsString(); if (!string.IsNullOrWhiteSpace(val) && userData != null) { - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var count)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var count)) { userData.PlayCount = count; } @@ -475,7 +473,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(text)) { - if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, UsCulture, out var runtime)) + if (int.TryParse(text.AsSpan().LeftPart(' '), NumberStyles.Integer, CultureInfo.InvariantCulture, out var runtime)) { item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks; } @@ -1265,7 +1263,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var intVal)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var intVal)) { sortOrder = intVal; } diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index 3a305024e..d2f349ad7 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -163,7 +163,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeEpisodeNumber = rval; } @@ -179,7 +179,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsAfterSeasonNumber = rval; } @@ -195,7 +195,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeSeasonNumber = rval; } @@ -211,7 +211,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeSeasonNumber = rval; } @@ -227,7 +227,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers if (!string.IsNullOrWhiteSpace(val)) { // int.TryParse is local aware, so it can be problematic, force us culture - if (int.TryParse(val, NumberStyles.Integer, UsCulture, out var rval)) + if (int.TryParse(val, NumberStyles.Integer, CultureInfo.InvariantCulture, out var rval)) { item.AirsBeforeEpisodeNumber = rval; } diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs index 62f80e81b..2cd3fdf02 100644 --- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs @@ -17,8 +17,6 @@ namespace MediaBrowser.XbmcMetadata.Savers /// public class EpisodeNfoSaver : BaseNfoSaver { - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - /// /// Initializes a new instance of the class. /// @@ -60,17 +58,17 @@ namespace MediaBrowser.XbmcMetadata.Savers if (episode.IndexNumber.HasValue) { - writer.WriteElementString("episode", episode.IndexNumber.Value.ToString(_usCulture)); + writer.WriteElementString("episode", episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.IndexNumberEnd.HasValue) { - writer.WriteElementString("episodenumberend", episode.IndexNumberEnd.Value.ToString(_usCulture)); + writer.WriteElementString("episodenumberend", episode.IndexNumberEnd.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.ParentIndexNumber.HasValue) { - writer.WriteElementString("season", episode.ParentIndexNumber.Value.ToString(_usCulture)); + writer.WriteElementString("season", episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.PremiereDate.HasValue) @@ -84,28 +82,28 @@ namespace MediaBrowser.XbmcMetadata.Savers { if (episode.AirsAfterSeasonNumber.HasValue && episode.AirsAfterSeasonNumber.Value != -1) { - writer.WriteElementString("airsafter_season", episode.AirsAfterSeasonNumber.Value.ToString(_usCulture)); + writer.WriteElementString("airsafter_season", episode.AirsAfterSeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) { - writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(_usCulture)); + writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.AirsBeforeSeasonNumber.HasValue && episode.AirsBeforeSeasonNumber.Value != -1) { - writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(_usCulture)); + writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(CultureInfo.InvariantCulture)); } if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1) { - writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(_usCulture)); + writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(CultureInfo.InvariantCulture)); } var specialSeason = episode.AiredSeasonNumber; if (specialSeason.HasValue && specialSeason.Value != -1) { - writer.WriteElementString("displayseason", specialSeason.Value.ToString(_usCulture)); + writer.WriteElementString("displayseason", specialSeason.Value.ToString(CultureInfo.InvariantCulture)); } } } -- cgit v1.2.3 From c26e6d89b43cfd81c5735ae1a2ecd47cc43fda90 Mon Sep 17 00:00:00 2001 From: Dmitry Lyzo Date: Sun, 10 Oct 2021 19:48:11 +0300 Subject: Move FfmpegException to MediaBrowser.Common --- MediaBrowser.Common/FfmpegException.cs | 39 ++++++++++++++++++++++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 1 + MediaBrowser.MediaEncoding/FfmpegException.cs | 39 ---------------------- .../Subtitles/SubtitleEncoder.cs | 1 + 4 files changed, 41 insertions(+), 39 deletions(-) create mode 100644 MediaBrowser.Common/FfmpegException.cs delete mode 100644 MediaBrowser.MediaEncoding/FfmpegException.cs (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.Common/FfmpegException.cs b/MediaBrowser.Common/FfmpegException.cs new file mode 100644 index 000000000..be420196d --- /dev/null +++ b/MediaBrowser.Common/FfmpegException.cs @@ -0,0 +1,39 @@ +using System; + +namespace MediaBrowser.Common +{ + /// + /// Represents errors that occur during interaction with FFmpeg. + /// + public class FfmpegException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public FfmpegException() + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The message that describes the error. + public FfmpegException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and a + /// reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if + /// no inner exception is specified. + /// + public FfmpegException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 06fe95ce8..3dbbeda8a 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -12,6 +12,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Jellyfin.Extensions.Json; +using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Configuration; diff --git a/MediaBrowser.MediaEncoding/FfmpegException.cs b/MediaBrowser.MediaEncoding/FfmpegException.cs deleted file mode 100644 index 1697fd33a..000000000 --- a/MediaBrowser.MediaEncoding/FfmpegException.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; - -namespace MediaBrowser.MediaEncoding -{ - /// - /// Represents errors that occur during interaction with FFmpeg. - /// - public class FfmpegException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public FfmpegException() - { - } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// The message that describes the error. - public FfmpegException(string message) : base(message) - { - } - - /// - /// Initializes a new instance of the class with a specified error message and a - /// reference to the inner exception that is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// - /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if - /// no inner exception is specified. - /// - public FfmpegException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index bfc17904c..2b2de2ff6 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -11,6 +11,7 @@ using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Common; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; -- cgit v1.2.3 From 8d70cc2dde81aad0f484a2f0d0c5b90e6e1b97cd Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Thu, 7 Oct 2021 22:37:59 +0200 Subject: Add support for non-jpg image extractions --- .../MediaEncoding/IMediaEncoder.cs | 3 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 21 ++++---- .../Probing/ProbeResultNormalizer.cs | 18 +++---- .../MediaInfo/EmbeddedImageProvider.cs | 60 ++++++++++++++++------ 4 files changed, 62 insertions(+), 40 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index c5522bc3c..638588560 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -95,9 +95,10 @@ namespace MediaBrowser.Controller.MediaEncoding /// Media source information. /// Media stream information. /// Index of the stream to extract from. + /// The extension of the file to write. /// CancellationToken to use for operation. /// Location of video image. - Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken); + Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken); /// /// Extracts the video images on interval. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 06fe95ce8..30bc7125d 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -468,17 +468,17 @@ namespace MediaBrowser.MediaEncoding.Encoder Protocol = MediaProtocol.File }; - return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, cancellationToken); + return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, "jpg", cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, cancellationToken); + return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, "jpg", cancellationToken); } - public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken) + public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, cancellationToken); + return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, outputExtension, cancellationToken); } private async Task ExtractImage( @@ -490,6 +490,7 @@ namespace MediaBrowser.MediaEncoding.Encoder bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, + string outputExtension, CancellationToken cancellationToken) { var inputArgument = GetInputArgument(inputFile, mediaSource); @@ -499,7 +500,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter. try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, outputExtension, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -512,7 +513,7 @@ namespace MediaBrowser.MediaEncoding.Encoder try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, outputExtension, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -525,7 +526,7 @@ namespace MediaBrowser.MediaEncoding.Encoder try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, outputExtension, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -537,17 +538,17 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, outputExtension, cancellationToken).ConfigureAwait(false); } - private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, CancellationToken cancellationToken) + private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, string outputExtension, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) { throw new ArgumentNullException(nameof(inputPath)); } - var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg"); + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + "." + outputExtension); 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. diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 5f6539495..775689095 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -582,7 +582,8 @@ namespace MediaBrowser.MediaEncoding.Probing /// MediaAttachments. private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo) { - if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase) && + !(streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1)) { return null; } @@ -735,15 +736,14 @@ namespace MediaBrowser.MediaEncoding.Probing else if (string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase)) { // How to differentiate between video and embedded image? - // check disposition, alternately: presence of codec tag, also embedded images have high (unusual) framerates - if ((streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1) || - string.IsNullOrWhiteSpace(stream.CodecTag)) + // The only difference I've seen thus far is presence of codec tag, also embedded images have high (unusual) framerates + if (!string.IsNullOrWhiteSpace(stream.CodecTag)) { - stream.Type = MediaStreamType.EmbeddedImage; + stream.Type = MediaStreamType.Video; } else { - stream.Type = MediaStreamType.Video; + stream.Type = MediaStreamType.EmbeddedImage; } } else @@ -812,12 +812,6 @@ namespace MediaBrowser.MediaEncoding.Probing { stream.ColorPrimaries = streamInfo.ColorPrimaries; } - - // workaround for mkv attached_pics losing filename due to being classified as video based on codec - if (stream.Type == MediaStreamType.EmbeddedImage && streamInfo.Tags != null && string.IsNullOrEmpty(stream.Comment)) - { - stream.Comment = GetDictionaryValue(streamInfo.Tags, "filename"); - } } else { diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index 11fdfcdf9..ad95cdb06 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,6 +16,7 @@ using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; +using MediaBrowser.Model.Net; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo @@ -115,25 +117,49 @@ namespace MediaBrowser.Providers.MediaInfo Protocol = item.PathProtocol ?? MediaProtocol.File, }; - string[] imageFileNames; - switch (type) + string[] imageFileNames = type switch { - case ImageType.Backdrop: - imageFileNames = _backdropImageFileNames; - break; - case ImageType.Logo: - imageFileNames = _logoImageFileNames; - break; - case ImageType.Primary: - default: - imageFileNames = _primaryImageFileNames; - break; + ImageType.Primary => _primaryImageFileNames, + ImageType.Backdrop => _backdropImageFileNames, + ImageType.Logo => _logoImageFileNames, + _ => _primaryImageFileNames + }; + + // Try attachments first + var attachmentSources = item.GetMediaSources(false).SelectMany(source => source.MediaAttachments).ToList(); + var attachmentStream = attachmentSources + .Where(stream => !string.IsNullOrEmpty(stream.FileName)) + .First(stream => imageFileNames.Any(name => stream.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); + + if (attachmentStream != null) + { + var extension = (string.IsNullOrEmpty(attachmentStream.MimeType) ? + Path.GetExtension(attachmentStream.FileName) : + MimeTypes.ToExtension(attachmentStream.MimeType)) ?? "jpg"; + + string extractedAttachmentPath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken).ConfigureAwait(false); + + ImageFormat format = extension switch + { + "bmp" => ImageFormat.Bmp, + "gif" => ImageFormat.Gif, + "jpg" => ImageFormat.Jpg, + "png" => ImageFormat.Png, + "webp" => ImageFormat.Webp, + _ => ImageFormat.Jpg + }; + + return new DynamicImageResponse + { + Format = format, + HasImage = true, + Path = extractedAttachmentPath, + Protocol = MediaProtocol.File + }; } - var imageStreams = - item.GetMediaStreams() - .Where(i => i.Type == MediaStreamType.EmbeddedImage) - .ToList(); + // Fall back to EmbeddedImage streams + var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage); if (!imageStreams.Any()) { @@ -160,7 +186,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); + string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, "jpg", cancellationToken).ConfigureAwait(false); return new DynamicImageResponse { -- cgit v1.2.3 From e3eee10d05e9ecc7e3fac1f8fdad92329d38a4db Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 11 Oct 2021 12:34:18 +0200 Subject: Add image provider tests and clean up --- .../MediaEncoding/IMediaEncoder.cs | 2 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 6 +- .../MediaInfo/EmbeddedImageProvider.cs | 41 ++-- .../MediaInfo/VideoImageProvider.cs | 9 +- .../MediaInfo/EmbeddedImageProviderTests.cs | 211 +++++++++++++++++++++ .../MediaInfo/VideoImageProviderTests.cs | 168 ++++++++++++++++ 6 files changed, 408 insertions(+), 29 deletions(-) create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 638588560..e6511ca8d 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Media source information. /// Media stream information. /// Index of the stream to extract from. - /// The extension of the file to write. + /// The extension of the file to write, including the '.'. /// CancellationToken to use for operation. /// Location of video image. Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 30bc7125d..dac2c6a26 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -468,12 +468,12 @@ namespace MediaBrowser.MediaEncoding.Encoder Protocol = MediaProtocol.File }; - return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, "jpg", cancellationToken); + return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ".jpg", cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, "jpg", cancellationToken); + return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ".jpg", cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken) @@ -548,7 +548,7 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ArgumentNullException(nameof(inputPath)); } - var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + "." + outputExtension); + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); 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. diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index ad95cdb06..df87f2d49 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -1,9 +1,7 @@ -#nullable enable #pragma warning disable CS1591 using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; using System.Linq; using System.Threading; @@ -17,7 +15,6 @@ using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; -using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo { @@ -48,12 +45,10 @@ namespace MediaBrowser.Providers.MediaInfo }; private readonly IMediaEncoder _mediaEncoder; - private readonly ILogger _logger; - public EmbeddedImageProvider(IMediaEncoder mediaEncoder, ILogger logger) + public EmbeddedImageProvider(IMediaEncoder mediaEncoder) { _mediaEncoder = mediaEncoder; - _logger = logger; } /// @@ -84,7 +79,7 @@ namespace MediaBrowser.Providers.MediaInfo }; } - return ImmutableList.Empty; + return new List(); } /// @@ -98,13 +93,6 @@ namespace MediaBrowser.Providers.MediaInfo return Task.FromResult(new DynamicImageResponse { HasImage = false }); } - // Can't extract if we didn't find any video streams in the file - if (!video.DefaultVideoStreamIndex.HasValue) - { - _logger.LogInformation("Skipping image extraction due to missing DefaultVideoStreamIndex for {Path}.", video.Path ?? string.Empty); - return Task.FromResult(new DynamicImageResponse { HasImage = false }); - } - return GetEmbeddedImage(video, type, cancellationToken); } @@ -128,24 +116,29 @@ namespace MediaBrowser.Providers.MediaInfo // Try attachments first var attachmentSources = item.GetMediaSources(false).SelectMany(source => source.MediaAttachments).ToList(); var attachmentStream = attachmentSources - .Where(stream => !string.IsNullOrEmpty(stream.FileName)) - .First(stream => imageFileNames.Any(name => stream.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); + .Where(attachment => !string.IsNullOrEmpty(attachment.FileName)) + .FirstOrDefault(attachment => imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); if (attachmentStream != null) { - var extension = (string.IsNullOrEmpty(attachmentStream.MimeType) ? + var extension = string.IsNullOrEmpty(attachmentStream.MimeType) ? Path.GetExtension(attachmentStream.FileName) : - MimeTypes.ToExtension(attachmentStream.MimeType)) ?? "jpg"; + MimeTypes.ToExtension(attachmentStream.MimeType); + + if (string.IsNullOrEmpty(extension)) + { + extension = ".jpg"; + } string extractedAttachmentPath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken).ConfigureAwait(false); ImageFormat format = extension switch { - "bmp" => ImageFormat.Bmp, - "gif" => ImageFormat.Gif, - "jpg" => ImageFormat.Jpg, - "png" => ImageFormat.Png, - "webp" => ImageFormat.Webp, + ".bmp" => ImageFormat.Bmp, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpg, + ".png" => ImageFormat.Png, + ".webp" => ImageFormat.Webp, _ => ImageFormat.Jpg }; @@ -170,7 +163,7 @@ namespace MediaBrowser.Providers.MediaInfo // Extract first stream containing an element of imageFileNames var imageStream = imageStreams .Where(stream => !string.IsNullOrEmpty(stream.Comment)) - .First(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); + .FirstOrDefault(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); // Primary type only: default to first image if none found by label if (imageStream == null) diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 8f2009950..60739f156 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -81,7 +81,14 @@ namespace MediaBrowser.Providers.MediaInfo ? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10) : TimeSpan.FromSeconds(10); - var videoStream = item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video); + var videoStream = item.GetDefaultVideoStream() ?? item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video); + + if (videoStream == null) + { + _logger.LogInformation("Skipping image extraction: no video stream found for {Path}.", item.Path ?? string.Empty); + return new DynamicImageResponse { HasImage = false }; + } + string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); return new DynamicImageResponse diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs new file mode 100644 index 000000000..fcea1532a --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -0,0 +1,211 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Providers.MediaInfo; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo +{ + public class EmbeddedImageProviderTests + { + public static TheoryData GetSupportedImages_Empty_TestData => + new () + { + new AudioBook(), + new BoxSet(), + new Series(), + new Season(), + }; + + public static TheoryData> GetSupportedImages_Populated_TestData => + new TheoryData> + { + { new Episode(), new List { ImageType.Primary } }, + { new Movie(), new List { ImageType.Logo, ImageType.Backdrop, ImageType.Primary } }, + }; + + private EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder) + { + return new EmbeddedImageProvider(mediaEncoder); + } + + [Theory] + [MemberData(nameof(GetSupportedImages_Empty_TestData))] + public void GetSupportedImages_Empty(BaseItem item) + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + Assert.False(embeddedImageProvider.GetSupportedImages(item).Any()); + } + + [Theory] + [MemberData(nameof(GetSupportedImages_Populated_TestData))] + public void GetSupportedImages_Populated(BaseItem item, IEnumerable expected) + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + var actual = embeddedImageProvider.GetSupportedImages(item); + Assert.Equal(expected.OrderBy(i => i.ToString()), actual.OrderBy(i => i.ToString())); + } + + [Fact] + public async void GetImage_Empty_NoStreams() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_NoLabeledAttachments() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = new Mock(); + // add an attachment without a filename - has a list to look through but finds nothing + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List { new () { MediaAttachments = new List { new () } } }); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_NoEmbeddedLabeledBackdrop() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List { new () { Type = MediaStreamType.EmbeddedImage } }); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Attached() + { + // first tests file extension detection, second uses mimetype, third defaults to jpg + MediaAttachment sampleAttachment1 = new () { FileName = "clearlogo.png", Index = 1 }; + MediaAttachment sampleAttachment2 = new () { FileName = "backdrop", MimeType = "image/bmp", Index = 2 }; + MediaAttachment sampleAttachment3 = new () { FileName = "poster", Index = 3 }; + string targetPath1 = "path1.png"; + string targetPath2 = "path2.bmp"; + string targetPath3 = "path2.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 1, ".png", CancellationToken.None)) + .Returns(Task.FromResult(targetPath1)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 2, ".bmp", CancellationToken.None)) + .Returns(Task.FromResult(targetPath2)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 3, ".jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath3)); + var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List { new () { MediaAttachments = new List { sampleAttachment1, sampleAttachment2, sampleAttachment3 } } }); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + + var actualLogo = await embeddedImageProvider.GetImage(input.Object, ImageType.Logo, CancellationToken.None); + Assert.NotNull(actualLogo); + Assert.True(actualLogo.HasImage); + Assert.Equal(targetPath1, actualLogo.Path); + Assert.Equal(ImageFormat.Png, actualLogo.Format); + + var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actualBackdrop); + Assert.True(actualBackdrop.HasImage); + Assert.Equal(targetPath2, actualBackdrop.Path); + Assert.Equal(ImageFormat.Bmp, actualBackdrop.Format); + + var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actualPrimary); + Assert.True(actualPrimary.HasImage); + Assert.Equal(targetPath3, actualPrimary.Path); + Assert.Equal(ImageFormat.Jpg, actualPrimary.Format); + } + + [Fact] + public async void GetImage_EmbeddedDefault() + { + MediaStream sampleStream = new () { Type = MediaStreamType.EmbeddedImage, Index = 1 }; + string targetPath = "path"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream, 1, "jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath)); + var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { sampleStream }); + + var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.HasImage); + Assert.Equal(targetPath, actual.Path); + Assert.Equal(ImageFormat.Jpg, actual.Format); + } + + [Fact] + public async void GetImage_EmbeddedSelection() + { + // primary is second stream to ensure it's not defaulting, backdrop is first + MediaStream sampleStream1 = new () { Type = MediaStreamType.EmbeddedImage, Index = 1, Comment = "backdrop" }; + MediaStream sampleStream2 = new () { Type = MediaStreamType.EmbeddedImage, Index = 2, Comment = "cover" }; + string targetPath1 = "path1.jpg"; + string targetPath2 = "path2.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream1, 1, "jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath1)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream2, 2, "jpg", CancellationToken.None)) + .Returns(Task.FromResult(targetPath2)); + var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaSources(It.IsAny())) + .Returns(new List()); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List { sampleStream1, sampleStream2 }); + + var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actualPrimary); + Assert.True(actualPrimary.HasImage); + Assert.Equal(targetPath2, actualPrimary.Path); + Assert.Equal(ImageFormat.Jpg, actualPrimary.Format); + + var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actualBackdrop); + Assert.True(actualBackdrop.HasImage); + Assert.Equal(targetPath1, actualBackdrop.Path); + Assert.Equal(ImageFormat.Jpg, actualBackdrop.Format); + } + } +} diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs new file mode 100644 index 000000000..9a5cd79bb --- /dev/null +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Drawing; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Providers.MediaInfo; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Jellyfin.Providers.Tests.MediaInfo +{ + public class VideoImageProviderTests + { + private VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder) + { + // strict to ensure this isn't accidentally used where a prepared mock is intended + mediaEncoder ??= new Mock(MockBehavior.Strict).Object; + return new VideoImageProvider(mediaEncoder, new NullLogger()); + } + + [Fact] + public async void GetImage_Empty_IsPlaceholder() + { + var videoImageProvider = GetVideoImageProvider(null); + + var input = new Mock(); + input.Object.IsPlaceHolder = true; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_NoDefaultVideoStream() + { + var videoImageProvider = GetVideoImageProvider(null); + + var input = new Mock(); + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Empty_DefaultSet_NoVideoStream() + { + var videoImageProvider = GetVideoImageProvider(null); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List()); + // set a default index but don't put anything there (invalid input, but provider shouldn't break) + input.Object.DefaultVideoStreamIndex = 1; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_Extract_DefaultStream() + { + MediaStream firstStream = new () { Type = MediaStreamType.Video, Index = 0 }; + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 1 }; + string targetPath = "path.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), firstStream, It.IsAny(), It.IsAny(), CancellationToken.None)) + .Returns(Task.FromResult("wrong stream called!")); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), targetStream, It.IsAny(), It.IsAny(), CancellationToken.None)) + .Returns(Task.FromResult(targetPath)); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetDefaultVideoStream()) + .Returns(targetStream); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { firstStream, targetStream }); + input.Object.DefaultVideoStreamIndex = 1; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.HasImage); + Assert.Equal(targetPath, actual.Path); + Assert.Equal(ImageFormat.Jpg, actual.Format); + } + + [Fact] + public async void GetImage_Extract_FallbackToFirstVideoStream() + { + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + string targetPath = "path.jpg"; + + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), targetStream, It.IsAny(), It.IsAny(), CancellationToken.None)) + .Returns(Task.FromResult(targetPath)); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { targetStream }); + // default must be set, ensure a stream is still found if not pointed at a video + input.Object.DefaultVideoStreamIndex = 5; + + var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + Assert.NotNull(actual); + Assert.True(actual.HasImage); + Assert.Equal(targetPath, actual.Path); + Assert.Equal(ImageFormat.Jpg, actual.Format); + } + + [Fact] + public async void GetImage_Time_Default() + { + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + + TimeSpan? actualTimeSpan = null; + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None)) + .Callback((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan) + .Returns(Task.FromResult("path")); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { targetStream }); + // default must be set + input.Object.DefaultVideoStreamIndex = 0; + + // not testing return, just verifying what gets requested for time span + await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + + Assert.Equal(TimeSpan.FromSeconds(10), actualTimeSpan); + } + + [Fact] + public async void GetImage_Time_Calculated() + { + MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + + TimeSpan? actualTimeSpan = null; + var mediaEncoder = new Mock(MockBehavior.Strict); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None)) + .Callback((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan) + .Returns(Task.FromResult("path")); + var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); + + var input = new Mock(); + input.Setup(movie => movie.GetMediaStreams()) + .Returns(new List() { targetStream }); + // default must be set + input.Object.DefaultVideoStreamIndex = 0; + input.Object.RunTimeTicks = 5000; + + // not testing return, just verifying what gets requested for time span + await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + + Assert.Equal(TimeSpan.FromTicks(500), actualTimeSpan); + } + } +} -- cgit v1.2.3 From 31baea072a1ed25346437150f27a93916cb4cca0 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Mon, 11 Oct 2021 21:25:12 +0200 Subject: Address review comments Clean up style Fix references in class summaries Combine Where+FirstOrDefault queries Break up large method, long lines Add validation on file extension Apply test naming conventions Extract mock of Movie class, comment on why not mocking interface Co-authored-by: Cody Robibero Co-authored-by: Claus Vium --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 9 ++ .../Probing/ProbeResultNormalizer.cs | 4 +- .../MediaInfo/EmbeddedImageProvider.cs | 95 +++++++------ .../MediaInfo/VideoImageProvider.cs | 10 +- .../MediaInfo/EmbeddedImageProviderTests.cs | 155 +++++++++++---------- .../MediaInfo/VideoImageProviderTests.cs | 108 +++++++------- 6 files changed, 208 insertions(+), 173 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index dac2c6a26..102b4f943 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -548,6 +548,15 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new ArgumentNullException(nameof(inputPath)); } + if (string.IsNullOrEmpty(outputExtension)) + { + outputExtension = ".jpg"; + } + else if (outputExtension[0] != '.') + { + outputExtension = "." + outputExtension; + } + var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 775689095..9279cb220 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -582,8 +582,8 @@ namespace MediaBrowser.MediaEncoding.Probing /// MediaAttachments. private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo) { - if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase) && - !(streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1)) + if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase) + && streamInfo.Disposition?.GetValueOrDefault("attached_pic") != 1) { return null; } diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index df87f2d49..1d9d1e02a 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -1,5 +1,3 @@ -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.IO; @@ -19,7 +17,7 @@ using MediaBrowser.Model.Net; namespace MediaBrowser.Providers.MediaInfo { /// - /// Uses ffmpeg to extract embedded images. + /// Uses to extract embedded images. /// public class EmbeddedImageProvider : IDynamicImageProvider, IHasOrder { @@ -46,6 +44,10 @@ namespace MediaBrowser.Providers.MediaInfo private readonly IMediaEncoder _mediaEncoder; + /// + /// Initializes a new instance of the class. + /// + /// The media encoder for extracting attached/embedded images. public EmbeddedImageProvider(IMediaEncoder mediaEncoder) { _mediaEncoder = mediaEncoder; @@ -65,13 +67,13 @@ namespace MediaBrowser.Providers.MediaInfo { if (item is Episode) { - return new List + return new[] { ImageType.Primary, }; } - return new List + return new[] { ImageType.Primary, ImageType.Backdrop, @@ -79,7 +81,7 @@ namespace MediaBrowser.Providers.MediaInfo }; } - return new List(); + return Array.Empty(); } /// @@ -114,47 +116,20 @@ namespace MediaBrowser.Providers.MediaInfo }; // Try attachments first - var attachmentSources = item.GetMediaSources(false).SelectMany(source => source.MediaAttachments).ToList(); - var attachmentStream = attachmentSources - .Where(attachment => !string.IsNullOrEmpty(attachment.FileName)) - .FirstOrDefault(attachment => imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); + var attachmentStream = item.GetMediaSources(false) + .SelectMany(source => source.MediaAttachments) + .FirstOrDefault(attachment => !string.IsNullOrEmpty(attachment.FileName) + && imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); if (attachmentStream != null) { - var extension = string.IsNullOrEmpty(attachmentStream.MimeType) ? - Path.GetExtension(attachmentStream.FileName) : - MimeTypes.ToExtension(attachmentStream.MimeType); - - if (string.IsNullOrEmpty(extension)) - { - extension = ".jpg"; - } - - string extractedAttachmentPath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken).ConfigureAwait(false); - - ImageFormat format = extension switch - { - ".bmp" => ImageFormat.Bmp, - ".gif" => ImageFormat.Gif, - ".jpg" => ImageFormat.Jpg, - ".png" => ImageFormat.Png, - ".webp" => ImageFormat.Webp, - _ => ImageFormat.Jpg - }; - - return new DynamicImageResponse - { - Format = format, - HasImage = true, - Path = extractedAttachmentPath, - Protocol = MediaProtocol.File - }; + return await ExtractAttachment(item, cancellationToken, attachmentStream, mediaSource); } // Fall back to EmbeddedImage streams var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage); - if (!imageStreams.Any()) + if (imageStreams.Count == 0) { // Can't extract if we don't have any EmbeddedImage streams return new DynamicImageResponse { HasImage = false }; @@ -162,8 +137,8 @@ namespace MediaBrowser.Providers.MediaInfo // Extract first stream containing an element of imageFileNames var imageStream = imageStreams - .Where(stream => !string.IsNullOrEmpty(stream.Comment)) - .FirstOrDefault(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); + .FirstOrDefault(stream => !string.IsNullOrEmpty(stream.Comment) + && imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase))); // Primary type only: default to first image if none found by label if (imageStream == null) @@ -179,7 +154,9 @@ namespace MediaBrowser.Providers.MediaInfo } } - string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, "jpg", cancellationToken).ConfigureAwait(false); + string extractedImagePath = + await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, ".jpg", cancellationToken) + .ConfigureAwait(false); return new DynamicImageResponse { @@ -190,6 +167,40 @@ namespace MediaBrowser.Providers.MediaInfo }; } + private async Task ExtractAttachment(Video item, CancellationToken cancellationToken, MediaAttachment attachmentStream, MediaSourceInfo mediaSource) + { + var extension = string.IsNullOrEmpty(attachmentStream.MimeType) + ? Path.GetExtension(attachmentStream.FileName) + : MimeTypes.ToExtension(attachmentStream.MimeType); + + if (string.IsNullOrEmpty(extension)) + { + extension = ".jpg"; + } + + string extractedAttachmentPath = + await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken) + .ConfigureAwait(false); + + ImageFormat format = extension switch + { + ".bmp" => ImageFormat.Bmp, + ".gif" => ImageFormat.Gif, + ".jpg" => ImageFormat.Jpg, + ".png" => ImageFormat.Png, + ".webp" => ImageFormat.Webp, + _ => ImageFormat.Jpg + }; + + return new DynamicImageResponse + { + Format = format, + HasImage = true, + Path = extractedAttachmentPath, + Protocol = MediaProtocol.File + }; + } + /// public bool Supports(BaseItem item) { diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs index 60739f156..d226182c0 100644 --- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs @@ -1,6 +1,3 @@ -#nullable enable -#pragma warning disable CS1591 - using System; using System.Collections.Generic; using System.Linq; @@ -18,13 +15,18 @@ using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo { /// - /// Uses ffmpeg to create still images from the main video. + /// Uses to create still images from the main video. /// public class VideoImageProvider : IDynamicImageProvider, IHasOrder { private readonly IMediaEncoder _mediaEncoder; private readonly ILogger _logger; + /// + /// Initializes a new instance of the class. + /// + /// The media encoder for capturing images. + /// The logger. public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger logger) { _mediaEncoder = mediaEncoder; diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs index fcea1532a..b194e3885 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -17,38 +17,37 @@ namespace Jellyfin.Providers.Tests.MediaInfo { public class EmbeddedImageProviderTests { - public static TheoryData GetSupportedImages_Empty_TestData => - new () + private static TheoryData GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty_TestData() + { + return new () { new AudioBook(), new BoxSet(), new Series(), new Season(), }; - - public static TheoryData> GetSupportedImages_Populated_TestData => - new TheoryData> - { - { new Episode(), new List { ImageType.Primary } }, - { new Movie(), new List { ImageType.Logo, ImageType.Backdrop, ImageType.Primary } }, - }; - - private EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder) - { - return new EmbeddedImageProvider(mediaEncoder); } [Theory] - [MemberData(nameof(GetSupportedImages_Empty_TestData))] - public void GetSupportedImages_Empty(BaseItem item) + [MemberData(nameof(GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty_TestData))] + public void GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty(BaseItem item) { var embeddedImageProvider = GetEmbeddedImageProvider(null); - Assert.False(embeddedImageProvider.GetSupportedImages(item).Any()); + Assert.Empty(embeddedImageProvider.GetSupportedImages(item)); + } + + private static TheoryData> GetSupportedImages_SupportedBaseItems_ReturnsPopulated_TestData() + { + return new TheoryData> + { + { new Episode(), new List { ImageType.Primary } }, + { new Movie(), new List { ImageType.Logo, ImageType.Backdrop, ImageType.Primary } }, + }; } [Theory] - [MemberData(nameof(GetSupportedImages_Populated_TestData))] - public void GetSupportedImages_Populated(BaseItem item, IEnumerable expected) + [MemberData(nameof(GetSupportedImages_SupportedBaseItems_ReturnsPopulated_TestData))] + public void GetSupportedImages_SupportedBaseItems_ReturnsPopulated(BaseItem item, IEnumerable expected) { var embeddedImageProvider = GetEmbeddedImageProvider(null); var actual = embeddedImageProvider.GetSupportedImages(item); @@ -56,56 +55,34 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_Empty_NoStreams() + public async void GetImage_InputWithNoStreams_ReturnsNoImage() { var embeddedImageProvider = GetEmbeddedImageProvider(null); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); + var input = GetMovie(new List(), new List()); - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_NoLabeledAttachments() + public async void GetImage_InputWithUnlabeledAttachments_ReturnsNoImage() { var embeddedImageProvider = GetEmbeddedImageProvider(null); - var input = new Mock(); // add an attachment without a filename - has a list to look through but finds nothing - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List { new () { MediaAttachments = new List { new () } } }); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); + var input = GetMovie( + new List { new () }, + new List()); - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_NoEmbeddedLabeledBackdrop() - { - var embeddedImageProvider = GetEmbeddedImageProvider(null); - - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List { new () { Type = MediaStreamType.EmbeddedImage } }); - - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); - Assert.NotNull(actual); - Assert.False(actual.HasImage); - } - - [Fact] - public async void GetImage_Attached() + public async void GetImage_InputWithLabeledAttachments_ReturnsCorrectSelection() { // first tests file extension detection, second uses mimetype, third defaults to jpg MediaAttachment sampleAttachment1 = new () { FileName = "clearlogo.png", Index = 1 }; @@ -124,25 +101,23 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult(targetPath3)); var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List { new () { MediaAttachments = new List { sampleAttachment1, sampleAttachment2, sampleAttachment3 } } }); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); + var input = GetMovie( + new List { sampleAttachment1, sampleAttachment2, sampleAttachment3 }, + new List()); - var actualLogo = await embeddedImageProvider.GetImage(input.Object, ImageType.Logo, CancellationToken.None); + var actualLogo = await embeddedImageProvider.GetImage(input, ImageType.Logo, CancellationToken.None); Assert.NotNull(actualLogo); Assert.True(actualLogo.HasImage); Assert.Equal(targetPath1, actualLogo.Path); Assert.Equal(ImageFormat.Png, actualLogo.Format); - var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + var actualBackdrop = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None); Assert.NotNull(actualBackdrop); Assert.True(actualBackdrop.HasImage); Assert.Equal(targetPath2, actualBackdrop.Path); Assert.Equal(ImageFormat.Bmp, actualBackdrop.Format); - var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actualPrimary = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actualPrimary); Assert.True(actualPrimary.HasImage); Assert.Equal(targetPath3, actualPrimary.Path); @@ -150,23 +125,35 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_EmbeddedDefault() + public async void GetImage_InputWithUnlabeledEmbeddedImages_BackdropReturnsNoImage() + { + var embeddedImageProvider = GetEmbeddedImageProvider(null); + + var input = GetMovie( + new List(), + new List { new () { Type = MediaStreamType.EmbeddedImage } }); + + var actual = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None); + Assert.NotNull(actual); + Assert.False(actual.HasImage); + } + + [Fact] + public async void GetImage_InputWithUnlabeledEmbeddedImages_PrimaryReturnsImage() { MediaStream sampleStream = new () { Type = MediaStreamType.EmbeddedImage, Index = 1 }; string targetPath = "path"; var mediaEncoder = new Mock(MockBehavior.Strict); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream, 1, "jpg", CancellationToken.None)) + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream, 1, ".jpg", CancellationToken.None)) .Returns(Task.FromResult(targetPath)); var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { sampleStream }); + var input = GetMovie( + new List(), + new List { sampleStream }); - var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.HasImage); Assert.Equal(targetPath, actual.Path); @@ -174,7 +161,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_EmbeddedSelection() + public async void GetImage_InputWithLabeledEmbeddedImages_ReturnsCorrectSelection() { // primary is second stream to ensure it's not defaulting, backdrop is first MediaStream sampleStream1 = new () { Type = MediaStreamType.EmbeddedImage, Index = 1, Comment = "backdrop" }; @@ -183,29 +170,47 @@ namespace Jellyfin.Providers.Tests.MediaInfo string targetPath2 = "path2.jpg"; var mediaEncoder = new Mock(MockBehavior.Strict); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream1, 1, "jpg", CancellationToken.None)) + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream1, 1, ".jpg", CancellationToken.None)) .Returns(Task.FromResult(targetPath1)); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream2, 2, "jpg", CancellationToken.None)) + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream2, 2, ".jpg", CancellationToken.None)) .Returns(Task.FromResult(targetPath2)); var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaSources(It.IsAny())) - .Returns(new List()); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List { sampleStream1, sampleStream2 }); + var input = GetMovie( + new List(), + new List { sampleStream1, sampleStream2 }); - var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actualPrimary = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actualPrimary); Assert.True(actualPrimary.HasImage); Assert.Equal(targetPath2, actualPrimary.Path); Assert.Equal(ImageFormat.Jpg, actualPrimary.Format); - var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None); + var actualBackdrop = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None); Assert.NotNull(actualBackdrop); Assert.True(actualBackdrop.HasImage); Assert.Equal(targetPath1, actualBackdrop.Path); Assert.Equal(ImageFormat.Jpg, actualBackdrop.Format); } + + private static EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder) + { + return new EmbeddedImageProvider(mediaEncoder); + } + + private static Movie GetMovie(List mediaAttachments, List mediaStreams) + { + // Mocking IMediaSourceManager GetMediaAttachments and GetMediaStreams instead of mocking Movie works, but + // has concurrency problems between this and VideoImageProviderTests due to BaseItem.MediaSourceManager + // being static + var movie = new Mock(); + + movie.Setup(item => item.GetMediaSources(It.IsAny())) + .Returns(new List { new () { MediaAttachments = mediaAttachments } } ); + movie.Setup(item => item.GetMediaStreams()) + .Returns(mediaStreams); + + return movie.Object; + } } } diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs index 9a5cd79bb..0f51a2b8f 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs @@ -16,56 +16,51 @@ namespace Jellyfin.Providers.Tests.MediaInfo { public class VideoImageProviderTests { - private VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder) - { - // strict to ensure this isn't accidentally used where a prepared mock is intended - mediaEncoder ??= new Mock(MockBehavior.Strict).Object; - return new VideoImageProvider(mediaEncoder, new NullLogger()); - } - [Fact] - public async void GetImage_Empty_IsPlaceholder() + public async void GetImage_InputIsPlaceholder_ReturnsNoImage() { var videoImageProvider = GetVideoImageProvider(null); - var input = new Mock(); - input.Object.IsPlaceHolder = true; + var input = new Movie + { + IsPlaceHolder = true + }; - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_NoDefaultVideoStream() + public async void GetImage_NoDefaultVideoStream_ReturnsNoImage() { var videoImageProvider = GetVideoImageProvider(null); - var input = new Mock(); + var input = new Movie + { + DefaultVideoStreamIndex = null + }; - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Empty_DefaultSet_NoVideoStream() + public async void GetImage_DefaultSetButNoVideoStream_ReturnsNoImage() { var videoImageProvider = GetVideoImageProvider(null); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List()); // set a default index but don't put anything there (invalid input, but provider shouldn't break) - input.Object.DefaultVideoStreamIndex = 1; + var input = GetMovie(0, null, new List()); - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.False(actual.HasImage); } [Fact] - public async void GetImage_Extract_DefaultStream() + public async void GetImage_DefaultSetMultipleVideoStreams_ReturnsDefaultStreamImage() { MediaStream firstStream = new () { Type = MediaStreamType.Video, Index = 0 }; MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 1 }; @@ -78,14 +73,9 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult(targetPath)); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetDefaultVideoStream()) - .Returns(targetStream); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { firstStream, targetStream }); - input.Object.DefaultVideoStreamIndex = 1; + var input = GetMovie(1, targetStream, new List { firstStream, targetStream } ); - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.HasImage); Assert.Equal(targetPath, actual.Path); @@ -93,7 +83,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_Extract_FallbackToFirstVideoStream() + public async void GetImage_InvalidDefaultSingleVideoStream_ReturnsFirstVideoStreamImage() { MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; string targetPath = "path.jpg"; @@ -103,13 +93,10 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult(targetPath)); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { targetStream }); - // default must be set, ensure a stream is still found if not pointed at a video - input.Object.DefaultVideoStreamIndex = 5; + // provide query results for default (empty) and all streams (populated) + var input = GetMovie(5, null, new List { targetStream }); - var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.NotNull(actual); Assert.True(actual.HasImage); Assert.Equal(targetPath, actual.Path); @@ -117,10 +104,12 @@ namespace Jellyfin.Providers.Tests.MediaInfo } [Fact] - public async void GetImage_Time_Default() + public async void GetImage_NoTimeSpanSet_CallsEncoderWithDefaultTime() { MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; + // use a callback to catch the actual value + // provides more information on failure than verifying a specific input was called on the mock TimeSpan? actualTimeSpan = null; var mediaEncoder = new Mock(MockBehavior.Strict); mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None)) @@ -128,20 +117,16 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult("path")); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { targetStream }); - // default must be set - input.Object.DefaultVideoStreamIndex = 0; + var input = GetMovie(0, targetStream, new List { targetStream }); // not testing return, just verifying what gets requested for time span - await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.Equal(TimeSpan.FromSeconds(10), actualTimeSpan); } [Fact] - public async void GetImage_Time_Calculated() + public async void GetImage_TimeSpanSet_CallsEncoderWithCalculatedTime() { MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 }; @@ -152,17 +137,40 @@ namespace Jellyfin.Providers.Tests.MediaInfo .Returns(Task.FromResult("path")); var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object); - var input = new Mock(); - input.Setup(movie => movie.GetMediaStreams()) - .Returns(new List() { targetStream }); - // default must be set - input.Object.DefaultVideoStreamIndex = 0; - input.Object.RunTimeTicks = 5000; + var input = GetMovie(0, targetStream, new List { targetStream }); + input.RunTimeTicks = 5000; // not testing return, just verifying what gets requested for time span - await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None); + await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None); Assert.Equal(TimeSpan.FromTicks(500), actualTimeSpan); } + + private static VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder) + { + // strict to ensure this isn't accidentally used where a prepared mock is intended + mediaEncoder ??= new Mock(MockBehavior.Strict).Object; + return new VideoImageProvider(mediaEncoder, new NullLogger()); + } + + private static Movie GetMovie(int defaultVideoStreamIndex, MediaStream? defaultStream, List mediaStreams) + { + // Mocking IMediaSourceManager GetMediaStreams instead of mocking Movie works, but has concurrency problems + // between this and EmbeddedImageProviderTests due to BaseItem.MediaSourceManager being static + var movie = new Mock + { + Object = + { + DefaultVideoStreamIndex = defaultVideoStreamIndex + } + }; + + movie.Setup(item => item.GetDefaultVideoStream()) + .Returns(defaultStream!); + movie.Setup(item => item.GetMediaStreams()) + .Returns(mediaStreams); + + return movie.Object; + } } } -- cgit v1.2.3 From 1d19a5be617c191a731b76e556fae1e395eb3788 Mon Sep 17 00:00:00 2001 From: Bond_009 Date: Tue, 9 Nov 2021 22:29:33 +0100 Subject: Fix some warnings down to 580 --- Emby.Dlna/DlnaManager.cs | 19 +--- Emby.Dlna/Main/DlnaEntryPoint.cs | 7 +- Emby.Server.Implementations/Dto/DtoService.cs | 7 +- Emby.Server.Implementations/IO/LibraryMonitor.cs | 2 +- .../Library/LibraryManager.cs | 3 +- .../Library/Resolvers/Audio/MusicAlbumResolver.cs | 2 +- .../LiveTv/EmbyTV/EmbyTV.cs | 2 +- .../LiveTv/EmbyTV/EncodedRecorder.cs | 5 +- .../ScheduledTasks/ScheduledTaskWorker.cs | 10 +- .../Updates/InstallationManager.cs | 2 +- Jellyfin.Api/Controllers/DynamicHlsController.cs | 2 +- Jellyfin.Api/Helpers/TranscodingJobHelper.cs | 6 +- .../Models/PlaybackDtos/TranscodingThrottler.cs | 2 +- MediaBrowser.Controller/Entities/Folder.cs | 4 +- .../MediaEncoding/IMediaEncoder.cs | 26 ----- .../Parsers/BaseItemXmlParser.cs | 2 +- .../Attachments/AttachmentExtractor.cs | 7 +- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 117 +-------------------- .../Subtitles/SubtitleEncoder.cs | 11 +- .../Parsers/SeriesNfoParser.cs | 2 +- jellyfin.ruleset | 4 + 21 files changed, 38 insertions(+), 204 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs index f37d2d7d7..277a0e678 100644 --- a/Emby.Dlna/DlnaManager.cs +++ b/Emby.Dlna/DlnaManager.cs @@ -112,7 +112,7 @@ namespace Emby.Dlna if (profile == null) { - LogUnmatchedProfile(deviceInfo); + _logger.LogInformation("No matching device profile found. The default will need to be used. \n{@Profile}", deviceInfo); } else { @@ -122,23 +122,6 @@ namespace Emby.Dlna return profile; } - private void LogUnmatchedProfile(DeviceIdentification profile) - { - var builder = new StringBuilder(); - - builder.AppendLine("No matching device profile found. The default will need to be used."); - builder.Append("FriendlyName: ").AppendLine(profile.FriendlyName); - builder.Append("Manufacturer: ").AppendLine(profile.Manufacturer); - builder.Append("ManufacturerUrl: ").AppendLine(profile.ManufacturerUrl); - builder.Append("ModelDescription: ").AppendLine(profile.ModelDescription); - builder.Append("ModelName: ").AppendLine(profile.ModelName); - builder.Append("ModelNumber: ").AppendLine(profile.ModelNumber); - builder.Append("ModelUrl: ").AppendLine(profile.ModelUrl); - builder.Append("SerialNumber: ").AppendLine(profile.SerialNumber); - - _logger.LogInformation(builder.ToString()); - } - /// /// Attempts to match a device with a profile. /// Rules: diff --git a/Emby.Dlna/Main/DlnaEntryPoint.cs b/Emby.Dlna/Main/DlnaEntryPoint.cs index 8e89d9ae6..722428c73 100644 --- a/Emby.Dlna/Main/DlnaEntryPoint.cs +++ b/Emby.Dlna/Main/DlnaEntryPoint.cs @@ -218,11 +218,6 @@ namespace Emby.Dlna.Main } } - private void LogMessage(string msg) - { - _logger.LogDebug(msg); - } - private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer) { try @@ -272,7 +267,7 @@ namespace Emby.Dlna.Main Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost) { - LogFunction = LogMessage, + LogFunction = (msg) => _logger.LogDebug("{Msg}", msg), SupportPnpRootDevice = false }; diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index c6b32a52c..67ecd04e0 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -134,14 +134,11 @@ namespace Emby.Server.Implementations.Dto var dto = GetBaseItemDtoInternal(item, options, user, owner); if (item is LiveTvChannel tvChannel) { - var list = new List<(BaseItemDto, LiveTvChannel)>(1) { (dto, tvChannel) }; - LivetvManager.AddChannelInfo(list, options, user); + LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user); } else if (item is LiveTvProgram) { - var list = new List<(BaseItem, BaseItemDto)>(1) { (item, dto) }; - var task = LivetvManager.AddInfoToProgramDto(list, options.Fields, user); - Task.WaitAll(task); + LivetvManager.AddInfoToProgramDto(new[] { (item, dto) }, options.Fields, user).GetAwaiter().GetResult(); } if (item is IItemByName itemByName diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs index 7ebc800b9..b525f5a2f 100644 --- a/Emby.Server.Implementations/IO/LibraryMonitor.cs +++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs @@ -267,7 +267,7 @@ namespace Emby.Server.Implementations.IO if (_fileSystemWatchers.TryAdd(path, newWatcher)) { newWatcher.EnableRaisingEvents = true; - _logger.LogInformation("Watching directory " + path); + _logger.LogInformation("Watching directory {Path}", path); } else { diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 2dbb569c6..559da7f5c 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -333,8 +333,7 @@ namespace Emby.Server.Implementations.Library { try { - var task = BaseItem.ChannelManager.DeleteItem(item); - Task.WaitAll(task); + BaseItem.ChannelManager.DeleteItem(item).GetAwaiter().GetResult(); } catch (ArgumentException) { diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 60720dd2f..9e3f62276 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -151,7 +151,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio { if (parser.IsMultiPart(path)) { - logger.LogDebug("Found multi-disc folder: " + path); + logger.LogDebug("Found multi-disc folder: {Path}", path); Interlocked.Increment(ref discSubfolderCount); } else diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 367f3cb9e..644f9050d 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -957,7 +957,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV public async Task GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List currentLiveStreams, CancellationToken cancellationToken) { - _logger.LogInformation("Streaming Channel " + channelId); + _logger.LogInformation("Streaming Channel {Id}", channelId); var result = string.IsNullOrEmpty(streamId) ? null : diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 8688688e9..5726d7158 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -87,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV ErrorDialog = false }; - var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments; - _logger.LogInformation(commandLineLogMessage); + _logger.LogInformation("{Filename} {Arguments}", processStartInfo.FileName, processStartInfo.Arguments); var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt"); Directory.CreateDirectory(Path.GetDirectoryName(logFilePath)); @@ -97,7 +96,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV _logFileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false); - await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false); + await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + processStartInfo.FileName + " " + processStartInfo.Arguments + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false); _process = new Process { diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index f2cdfeb16..21a7f4f5f 100644 --- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -638,7 +638,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { try { - _logger.LogInformation(Name + ": Cancelling"); + _logger.LogInformation("{Name}: Cancelling", Name); token.Cancel(); } catch (Exception ex) @@ -652,16 +652,16 @@ namespace Emby.Server.Implementations.ScheduledTasks { try { - _logger.LogInformation(Name + ": Waiting on Task"); + _logger.LogInformation("{Name}: Waiting on Task", Name); var exited = task.Wait(2000); if (exited) { - _logger.LogInformation(Name + ": Task exited"); + _logger.LogInformation("{Name}: Task exited", Name); } else { - _logger.LogInformation(Name + ": Timed out waiting for task to stop"); + _logger.LogInformation("{Name}: Timed out waiting for task to stop", Name); } } catch (Exception ex) @@ -674,7 +674,7 @@ namespace Emby.Server.Implementations.ScheduledTasks { try { - _logger.LogDebug(Name + ": Disposing CancellationToken"); + _logger.LogDebug("{Name}: Disposing CancellationToken", Name); token.Dispose(); } catch (Exception ex) diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs index 4a022c5db..ef95ebf94 100644 --- a/Emby.Server.Implementations/Updates/InstallationManager.cs +++ b/Emby.Server.Implementations/Updates/InstallationManager.cs @@ -571,7 +571,7 @@ namespace Emby.Server.Implementations.Updates ?? _pluginManager.Plugins.FirstOrDefault(p => p.Name.Equals(package.Name, StringComparison.OrdinalIgnoreCase) && p.Version.Equals(package.Version)); await PerformPackageInstallation(package, plugin?.Manifest.Status ?? PluginStatus.Active, cancellationToken).ConfigureAwait(false); - _logger.LogInformation(plugin == null ? "New plugin installed: {PluginName} {PluginVersion}" : "Plugin updated: {PluginName} {PluginVersion}", package.Name, package.Version); + _logger.LogInformation("Plugin {Action}: {PluginName} {PluginVersion}", plugin == null ? "installed" : "updated", package.Name, package.Version); return plugin != null; } diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 049fd503b..caa3d2368 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1391,7 +1391,7 @@ namespace Jellyfin.Api.Controllers } else { - _logger.LogError("Invalid HLS segment container: " + segmentFormat); + _logger.LogError("Invalid HLS segment container: {SegmentFormat}", segmentFormat); } var maxMuxingQueueSize = _encodingOptions.MaxMuxingQueueSize > 128 diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs index f435bbf00..9d80070eb 100644 --- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs +++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs @@ -543,8 +543,7 @@ namespace Jellyfin.Api.Helpers state, cancellationTokenSource); - var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; - _logger.LogInformation(commandLineLogMessage); + _logger.LogInformation("{Filename} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments); var logFilePrefix = "FFmpeg.Transcode-"; if (state.VideoRequest != null @@ -562,8 +561,9 @@ namespace Jellyfin.Api.Helpers // FFmpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory. Stream logStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous); + var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments; var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(request.Path + Environment.NewLine + Environment.NewLine + JsonSerializer.Serialize(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine); - await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false); + await logStream.WriteAsync(commandLineLogMessageBytes, cancellationTokenSource.Token).ConfigureAwait(false); process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state); diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs index 7b32d76ba..0136d9f86 100644 --- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs +++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingThrottler.cs @@ -197,7 +197,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos } } - _logger.LogDebug("No throttle data for " + path); + _logger.LogDebug("No throttle data for {Path}", path); return false; } diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index ffd1c7f0a..ec1ebaabe 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -425,7 +425,7 @@ namespace MediaBrowser.Controller.Entities { if (item.IsFileProtocol) { - Logger.LogDebug("Removed item: " + item.Path); + Logger.LogDebug("Removed item: {Path}", item.Path); item.SetParent(null); LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false); @@ -807,7 +807,7 @@ namespace MediaBrowser.Controller.Entities { if (this is not ICollectionFolder) { - Logger.LogDebug("Query requires post-filtering due to LinkedChildren. Type: " + GetType().Name); + Logger.LogDebug("{Type}: Query requires post-filtering due to LinkedChildren.", GetType().Name); return true; } } diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index e6511ca8d..7d62fb6e1 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -100,32 +100,6 @@ namespace MediaBrowser.Controller.MediaEncoding /// Location of video image. Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken); - /// - /// Extracts the video images on interval. - /// - /// Input file. - /// Video container type. - /// Media stream information. - /// Media source information. - /// Video 3D format. - /// Time interval. - /// Directory to write images. - /// Filename prefix to use. - /// Maximum width of image. - /// CancellationToken to use for operation. - /// A task. - Task ExtractVideoImagesOnInterval( - string inputFile, - string container, - MediaStream videoStream, - MediaSourceInfo mediaSource, - Video3DFormat? threedFormat, - TimeSpan interval, - string targetDirectory, - string filenamePrefix, - int? maxWidth, - CancellationToken cancellationToken); - /// /// Gets the media info. /// diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index 80eb45423..777fe6774 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs @@ -149,7 +149,7 @@ namespace MediaBrowser.LocalMetadata.Parsers } else { - Logger.LogWarning("Invalid Added value found: " + val); + Logger.LogWarning("Invalid Added value found: {Value}", val); } } diff --git a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs index a524aeaa9..9ebc0d0cf 100644 --- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs +++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs @@ -223,11 +223,10 @@ namespace MediaBrowser.MediaEncoding.Attachments if (failed) { - var msg = $"ffmpeg attachment extraction failed for {inputPath} to {outputPath}"; + _logger.LogError("ffmpeg attachment extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath); - _logger.LogError(msg); - - throw new InvalidOperationException(msg); + throw new InvalidOperationException( + string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath)); } else { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index fbc7ba72f..a2bac7b49 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -682,11 +682,9 @@ namespace MediaBrowser.MediaEncoding.Encoder if (exitCode == -1 || !file.Exists || file.Length == 0) { - var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputPath); + _logger.LogError("ffmpeg image extraction failed for {Path}", inputPath); - _logger.LogError(msg); - - throw new FfmpegException(msg); + throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputPath)); } return tempExtractPath; @@ -705,117 +703,6 @@ namespace MediaBrowser.MediaEncoding.Encoder return time.ToString(@"hh\:mm\:ss\.fff", CultureInfo.InvariantCulture); } - public async Task ExtractVideoImagesOnInterval( - string inputFile, - string container, - MediaStream videoStream, - MediaSourceInfo mediaSource, - Video3DFormat? threedFormat, - TimeSpan interval, - string targetDirectory, - string filenamePrefix, - int? maxWidth, - CancellationToken cancellationToken) - { - var inputArgument = GetInputArgument(inputFile, mediaSource); - - var vf = "fps=fps=1/" + interval.TotalSeconds.ToString(CultureInfo.InvariantCulture); - - if (maxWidth.HasValue) - { - var maxWidthParam = maxWidth.Value.ToString(CultureInfo.InvariantCulture); - - vf += string.Format(CultureInfo.InvariantCulture, ",scale=min(iw\\,{0}):trunc(ow/dar/2)*2", maxWidthParam); - } - - Directory.CreateDirectory(targetDirectory); - var outputPath = Path.Combine(targetDirectory, filenamePrefix + "%05d.jpg"); - - var args = string.Format(CultureInfo.InvariantCulture, "-i {0} -threads {3} -v quiet {2} -f image2 \"{1}\"", inputArgument, outputPath, vf, _threads); - - if (!string.IsNullOrWhiteSpace(container)) - { - var inputFormat = EncodingHelper.GetInputFormat(container); - if (!string.IsNullOrWhiteSpace(inputFormat)) - { - args = "-f " + inputFormat + " " + args; - } - } - - var processStartInfo = new ProcessStartInfo - { - CreateNoWindow = true, - UseShellExecute = false, - FileName = _ffmpegPath, - Arguments = args, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false - }; - - _logger.LogInformation(processStartInfo.FileName + " " + processStartInfo.Arguments); - - await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); - - bool ranToCompletion = false; - - var process = new Process - { - StartInfo = processStartInfo, - EnableRaisingEvents = true - }; - using (var processWrapper = new ProcessWrapper(process, this)) - { - try - { - StartProcess(processWrapper); - - // Need to give ffmpeg enough time to make all the thumbnails, which could be a while, - // but we still need to detect if the process hangs. - // Making the assumption that as long as new jpegs are showing up, everything is good. - - bool isResponsive = true; - int lastCount = 0; - - while (isResponsive) - { - if (await process.WaitForExitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false)) - { - ranToCompletion = true; - break; - } - - cancellationToken.ThrowIfCancellationRequested(); - - var jpegCount = _fileSystem.GetFilePaths(targetDirectory) - .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase)); - - isResponsive = jpegCount > lastCount; - lastCount = jpegCount; - } - - if (!ranToCompletion) - { - StopProcess(processWrapper, 1000); - } - } - finally - { - _thumbnailResourcePool.Release(); - } - - var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; - - if (exitCode == -1) - { - var msg = string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", inputArgument); - - _logger.LogError(msg); - - throw new FfmpegException(msg); - } - } - } - private void StartProcess(ProcessWrapper process) { process.Process.Start(); diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs index 2b2de2ff6..89365a516 100644 --- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs +++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs @@ -636,17 +636,14 @@ namespace MediaBrowser.MediaEncoding.Subtitles if (failed) { - var msg = $"ffmpeg subtitle extraction failed for {inputPath} to {outputPath}"; + _logger.LogError("ffmpeg subtitle extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath); - _logger.LogError(msg); - - throw new FfmpegException(msg); + throw new FfmpegException( + string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath)); } else { - var msg = $"ffmpeg subtitle extraction completed for {inputPath} to {outputPath}"; - - _logger.LogInformation(msg); + _logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath); } if (string.Equals(outputCodec, "ass", StringComparison.OrdinalIgnoreCase)) diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index 2c893ac9f..3011d65a6 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers } else { - Logger.LogInformation("Unrecognized series status: " + status); + Logger.LogInformation("Unrecognized series status: {Status}", status); } } diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 3bced438c..e14c1c427 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -44,9 +44,13 @@ + + + + -- cgit v1.2.3 From f73a7a6ed8554a188809c955ddccb48445f4dd71 Mon Sep 17 00:00:00 2001 From: Joe Rogers <1337joe@gmail.com> Date: Fri, 12 Nov 2021 16:11:15 +0100 Subject: Use ImageFormat instead of string for extension --- .../MediaEncoding/IMediaEncoder.cs | 5 +-- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 36 ++++++++++++---------- .../MediaInfo/EmbeddedImageProvider.cs | 13 ++++---- .../MediaInfo/EmbeddedImageProviderTests.cs | 18 +++++------ 4 files changed, 38 insertions(+), 34 deletions(-) (limited to 'MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 7d62fb6e1..1418e583e 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; @@ -95,10 +96,10 @@ namespace MediaBrowser.Controller.MediaEncoding /// Media source information. /// Media stream information. /// Index of the stream to extract from. - /// The extension of the file to write, including the '.'. + /// The format of the file to write. /// CancellationToken to use for operation. /// Location of video image. - Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken); + Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken); /// /// Gets the media info. diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index a2bac7b49..1c97a1982 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -19,6 +19,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Probing; using MediaBrowser.Model.Dlna; +using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; @@ -478,17 +479,17 @@ namespace MediaBrowser.MediaEncoding.Encoder Protocol = MediaProtocol.File }; - return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ".jpg", cancellationToken); + return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ImageFormat.Jpg, cancellationToken); } public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ".jpg", cancellationToken); + return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ImageFormat.Jpg, cancellationToken); } - public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken) + public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken) { - return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, outputExtension, cancellationToken); + return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, targetFormat, cancellationToken); } private async Task ExtractImage( @@ -500,7 +501,7 @@ namespace MediaBrowser.MediaEncoding.Encoder bool isAudio, Video3DFormat? threedFormat, TimeSpan? offset, - string outputExtension, + ImageFormat? targetFormat, CancellationToken cancellationToken) { var inputArgument = GetInputArgument(inputFile, mediaSource); @@ -510,7 +511,7 @@ namespace MediaBrowser.MediaEncoding.Encoder // The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter. try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, outputExtension, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, targetFormat, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -523,7 +524,7 @@ namespace MediaBrowser.MediaEncoding.Encoder try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, outputExtension, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, targetFormat, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -536,7 +537,7 @@ namespace MediaBrowser.MediaEncoding.Encoder try { - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, outputExtension, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, targetFormat, cancellationToken).ConfigureAwait(false); } catch (ArgumentException) { @@ -548,24 +549,25 @@ namespace MediaBrowser.MediaEncoding.Encoder } } - return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, outputExtension, cancellationToken).ConfigureAwait(false); + return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, targetFormat, cancellationToken).ConfigureAwait(false); } - private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, string outputExtension, CancellationToken cancellationToken) + private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, ImageFormat? targetFormat, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(inputPath)) { throw new ArgumentNullException(nameof(inputPath)); } - if (string.IsNullOrEmpty(outputExtension)) + var outputExtension = targetFormat switch { - outputExtension = ".jpg"; - } - else if (outputExtension[0] != '.') - { - outputExtension = "." + outputExtension; - } + ImageFormat.Bmp => ".bmp", + ImageFormat.Gif => ".gif", + ImageFormat.Jpg => ".jpg", + ImageFormat.Png => ".png", + ImageFormat.Webp => ".webp", + _ => ".jpg" + }; var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension); Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath)); diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs index ca0e72e49..79189416e 100644 --- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs +++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs @@ -156,13 +156,14 @@ namespace MediaBrowser.Providers.MediaInfo } } + var format = ImageFormat.Jpg; string extractedImagePath = - await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, ".jpg", cancellationToken) + await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, format, cancellationToken) .ConfigureAwait(false); return new DynamicImageResponse { - Format = ImageFormat.Jpg, + Format = format, HasImage = true, Path = extractedImagePath, Protocol = MediaProtocol.File @@ -180,10 +181,6 @@ namespace MediaBrowser.Providers.MediaInfo extension = ".jpg"; } - string extractedAttachmentPath = - await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken) - .ConfigureAwait(false); - ImageFormat format = extension switch { ".bmp" => ImageFormat.Bmp, @@ -194,6 +191,10 @@ namespace MediaBrowser.Providers.MediaInfo _ => ImageFormat.Jpg }; + string extractedAttachmentPath = + await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, format, cancellationToken) + .ConfigureAwait(false); + return new DynamicImageResponse { Format = format, diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs index 19391ba68..b6d6c3b25 100644 --- a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs +++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs @@ -57,7 +57,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo for (int i = 1; i <= targetIndex; i++) { var name = i == targetIndex ? filename : "unmatched"; - attachments.Add(new() + attachments.Add(new () { FileName = name, MimeType = mimetype, @@ -66,8 +66,8 @@ namespace Jellyfin.Providers.Tests.MediaInfo } var mediaEncoder = new Mock(MockBehavior.Strict); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((_, _, _, _, index, ext, _) => Task.FromResult(pathPrefix + index + ext)); + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((_, _, _, _, index, ext, _) => Task.FromResult(pathPrefix + index + "." + ext)); var embeddedImageProvider = new EmbeddedImageProvider(mediaEncoder.Object); var input = GetMovie(attachments, new List()); @@ -81,7 +81,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo else { Assert.True(actual.HasImage); - Assert.Equal(pathPrefix + targetIndex + "." + format, actual.Path, StringComparer.InvariantCultureIgnoreCase); + Assert.Equal(pathPrefix + targetIndex + "." + format, actual.Path, StringComparer.OrdinalIgnoreCase); Assert.Equal(format, actual.Format); } } @@ -97,7 +97,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo for (int i = 1; i <= targetIndex; i++) { var comment = i == targetIndex ? label : "unmatched"; - streams.Add(new() + streams.Add(new () { Type = MediaStreamType.EmbeddedImage, Index = i, @@ -107,11 +107,11 @@ namespace Jellyfin.Providers.Tests.MediaInfo var pathPrefix = "path"; var mediaEncoder = new Mock(MockBehavior.Strict); - mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((_, _, _, stream, index, ext, _) => + mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) + .Returns((_, _, _, stream, index, ext, _) => { Assert.Equal(streams[index - 1], stream); - return Task.FromResult(pathPrefix + index + ext); + return Task.FromResult(pathPrefix + index + "." + ext); }); var embeddedImageProvider = new EmbeddedImageProvider(mediaEncoder.Object); @@ -122,7 +122,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo Assert.Equal(hasImage, actual.HasImage); if (hasImage) { - Assert.Equal(pathPrefix + targetIndex + ".jpg", actual.Path); + Assert.Equal(pathPrefix + targetIndex + ".jpg", actual.Path, StringComparer.OrdinalIgnoreCase); Assert.Equal(ImageFormat.Jpg, actual.Format); } } -- cgit v1.2.3