diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 1fe255385..cb52cafed 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -46,6 +46,7 @@
- [fruhnow](https://github.com/fruhnow)
- [geilername](https://github.com/geilername)
- [gnattu](https://github.com/gnattu)
+ - [GodTamIt](https://github.com/GodTamIt)
- [grafixeyehero](https://github.com/grafixeyehero)
- [h1nk](https://github.com/h1nk)
- [hawken93](https://github.com/hawken93)
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index a6e70e72d..54ac06276 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -76,7 +76,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
/// Optional. Whether to enable the MpegtsM2Ts mode.
- /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.
+ /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.
/// Optional. Specify a subtitle codec to encode to.
/// Optional. The transcoding reason.
/// Optional. The index of the audio stream to use. If omitted the first audio stream will be used.
@@ -241,7 +241,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
/// Optional. Whether to enable the MpegtsM2Ts mode.
- /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.
+ /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.
/// Optional. Specify a subtitle codec to encode to.
/// Optional. The transcoding reason.
/// Optional. The index of the audio stream to use. If omitted the first audio stream will be used.
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 35435b007..fbfb47215 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -150,7 +150,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
/// Optional. Whether to enable the MpegtsM2Ts mode.
- /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.
+ /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.
/// Optional. Specify a subtitle codec to encode to.
/// Optional. The transcoding reason.
/// Optional. The index of the audio stream to use. If omitted the first audio stream will be used.
@@ -316,7 +316,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
/// Optional. Whether to enable the MpegtsM2Ts mode.
- /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.
+ /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.
/// Optional. Specify a subtitle codec to encode to.
/// Optional. The transcoding reason.
/// Optional. The index of the audio stream to use. If omitted the first audio stream will be used.
@@ -482,7 +482,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
/// Optional. Whether to enable the MpegtsM2Ts mode.
- /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.
+ /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.
/// Optional. Specify a subtitle codec to encode to.
/// Optional. The transcoding reason.
/// Optional. The index of the audio stream to use. If omitted the first audio stream will be used.
@@ -813,7 +813,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
/// Optional. Whether to enable the MpegtsM2Ts mode.
- /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.
+ /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.
/// Optional. Specify a subtitle codec to encode to.
/// Optional. The transcoding reason.
/// Optional. The index of the audio stream to use. If omitted the first audio stream will be used.
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
index 5c941b276..8560df2ea 100644
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ b/Jellyfin.Api/Controllers/VideoHlsController.cs
@@ -140,7 +140,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
/// Optional. Whether to enable the MpegtsM2Ts mode.
- /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.
+ /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.
/// Optional. Specify a subtitle codec to encode to.
/// Optional. The transcoding reason.
/// Optional. The index of the audio stream to use. If omitted the first audio stream will be used.
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 29a25fa6a..5c5057c83 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -310,7 +310,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
/// Optional. Whether to enable the MpegtsM2Ts mode.
- /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.
+ /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.
/// Optional. Specify a subtitle codec to encode to.
/// Optional. The transcoding reason.
/// Optional. The index of the audio stream to use. If omitted the first audio stream will be used.
@@ -456,9 +456,9 @@ namespace Jellyfin.Api.Controllers
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
await new ProgressiveFileCopier(state.DirectStreamProvider, null, _transcodingJobHelper, CancellationToken.None)
- {
- AllowEndOfFile = false
- }.WriteToAsync(Response.Body, CancellationToken.None)
+ {
+ AllowEndOfFile = false
+ }.WriteToAsync(Response.Body, CancellationToken.None)
.ConfigureAwait(false);
// TODO (moved from MediaBrowser.Api): Don't hardcode contentType
@@ -495,9 +495,9 @@ namespace Jellyfin.Api.Controllers
if (state.MediaSource.IsInfiniteStream)
{
await new ProgressiveFileCopier(state.MediaPath, null, _transcodingJobHelper, CancellationToken.None)
- {
- AllowEndOfFile = false
- }.WriteToAsync(Response.Body, CancellationToken.None)
+ {
+ AllowEndOfFile = false
+ }.WriteToAsync(Response.Body, CancellationToken.None)
.ConfigureAwait(false);
return File(Response.Body, contentType);
@@ -570,7 +570,7 @@ namespace Jellyfin.Api.Controllers
/// Optional. The limit of how many cpu cores to use.
/// The live stream id.
/// Optional. Whether to enable the MpegtsM2Ts mode.
- /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.
+ /// Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.
/// Optional. Specify a subtitle codec to encode to.
/// Optional. The transcoding reason.
/// Optional. The index of the audio stream to use. If omitted the first audio stream will be used.
diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs
index 1c2b301cd..0041251e3 100644
--- a/Jellyfin.Api/Helpers/StreamingHelpers.cs
+++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs
@@ -433,7 +433,9 @@ namespace Jellyfin.Api.Helpers
return ".ogv";
}
- if (string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoCodec, "vp8", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "vp9", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "vpx", StringComparison.OrdinalIgnoreCase))
{
return ".webm";
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 1be3a1ca4..ec44150a2 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -206,11 +206,17 @@ namespace MediaBrowser.Controller.MediaEncoding
return GetH264Encoder(state, encodingOptions);
}
- if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(codec, "vp8", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
{
return "libvpx";
}
+ if (string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
+ {
+ return "libvpx-vp9";
+ }
+
if (string.Equals(codec, "wmv", StringComparison.OrdinalIgnoreCase))
{
return "wmv2";
@@ -442,7 +448,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase))
{
- return "vpx";
+ // TODO: this may not always mean VP8, as the codec ages
+ return "vp8";
}
if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase))
@@ -560,12 +567,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isOpenclTonemappingSupported && !isVppTonemappingSupported)
{
- arg.Append("-init_hw_device vaapi=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 ");
+ arg.Append("-init_hw_device vaapi=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 ");
}
else
{
@@ -774,49 +781,37 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
{
- var bitrate = state.OutputVideoBitrate;
-
- if (bitrate.HasValue)
+ if (state.OutputVideoBitrate == null)
{
- if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
- {
- // When crf is used with vpx, b:v becomes a max rate
- // https://trac.ffmpeg.org/wiki/Encode/VP9
- return string.Format(
- CultureInfo.InvariantCulture,
- " -maxrate:v {0} -bufsize:v {1} -b:v {0}",
- bitrate.Value,
- bitrate.Value * 2);
- }
-
- if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
- {
- return string.Format(
- CultureInfo.InvariantCulture,
- " -b:v {0}",
- bitrate.Value);
- }
-
- if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
- {
- // h264
- return string.Format(
- CultureInfo.InvariantCulture,
- " -maxrate {0} -bufsize {1}",
- bitrate.Value,
- bitrate.Value * 2);
- }
-
- // h264
- return string.Format(
- CultureInfo.InvariantCulture,
- " -b:v {0} -maxrate {0} -bufsize {1}",
- bitrate.Value,
- bitrate.Value * 2);
+ return string.Empty;
}
- return string.Empty;
+ int bitrate = state.OutputVideoBitrate.Value;
+
+ // Currently use the same buffer size for all encoders
+ int bufsize = bitrate * 2;
+
+ if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "libvpx-vp9", StringComparison.OrdinalIgnoreCase))
+ {
+ // When crf is used with vpx, b:v becomes a max rate
+ // https://trac.ffmpeg.org/wiki/Encode/VP8
+ // https://trac.ffmpeg.org/wiki/Encode/VP9
+ return FormattableString.Invariant($" -maxrate:v {bitrate} -bufsize:v {bufsize} -b:v {bitrate}");
+ }
+
+ if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
+ {
+ return FormattableString.Invariant($" -b:v {bitrate}");
+ }
+
+ if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
+ {
+ return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
+ }
+
+ return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
@@ -1199,7 +1194,7 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -header_insertion_mode gop -gops_per_idr 1";
}
}
- else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm
+ else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // vp8
{
// Values 0-3, 0 being highest quality but slower
var profileScore = 0;
@@ -1227,6 +1222,55 @@ namespace MediaBrowser.Controller.MediaEncoding
qmin,
qmax);
}
+ else if (string.Equals(videoEncoder, "libvpx-vp9", StringComparison.OrdinalIgnoreCase)) // vp9
+ {
+ // When `-deadline` is set to `good` or `best`, `-cpu-used` ranges from 0-5.
+ // When `-deadline` is set to `realtime`, `-cpu-used` ranges from 0-15.
+ // Resources:
+ // * https://trac.ffmpeg.org/wiki/Encode/VP9
+ // * https://superuser.com/questions/1586934
+ // * https://developers.google.com/media/vp9
+ param += encodingOptions.EncoderPreset switch
+ {
+ "veryslow" => " -deadline best -cpu-used 0",
+ "slower" => " -deadline best -cpu-used 2",
+ "slow" => " -deadline best -cpu-used 3",
+ "medium" => " -deadline good -cpu-used 0",
+ "fast" => " -deadline good -cpu-used 1",
+ "faster" => " -deadline good -cpu-used 2",
+ "veryfast" => " -deadline good -cpu-used 3",
+ "superfast" => " -deadline good -cpu-used 4",
+ "ultrafast" => " -deadline good -cpu-used 5",
+ _ => " -deadline good -cpu-used 1"
+ };
+
+ // TODO: until VP9 gets its own CRF setting, base CRF on H.265.
+ int h265Crf = encodingOptions.H265Crf;
+ int defaultVp9Crf = 31;
+ if (h265Crf >= 0 && h265Crf <= 51)
+ {
+ // This conversion factor is chosen to match the default CRF for H.265 to the
+ // recommended 1080p CRF from Google. The factor also maps the logarithmic CRF
+ // scale of x265 [0, 51] to that of VP9 [0, 63] relatively well.
+
+ // Resources:
+ // * https://developers.google.com/media/vp9/settings/vod
+ const float H265ToVp9CrfConversionFactor = 1.12F;
+
+ var vp9Crf = Convert.ToInt32(h265Crf * H265ToVp9CrfConversionFactor);
+
+ // Encoder allows for CRF values in the range [0, 63].
+ vp9Crf = Math.Clamp(vp9Crf, 0, 63);
+
+ param += FormattableString.Invariant($" -crf {vp9Crf}");
+ }
+ else
+ {
+ param += FormattableString.Invariant($" -crf {defaultVp9Crf}");
+ }
+
+ param += " -row-mt 1 -profile 1";
+ }
else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase))
{
param += " -mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
@@ -3151,20 +3195,16 @@ namespace MediaBrowser.Controller.MediaEncoding
#nullable enable
public static int GetNumberOfThreads(EncodingJobInfo? state, EncodingOptions encodingOptions, string? outputVideoCodec)
{
- if (string.Equals(outputVideoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
- {
- // per docs:
- // -threads number of threads to use for encoding, can't be 0 [auto] with VP8
- // (recommended value : number of real cores - 1)
- return Math.Max(Environment.ProcessorCount - 1, 1);
- }
+ // VP8 and VP9 encoders must have their thread counts set.
+ bool mustSetThreadCount = string.Equals(outputVideoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(outputVideoCodec, "libvpx-vp9", StringComparison.OrdinalIgnoreCase);
var threads = state?.BaseRequest.CpuCoreLimit ?? encodingOptions.EncodingThreadCount;
- // Automatic
if (threads <= 0)
{
- return 0;
+ // Automatically set thread count
+ return mustSetThreadCount ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
}
else if (threads >= Environment.ProcessorCount)
{