aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines-package.yml2
-rw-r--r--.github/workflows/codeql-analysis.yml6
-rw-r--r--.github/workflows/openapi.yml4
-rw-r--r--Directory.Packages.props30
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs23
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs21
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs47
-rw-r--r--Emby.Server.Implementations/Localization/Core/ne.json16
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs33
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs20
-rw-r--r--Jellyfin.Api/Helpers/TranscodingJobHelper.cs2
-rw-r--r--Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs4
-rw-r--r--Jellyfin.Api/Models/UserDtos/CreateUserByName.cs2
-rw-r--r--Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs2
-rw-r--r--Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs2
-rw-r--r--MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs65
-rw-r--r--MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs5
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs33
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs38
-rw-r--r--MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs17
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs42
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs17
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs5
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs12
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs14
-rw-r--r--MediaBrowser.Model/Dlna/MediaOptions.cs2
-rw-r--r--MediaBrowser.Model/Users/UserPolicy.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs63
-rw-r--r--MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs8
-rw-r--r--deployment/Dockerfile.centos.amd642
-rw-r--r--deployment/Dockerfile.fedora.amd642
-rw-r--r--deployment/Dockerfile.ubuntu.amd642
-rw-r--r--deployment/Dockerfile.ubuntu.arm642
-rw-r--r--deployment/Dockerfile.ubuntu.armhf2
-rw-r--r--src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj2
-rw-r--r--src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs20
-rw-r--r--src/Jellyfin.Drawing/ImageProcessor.cs2
39 files changed, 228 insertions, 348 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 1618237f1..c28b1bf7f 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -47,7 +47,7 @@ jobs:
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
+ - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) --label "org.opencontainers.image.url=$(Build.Repository.Uri)" --label "org.opencontainers.image.revision=$(Build.SourceVersion)" deployment'
displayName: 'Build Dockerfile'
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 9f1be0232..f83b38949 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -27,11 +27,11 @@ jobs:
dotnet-version: '7.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6
+ uses: github/codeql-action/init@f6e388ebf0efc915c6c5b165b019ee61a6746a38 # v2.20.1
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6
+ uses: github/codeql-action/autobuild@f6e388ebf0efc915c6c5b165b019ee61a6746a38 # v2.20.1
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@83f0fe6c4988d98a455712a27f0255212bba9bd4 # v2.3.6
+ uses: github/codeql-action/analyze@f6e388ebf0efc915c6c5b165b019ee61a6746a38 # v2.20.1
diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml
index ad1cedd52..d3dfd0a6a 100644
--- a/.github/workflows/openapi.yml
+++ b/.github/workflows/openapi.yml
@@ -45,10 +45,12 @@ jobs:
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
- name: Checkout common ancestor
+ env:
+ HEAD_REF: ${{ github.head_ref }}
run: |
git remote add upstream https://github.com/${{ github.event.pull_request.base.repo.full_name }}
git -c protocol.version=2 fetch --prune --progress --no-recurse-submodules upstream +refs/heads/*:refs/remotes/upstream/* +refs/tags/*:refs/tags/*
- ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/${{ github.head_ref }})
+ ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
git checkout --progress --force $ANCESTOR_REF
- name: Setup .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 49081399e..c3532467a 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -23,13 +23,13 @@
<PackageVersion Include="libse" Version="3.6.13" />
<PackageVersion Include="LrcParser" Version="2023.524.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
- <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.5" />
- <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.5" />
+ <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.8" />
+ <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.8" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.5" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.5" />
- <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.5" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.8" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.8" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.8" />
+ <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.8" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
@@ -38,14 +38,14 @@
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
- <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.5" />
- <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.5" />
+ <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.8" />
+ <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" />
- <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
+ <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
- <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
+ <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.3" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
<PackageVersion Include="MimeTypes" Version="2.4.0" />
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
@@ -62,22 +62,24 @@
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
- <PackageVersion Include="Serilog.Sinks.Graylog" Version="3.0.0" />
+ <PackageVersion Include="Serilog.Sinks.Graylog" Version="3.0.1" />
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
- <PackageVersion Include="SharpFuzz" Version="2.0.2" />
+ <PackageVersion Include="SharpFuzz" Version="2.1.0" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.3" />
<PackageVersion Include="SkiaSharp.Svg" Version="1.60.0" />
+ <PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.3" />
+ <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.8.2.3" />
<PackageVersion Include="SkiaSharp" Version="2.88.3" />
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
<PackageVersion Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.5" />
- <PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.435" />
+ <PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.507" />
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.4.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageVersion Include="System.Globalization" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="7.0.0" />
- <PackageVersion Include="System.Text.Json" Version="7.0.2" />
+ <PackageVersion Include="System.Text.Json" Version="7.0.3" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="7.0.0" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="TMDbLib" Version="2.0.0" />
diff --git a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index 2e3988f9e..be36bbd2c 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -12,6 +12,7 @@ using System.Threading.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Events;
using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@@ -26,12 +27,8 @@ namespace Emby.Server.Implementations.EntryPoints
{
public class LibraryChangedNotifier : IServerEntryPoint
{
- /// <summary>
- /// The library update duration.
- /// </summary>
- private const int LibraryUpdateDuration = 30000;
-
private readonly ILibraryManager _libraryManager;
+ private readonly IServerConfigurationManager _configurationManager;
private readonly IProviderManager _providerManager;
private readonly ISessionManager _sessionManager;
private readonly IUserManager _userManager;
@@ -51,12 +48,14 @@ namespace Emby.Server.Implementations.EntryPoints
public LibraryChangedNotifier(
ILibraryManager libraryManager,
+ IServerConfigurationManager configurationManager,
ISessionManager sessionManager,
IUserManager userManager,
ILogger<LibraryChangedNotifier> logger,
IProviderManager providerManager)
{
_libraryManager = libraryManager;
+ _configurationManager = configurationManager;
_sessionManager = sessionManager;
_userManager = userManager;
_logger = logger;
@@ -196,12 +195,12 @@ namespace Emby.Server.Implementations.EntryPoints
LibraryUpdateTimer = new Timer(
LibraryUpdateTimerCallback,
null,
- LibraryUpdateDuration,
- Timeout.Infinite);
+ TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration),
+ Timeout.InfiniteTimeSpan);
}
else
{
- LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite);
+ LibraryUpdateTimer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
}
if (e.Item.GetParent() is Folder parent)
@@ -229,11 +228,11 @@ namespace Emby.Server.Implementations.EntryPoints
{
if (LibraryUpdateTimer is null)
{
- LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite);
+ LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
}
else
{
- LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite);
+ LibraryUpdateTimer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
}
_itemsUpdated.Add(e.Item);
@@ -256,11 +255,11 @@ namespace Emby.Server.Implementations.EntryPoints
{
if (LibraryUpdateTimer is null)
{
- LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite);
+ LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
}
else
{
- LibraryUpdateTimer.Change(LibraryUpdateDuration, Timeout.Infinite);
+ LibraryUpdateTimer.Change(TimeSpan.FromSeconds(_configurationManager.Configuration.LibraryUpdateDuration), Timeout.InfiniteTimeSpan);
}
if (e.Parent is Folder parent)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index bcb42e162..acf3964c8 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -30,12 +30,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
{
public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
- private static readonly string[] _disallowedSharedStreamExtensions =
+ private static readonly string[] _disallowedMimeTypes =
{
- ".mkv",
- ".mp4",
- ".m3u8",
- ".mpd"
+ "video/x-matroska",
+ "video/mp4",
+ "application/vnd.apple.mpegurl",
+ "application/mpegurl",
+ "application/x-mpegurl",
+ "video/vnd.mpeg.dash.mpd"
};
private readonly IHttpClientFactory _httpClientFactory;
@@ -118,9 +120,14 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping)
{
- var extension = Path.GetExtension(mediaSource.Path) ?? string.Empty;
+ using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path);
+ using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
+ .SendAsync(message, cancellationToken)
+ .ConfigureAwait(false);
- if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
+ response.EnsureSuccessStatusCode();
+
+ if (!_disallowedMimeTypes.Contains(response.Content.Headers.ContentType?.ToString(), StringComparison.OrdinalIgnoreCase))
{
return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper);
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
index e84e1e074..51f46f4da 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/SharedHttpStream.cs
@@ -38,7 +38,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
_httpClientFactory = httpClientFactory;
_appHost = appHost;
OriginalStreamId = originalStreamId;
- EnableStreamSharing = true;
}
public override async Task Open(CancellationToken openCancellationToken)
@@ -59,39 +58,13 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None)
.ConfigureAwait(false);
- var contentType = response.Content.Headers.ContentType?.ToString() ?? string.Empty;
- if (contentType.Contains("matroska", StringComparison.OrdinalIgnoreCase)
- || contentType.Contains("mp4", StringComparison.OrdinalIgnoreCase)
- || contentType.Contains("dash", StringComparison.OrdinalIgnoreCase)
- || contentType.Contains("mpegURL", StringComparison.OrdinalIgnoreCase)
- || contentType.Contains("text/", StringComparison.OrdinalIgnoreCase))
- {
- // Close the stream without any sharing features
- response.Dispose();
- return;
- }
-
- SetTempFilePath("ts");
-
var taskCompletionSource = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_ = StartStreaming(response, taskCompletionSource, LiveStreamCancellationTokenSource.Token);
- // OpenedMediaSource.Protocol = MediaProtocol.File;
- // OpenedMediaSource.Path = tempFile;
- // OpenedMediaSource.ReadAtNativeFramerate = true;
-
MediaSource.Path = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveStreamFiles/" + UniqueId + "/stream.ts";
MediaSource.Protocol = MediaProtocol.Http;
- // OpenedMediaSource.Path = TempFilePath;
- // OpenedMediaSource.Protocol = MediaProtocol.File;
-
- // OpenedMediaSource.Path = _tempFilePath;
- // OpenedMediaSource.Protocol = MediaProtocol.File;
- // OpenedMediaSource.SupportsDirectPlay = false;
- // OpenedMediaSource.SupportsDirectStream = true;
- // OpenedMediaSource.SupportsTranscoding = true;
var res = await taskCompletionSource.Task.ConfigureAwait(false);
if (!res)
{
@@ -108,15 +81,17 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
try
{
Logger.LogInformation("Beginning {StreamType} stream to {FilePath}", GetType().Name, TempFilePath);
- using var message = response;
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
- await StreamHelper.CopyToAsync(
- stream,
- fileStream,
- IODefaults.CopyToBufferSize,
- () => Resolve(openTaskCompletionSource),
- cancellationToken).ConfigureAwait(false);
+ using (response)
+ {
+ await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ await using var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
+ await StreamHelper.CopyToAsync(
+ stream,
+ fileStream,
+ IODefaults.CopyToBufferSize,
+ () => Resolve(openTaskCompletionSource),
+ cancellationToken).ConfigureAwait(false);
+ }
}
catch (OperationCanceledException ex)
{
diff --git a/Emby.Server.Implementations/Localization/Core/ne.json b/Emby.Server.Implementations/Localization/Core/ne.json
index 4c8e820a5..7c6b08fb3 100644
--- a/Emby.Server.Implementations/Localization/Core/ne.json
+++ b/Emby.Server.Implementations/Localization/Core/ne.json
@@ -109,5 +109,19 @@
"Sync": "समकालीन",
"SubtitleDownloadFailureFromForItem": "उपशीर्षकहरू {0} बाट {1} को लागि डाउनलोड गर्न असफल",
"PluginUpdatedWithName": "{0} अद्यावधिक गरिएको थियो",
- "PluginUninstalledWithName": "{0} को स्थापना रद्द गरिएको थियो"
+ "PluginUninstalledWithName": "{0} को स्थापना रद्द गरिएको थियो",
+ "HearingImpaired": "सुन्न नसक्ने",
+ "TaskUpdatePluginsDescription": "स्वचालित रूपमा अद्यावधिक गर्न कन्फिगर गरिएका प्लगइनहरूका लागि अद्यावधिकहरू डाउनलोड र स्थापना गर्दछ।",
+ "TaskCleanTranscode": "सफा ट्रान्सकोड निर्देशिका",
+ "TaskCleanTranscodeDescription": "एक दिन भन्दा पुराना ट्रान्सकोड फाइलहरू मेटाउँछ।",
+ "TaskRefreshChannels": "च्यानलहरू ताजा गर्नुहोस्",
+ "TaskDownloadMissingSubtitlesDescription": "मेटाडेटा कन्फिगरेसनमा आधारित हराइरहेको उपशीर्षकहरूको लागि इन्टरनेट खोज्छ।",
+ "TaskOptimizeDatabase": "डेटाबेस अप्टिमाइज गर्नुहोस्",
+ "TaskOptimizeDatabaseDescription": "डाटाबेस कम्प्याक्ट र खाली ठाउँ काट्छ। पुस्तकालय स्क्यान गरेपछि वा डाटाबेस परिमार्जनलाई संकेत गर्ने अन्य परिवर्तनहरू गरेपछि यो कार्य चलाउँदा कार्यसम्पादनमा सुधार हुन सक्छ।",
+ "TaskKeyframeExtractorDescription": "थप सटीक एचएलएस प्लेलिस्टहरू सिर्जना गर्न भिडियो फाइलहरूबाट कीफ्रेमहरू निकाल्छ। यो कार्य लामो समय सम्म चल्न सक्छ।",
+ "TaskUpdatePlugins": "प्लगइनहरू अपडेट गर्नुहोस्",
+ "TaskRefreshPeopleDescription": "तपाईंको मिडिया लाइब्रेरीमा अभिनेता र निर्देशकहरूको लागि मेटाडेटा अपडेट गर्दछ।",
+ "TaskRefreshChannelsDescription": "इन्टरनेट च्यानल जानकारी ताजा गर्दछ।",
+ "TaskDownloadMissingSubtitles": "छुटेका उपशीर्षकहरू डाउनलोड गर्नुहोस्",
+ "TaskKeyframeExtractor": "कीफ्रेम एक्स्ट्रक्टर"
}
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index 898c673ea..ce684e457 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -1642,9 +1642,11 @@ public class DynamicHlsController : BaseJellyfinApiController
Path.GetFileNameWithoutExtension(outputPath));
}
+ var hlsArguments = GetHlsArguments(isEventPlaylist, state.SegmentLength);
+
return string.Format(
CultureInfo.InvariantCulture,
- "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{12}\" -hls_playlist_type {11} -hls_list_size 0 -y \"{13}\"",
+ "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{11}\" {12} -y \"{13}\"",
inputModifier,
_encodingHelper.GetInputArgument(state, _encodingOptions, segmentContainer),
threads,
@@ -1656,12 +1658,39 @@ public class DynamicHlsController : BaseJellyfinApiController
segmentFormat,
startNumber.ToString(CultureInfo.InvariantCulture),
baseUrlParam,
- isEventPlaylist ? "event" : "vod",
EncodingUtils.NormalizePath(outputTsArg),
+ hlsArguments,
EncodingUtils.NormalizePath(outputPath)).Trim();
}
/// <summary>
+ /// Gets the HLS arguments for transcoding.
+ /// </summary>
+ /// <returns>The command line arguments for HLS transcoding.</returns>
+ private string GetHlsArguments(bool isEventPlaylist, int segmentLength)
+ {
+ var enableThrottling = _encodingOptions.EnableThrottling;
+ var enableSegmentDeletion = _encodingOptions.EnableSegmentDeletion;
+
+ // Only enable segment deletion when throttling is enabled
+ if (enableThrottling && enableSegmentDeletion)
+ {
+ // Store enough segments for configured seconds of playback; this needs to be above throttling settings
+ var segmentCount = _encodingOptions.SegmentKeepSeconds / segmentLength;
+
+ _logger.LogDebug("Using throttling and segment deletion, keeping {0} segments", segmentCount);
+
+ return string.Format(CultureInfo.InvariantCulture, "-hls_list_size {0} -hls_flags delete_segments", segmentCount.ToString(CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ _logger.LogDebug("Using normal playback, is event playlist? {0}", isEventPlaylist);
+
+ return string.Format(CultureInfo.InvariantCulture, "-hls_playlist_type {0} -hls_list_size 0", isEventPlaylist ? "event" : "vod");
+ }
+ }
+
+ /// <summary>
/// Gets the audio arguments for transcoding.
/// </summary>
/// <param name="state">The <see cref="StreamState"/>.</param>
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 7650b861f..80128536d 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -256,8 +256,7 @@ public class ItemsController : BaseJellyfinApiController
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
if (includeItemTypes.Length == 1
- && (includeItemTypes[0] == BaseItemKind.Playlist
- || includeItemTypes[0] == BaseItemKind.BoxSet))
+ && includeItemTypes[0] == BaseItemKind.BoxSet)
{
parentId = null;
}
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index e094d2d77..46c0a8d52 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -969,12 +969,8 @@ public class LibraryController : BaseJellyfinApiController
|| string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase);
}
- var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions
- .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
- .ToArray();
-
- return metadataOptions.Length == 0
- || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase));
+ var metadataOptions = _serverConfigurationManager.GetMetadataOptionsForType(type);
+ return metadataOptions is null || !metadataOptions.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase);
}
private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
@@ -995,15 +991,7 @@ public class LibraryController : BaseJellyfinApiController
|| string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase);
}
- var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions
- .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
- .ToArray();
-
- if (metadataOptions.Length == 0)
- {
- return true;
- }
-
- return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase));
+ var metadataOptions = _serverConfigurationManager.GetMetadataOptionsForType(type);
+ return metadataOptions is null || !metadataOptions.DisabledImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase);
}
}
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index f25a71869..cee8e0f9b 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -660,7 +660,7 @@ public class TranscodingJobHelper : IDisposable
{
if (EnableThrottling(state))
{
- transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem, _mediaEncoder);
+ transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, _loggerFactory.CreateLogger<TranscodingThrottler>(), _serverConfigurationManager, _fileSystem, _mediaEncoder);
transcodingJob.TranscodingThrottler.Start();
}
}
diff --git a/Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs b/Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs
index 75222ed01..cbc3548b1 100644
--- a/Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs
+++ b/Jellyfin.Api/Models/LiveTvDtos/ChannelMappingOptionsDto.cs
@@ -13,12 +13,12 @@ public class ChannelMappingOptionsDto
/// <summary>
/// Gets or sets list of tuner channels.
/// </summary>
- required public IReadOnlyList<TunerChannelMapping> TunerChannels { get; set; }
+ public required IReadOnlyList<TunerChannelMapping> TunerChannels { get; set; }
/// <summary>
/// Gets or sets list of provider channels.
/// </summary>
- required public IReadOnlyList<NameIdPair> ProviderChannels { get; set; }
+ public required IReadOnlyList<NameIdPair> ProviderChannels { get; set; }
/// <summary>
/// Gets or sets list of mappings.
diff --git a/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs b/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs
index 6b6d9682b..4f9fc4e78 100644
--- a/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs
+++ b/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs
@@ -11,7 +11,7 @@ public class CreateUserByName
/// Gets or sets the username.
/// </summary>
[Required]
- required public string Name { get; set; }
+ public required string Name { get; set; }
/// <summary>
/// Gets or sets the password.
diff --git a/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs b/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs
index a0631fd07..8ea51af2b 100644
--- a/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs
+++ b/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs
@@ -11,5 +11,5 @@ public class ForgotPasswordDto
/// Gets or sets the entered username to have its password reset.
/// </summary>
[Required]
- required public string EnteredUsername { get; set; }
+ public required string EnteredUsername { get; set; }
}
diff --git a/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs
index 79b8a5d63..91b5520ee 100644
--- a/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs
+++ b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs
@@ -11,5 +11,5 @@ public class ForgotPasswordPinDto
/// Gets or sets the entered pin to have the password reset.
/// </summary>
[Required]
- required public string Pin { get; set; }
+ public required string Pin { get; set; }
}
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
index ed7c2c2c1..b263c173e 100644
--- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
@@ -1,11 +1,10 @@
using System;
-using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using System.Threading;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.BaseItemManager
@@ -15,8 +14,6 @@ namespace MediaBrowser.Controller.BaseItemManager
{
private readonly IServerConfigurationManager _serverConfigurationManager;
- private int _metadataRefreshConcurrency;
-
/// <summary>
/// Initializes a new instance of the <see cref="BaseItemManager"/> class.
/// </summary>
@@ -24,17 +21,9 @@ namespace MediaBrowser.Controller.BaseItemManager
public BaseItemManager(IServerConfigurationManager serverConfigurationManager)
{
_serverConfigurationManager = serverConfigurationManager;
-
- _metadataRefreshConcurrency = GetMetadataRefreshConcurrency();
- SetupMetadataThrottler();
-
- _serverConfigurationManager.ConfigurationUpdated += OnConfigurationUpdated;
}
/// <inheritdoc />
- public SemaphoreSlim MetadataRefreshThrottler { get; private set; }
-
- /// <inheritdoc />
public bool IsMetadataFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name)
{
if (baseItem is Channel)
@@ -51,12 +40,11 @@ namespace MediaBrowser.Controller.BaseItemManager
if (libraryTypeOptions is not null)
{
- return libraryTypeOptions.MetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
+ return libraryTypeOptions.MetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase);
}
- var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, baseItem.GetType().Name, StringComparison.OrdinalIgnoreCase));
-
- return itemConfig is null || !itemConfig.DisabledMetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
+ var itemConfig = _serverConfigurationManager.GetMetadataOptionsForType(baseItem.GetType().Name);
+ return itemConfig is null || !itemConfig.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase);
}
/// <inheritdoc />
@@ -76,50 +64,11 @@ namespace MediaBrowser.Controller.BaseItemManager
if (libraryTypeOptions is not null)
{
- return libraryTypeOptions.ImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
- }
-
- var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, baseItem.GetType().Name, StringComparison.OrdinalIgnoreCase));
-
- return itemConfig is null || !itemConfig.DisabledImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
- }
-
- /// <summary>
- /// Called when the configuration is updated.
- /// It will refresh the metadata throttler if the relevant config changed.
- /// </summary>
- private void OnConfigurationUpdated(object? sender, EventArgs e)
- {
- int newMetadataRefreshConcurrency = GetMetadataRefreshConcurrency();
- if (_metadataRefreshConcurrency != newMetadataRefreshConcurrency)
- {
- _metadataRefreshConcurrency = newMetadataRefreshConcurrency;
- SetupMetadataThrottler();
- }
- }
-
- /// <summary>
- /// Creates the metadata refresh throttler.
- /// </summary>
- [MemberNotNull(nameof(MetadataRefreshThrottler))]
- private void SetupMetadataThrottler()
- {
- MetadataRefreshThrottler = new SemaphoreSlim(_metadataRefreshConcurrency);
- }
-
- /// <summary>
- /// Returns the metadata refresh concurrency.
- /// </summary>
- private int GetMetadataRefreshConcurrency()
- {
- var concurrency = _serverConfigurationManager.Configuration.LibraryMetadataRefreshConcurrency;
-
- if (concurrency <= 0)
- {
- concurrency = Environment.ProcessorCount;
+ return libraryTypeOptions.ImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase);
}
- return concurrency;
+ var itemConfig = _serverConfigurationManager.GetMetadataOptionsForType(baseItem.GetType().Name);
+ return itemConfig is null || !itemConfig.DisabledImageFetchers.Contains(name, StringComparison.OrdinalIgnoreCase);
}
}
}
diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
index b07c80879..ac20120d9 100644
--- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
@@ -10,11 +10,6 @@ namespace MediaBrowser.Controller.BaseItemManager
public interface IBaseItemManager
{
/// <summary>
- /// Gets the semaphore used to limit the amount of concurrent metadata refreshes.
- /// </summary>
- SemaphoreSlim MetadataRefreshThrottler { get; }
-
- /// <summary>
/// Is metadata fetcher enabled.
/// </summary>
/// <param name="baseItem">The base item.</param>
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 15a79fa1f..18d948a62 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -59,7 +59,7 @@ namespace MediaBrowser.Controller.Entities.Audio
{
if (IsAccessedByName)
{
- return new List<BaseItem>();
+ return Enumerable.Empty<BaseItem>();
}
return base.Children;
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 1e868194e..501811003 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1244,14 +1244,6 @@ namespace MediaBrowser.Controller.Entities
return RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(FileSystem)), cancellationToken);
}
- protected virtual void TriggerOnRefreshStart()
- {
- }
-
- protected virtual void TriggerOnRefreshComplete()
- {
- }
-
/// <summary>
/// Overrides the base implementation to refresh metadata for local trailers.
/// </summary>
@@ -1260,8 +1252,6 @@ namespace MediaBrowser.Controller.Entities
/// <returns>true if a provider reports we changed.</returns>
public async Task<ItemUpdateType> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
{
- TriggerOnRefreshStart();
-
var requiresSave = false;
if (SupportsOwnedItems)
@@ -1281,21 +1271,14 @@ namespace MediaBrowser.Controller.Entities
}
}
- try
- {
- var refreshOptions = requiresSave
- ? new MetadataRefreshOptions(options)
- {
- ForceSave = true
- }
- : options;
+ var refreshOptions = requiresSave
+ ? new MetadataRefreshOptions(options)
+ {
+ ForceSave = true
+ }
+ : options;
- return await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
- }
- finally
- {
- TriggerOnRefreshComplete();
- }
+ return await ProviderManager.RefreshSingleItem(this, refreshOptions, cancellationToken).ConfigureAwait(false);
}
protected bool IsVisibleStandaloneInternal(User user, bool checkFolders)
@@ -1367,7 +1350,7 @@ namespace MediaBrowser.Controller.Entities
private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, IReadOnlyList<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
var extras = LibraryManager.FindExtras(item, fileSystemChildren, options.DirectoryService).ToArray();
- var newExtraIds = extras.Select(i => i.Id).ToArray();
+ var newExtraIds = Array.ConvertAll(extras, x => x.Id);
var extrasChanged = !item.ExtraIds.SequenceEqual(newExtraIds);
if (!extrasChanged && !options.ReplaceAllMetadata && options.MetadataRefreshMode != MetadataRefreshMode.FullRefresh)
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 84952295c..44fe65103 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -301,14 +301,6 @@ namespace MediaBrowser.Controller.Entities
return dictionary;
}
- protected override void TriggerOnRefreshStart()
- {
- }
-
- protected override void TriggerOnRefreshComplete()
- {
- }
-
/// <summary>
/// Validates the children internal.
/// </summary>
@@ -510,26 +502,17 @@ namespace MediaBrowser.Controller.Entities
private async Task RefreshAllMetadataForContainer(IMetadataContainer container, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
{
- // limit the amount of concurrent metadata refreshes
- await ProviderManager.RunMetadataRefresh(
- async () =>
- {
- var series = container as Series;
- if (series is not null)
- {
- await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
- }
+ if (container is Series series)
+ {
+ await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+ }
- await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false);
- },
- cancellationToken).ConfigureAwait(false);
+ await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false);
}
private async Task RefreshChildMetadata(BaseItem child, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
{
- var container = child as IMetadataContainer;
-
- if (container is not null)
+ if (child is IMetadataContainer container)
{
await RefreshAllMetadataForContainer(container, refreshOptions, progress, cancellationToken).ConfigureAwait(false);
}
@@ -537,10 +520,7 @@ namespace MediaBrowser.Controller.Entities
{
if (refreshOptions.RefreshItem(child))
{
- // limit the amount of concurrent metadata refreshes
- await ProviderManager.RunMetadataRefresh(
- async () => await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false),
- cancellationToken).ConfigureAwait(false);
+ await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
}
if (recursive && child is Folder folder)
@@ -586,7 +566,7 @@ namespace MediaBrowser.Controller.Entities
}
var fanoutConcurrency = ConfigurationManager.Configuration.LibraryScanFanoutConcurrency;
- var parallelism = fanoutConcurrency == 0 ? Environment.ProcessorCount : fanoutConcurrency;
+ var parallelism = fanoutConcurrency > 0 ? fanoutConcurrency : 2 * Environment.ProcessorCount;
var actionBlock = new ActionBlock<int>(
async i =>
@@ -618,7 +598,7 @@ namespace MediaBrowser.Controller.Entities
for (var i = 0; i < childrenCount; i++)
{
- actionBlock.Post(i);
+ await actionBlock.SendAsync(i).ConfigureAwait(false);
}
actionBlock.Complete();
diff --git a/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs b/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs
index 41cfcae16..ee9420cb4 100644
--- a/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs
+++ b/MediaBrowser.Controller/Library/MetadataConfigurationExtensions.cs
@@ -1,8 +1,8 @@
-#nullable disable
-
#pragma warning disable CS1591
+using System;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Controller.Library
@@ -10,8 +10,15 @@ namespace MediaBrowser.Controller.Library
public static class MetadataConfigurationExtensions
{
public static MetadataConfiguration GetMetadataConfiguration(this IConfigurationManager config)
- {
- return config.GetConfiguration<MetadataConfiguration>("metadata");
- }
+ => config.GetConfiguration<MetadataConfiguration>("metadata");
+
+ /// <summary>
+ /// Gets the <see cref="MetadataOptions" /> for the specified type.
+ /// </summary>
+ /// <param name="config">The <see cref="IServerConfigurationManager"/>.</param>
+ /// <param name="type">The type to get the <see cref="MetadataOptions" /> for.</param>
+ /// <returns>The <see cref="MetadataOptions" /> for the specified type or <c>null</c>.</returns>
+ public static MetadataOptions? GetMetadataOptionsForType(this IServerConfigurationManager config, string type)
+ => Array.Find(config.Configuration.MetadataOptions, i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase));
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 027053b13..fb1521ad3 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -1365,22 +1365,11 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var args = string.Empty;
var gopArg = string.Empty;
- var keyFrameArg = string.Empty;
- if (isEventPlaylist)
- {
- keyFrameArg = string.Format(
- CultureInfo.InvariantCulture,
- " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
- segmentLength);
- }
- else if (startNumber.HasValue)
- {
- keyFrameArg = string.Format(
- CultureInfo.InvariantCulture,
- " -force_key_frames:0 \"expr:gte(t,{0}+n_forced*{1})\"",
- startNumber.Value * segmentLength,
- segmentLength);
- }
+
+ var keyFrameArg = string.Format(
+ CultureInfo.InvariantCulture,
+ " -force_key_frames:0 \"expr:gte(t,n_forced*{0})\"",
+ segmentLength);
var framerate = state.VideoStream?.RealFrameRate;
if (framerate.HasValue)
@@ -2966,7 +2955,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
{
- args = "procamp_vaapi=b={2}:c={3}," + args + ":extra_hw_frames=32";
+ args = "procamp_vaapi=b={1}:c={2},tonemap_vaapi=format={0}:p=bt709:t=bt709:m=bt709:extra_hw_frames=32";
return string.Format(
CultureInfo.InvariantCulture,
@@ -2975,25 +2964,6 @@ namespace MediaBrowser.Controller.MediaEncoding
options.VppTonemappingBrightness,
options.VppTonemappingContrast);
}
-
- if (string.Equals(hwTonemapSuffix, "vulkan", StringComparison.OrdinalIgnoreCase))
- {
- args = "libplacebo=format={1}:tonemapping={2}:color_primaries=bt709:color_trc=bt709:colorspace=bt709:peak_detect=0:upscaler=none:downscaler=none";
-
- if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
- {
- args += ":range={6}";
- }
-
- if (string.Equals(options.TonemappingAlgorithm, "bt2390", StringComparison.OrdinalIgnoreCase))
- {
- algorithm = "bt.2390";
- }
- else if (string.Equals(options.TonemappingAlgorithm, "none", StringComparison.OrdinalIgnoreCase))
- {
- algorithm = "clip";
- }
- }
else
{
args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 7e0a69586..16943f6aa 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -55,14 +55,6 @@ namespace MediaBrowser.Controller.Providers
Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken);
/// <summary>
- /// Runs multiple metadata refreshes concurrently.
- /// </summary>
- /// <param name="action">The action to run.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
- Task RunMetadataRefresh(Func<Task> action, CancellationToken cancellationToken);
-
- /// <summary>
/// Saves the image.
/// </summary>
/// <param name="item">The item.</param>
@@ -207,15 +199,6 @@ namespace MediaBrowser.Controller.Providers
where TItemType : BaseItem, new()
where TLookupType : ItemLookupInfo;
- /// <summary>
- /// Gets the search image.
- /// </summary>
- /// <param name="providerName">Name of the provider.</param>
- /// <param name="url">The URL.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task{HttpResponseInfo}.</returns>
- Task<HttpResponseMessage> GetSearchImage(string providerName, string url, CancellationToken cancellationToken);
-
HashSet<Guid> GetRefreshQueue();
void OnRefreshStart(BaseItem item);
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index d2112e5dc..4e63d205c 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -57,7 +57,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
private readonly IServerConfigurationManager _serverConfig;
private readonly string _startupOptionFFmpegPath;
- private readonly SemaphoreSlim _thumbnailResourcePool = new SemaphoreSlim(2, 2);
+ private readonly SemaphoreSlim _thumbnailResourcePool;
private readonly object _runningProcessesLock = new object();
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
@@ -113,6 +113,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
_jsonSerializerOptions = new JsonSerializerOptions(JsonDefaults.Options);
_jsonSerializerOptions.Converters.Add(new JsonBoolStringConverter());
+
+ var semaphoreCount = 2 * Environment.ProcessorCount;
+ _thumbnailResourcePool = new SemaphoreSlim(semaphoreCount, semaphoreCount);
}
/// <inheritdoc />
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index ac2f1e71a..a53be0fee 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -20,6 +20,8 @@ public class EncodingOptions
MaxMuxingQueueSize = 2048;
EnableThrottling = false;
ThrottleDelaySeconds = 180;
+ EnableSegmentDeletion = false;
+ SegmentKeepSeconds = 720;
EncodingThreadCount = -1;
// This is a DRM device that is almost guaranteed to be there on every intel platform,
// plus it's the default one in ffmpeg if you don't specify anything
@@ -103,6 +105,16 @@ public class EncodingOptions
public int ThrottleDelaySeconds { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether segment deletion is enabled.
+ /// </summary>
+ public bool EnableSegmentDeletion { get; set; }
+
+ /// <summary>
+ /// Gets or sets seconds for which segments should be kept before being deleted.
+ /// </summary>
+ public int SegmentKeepSeconds { get; set; }
+
+ /// <summary>
/// Gets or sets the hardware acceleration type.
/// </summary>
public string HardwareAccelerationType { get; set; }
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 07f02d187..78a310f0b 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -166,6 +166,12 @@ namespace MediaBrowser.Model.Configuration
public int LibraryMonitorDelay { get; set; } = 60;
/// <summary>
+ /// Gets or sets the duration in seconds that we will wait after a library updated event before executing the library changed notification.
+ /// </summary>
+ /// <value>The library update duration.</value>
+ public int LibraryUpdateDuration { get; set; } = 30;
+
+ /// <summary>
/// Gets or sets the image saving convention.
/// </summary>
/// <value>The image saving convention.</value>
@@ -183,7 +189,7 @@ namespace MediaBrowser.Model.Configuration
public NameValuePair[] ContentTypes { get; set; } = Array.Empty<NameValuePair>();
- public int RemoteClientBitrateLimit { get; set; } = 0;
+ public int RemoteClientBitrateLimit { get; set; }
public bool EnableFolderView { get; set; } = false;
@@ -197,7 +203,7 @@ namespace MediaBrowser.Model.Configuration
public bool EnableExternalContentInSuggestions { get; set; } = true;
- public int ImageExtractionTimeoutMs { get; set; } = 0;
+ public int ImageExtractionTimeoutMs { get; set; }
public PathSubstitution[] PathSubstitutions { get; set; } = Array.Empty<PathSubstitution>();
@@ -245,7 +251,7 @@ namespace MediaBrowser.Model.Configuration
/// Gets or sets the dummy chapter duration in seconds, use 0 (zero) or less to disable generation alltogether.
/// </summary>
/// <value>The dummy chapters duration.</value>
- public int DummyChapterDuration { get; set; } = 0;
+ public int DummyChapterDuration { get; set; }
/// <summary>
/// Gets or sets the chapter image resolution.
@@ -257,6 +263,6 @@ namespace MediaBrowser.Model.Configuration
/// Gets or sets the limit for parallel image encoding.
/// </summary>
/// <value>The limit for parallel image encoding.</value>
- public int ParallelImageEncodingLimit { get; set; } = 0;
+ public int ParallelImageEncodingLimit { get; set; }
}
}
diff --git a/MediaBrowser.Model/Dlna/MediaOptions.cs b/MediaBrowser.Model/Dlna/MediaOptions.cs
index 7ec0dd473..eca971e95 100644
--- a/MediaBrowser.Model/Dlna/MediaOptions.cs
+++ b/MediaBrowser.Model/Dlna/MediaOptions.cs
@@ -62,7 +62,7 @@ namespace MediaBrowser.Model.Dlna
/// <summary>
/// Gets or sets the device profile.
/// </summary>
- required public DeviceProfile Profile { get; set; }
+ public required DeviceProfile Profile { get; set; }
/// <summary>
/// Gets or sets a media source id. Optional. Only needed if a specific AudioStreamIndex or SubtitleStreamIndex are requested.
diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs
index 80f5e2c37..8354c60ef 100644
--- a/MediaBrowser.Model/Users/UserPolicy.cs
+++ b/MediaBrowser.Model/Users/UserPolicy.cs
@@ -2,6 +2,7 @@
#pragma warning disable CS1591, CA1819
using System;
+using System.ComponentModel;
using System.Xml.Serialization;
using Jellyfin.Data.Enums;
using AccessSchedule = Jellyfin.Data.Entities.AccessSchedule;
@@ -79,6 +80,7 @@ namespace MediaBrowser.Model.Users
/// Gets or sets a value indicating whether this instance can manage collections.
/// </summary>
/// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
+ [DefaultValue(false)]
public bool EnableCollectionManagement { get; set; }
/// <summary>
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 1028da32b..5cb28402e 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -131,12 +131,12 @@ namespace MediaBrowser.Providers.Manager
{
var type = item.GetType();
- var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type));
- service ??= _metadataServices.FirstOrDefault(current => current.CanRefresh(item));
+ var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type))
+ ?? _metadataServices.FirstOrDefault(current => current.CanRefresh(item));
if (service is null)
{
- _logger.LogError("Unable to find a metadata service for item of type {TypeName}", item.GetType().Name);
+ _logger.LogError("Unable to find a metadata service for item of type {TypeName}", type.Name);
return Task.FromResult(ItemUpdateType.None);
}
@@ -160,7 +160,7 @@ namespace MediaBrowser.Providers.Manager
// TODO: Isolate this hack into the tvh plugin
if (string.IsNullOrEmpty(contentType))
{
- if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1)
+ if (url.Contains("/imagecache/", StringComparison.OrdinalIgnoreCase))
{
contentType = "image/png";
}
@@ -232,6 +232,11 @@ namespace MediaBrowser.Providers.Manager
providers = providers.Where(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
}
+ if (query.ImageType is not null)
+ {
+ providers = providers.Where(i => i.GetSupportedImages(item).Contains(query.ImageType.Value));
+ }
+
var preferredLanguage = item.GetPreferredMetadataLanguage();
var tasks = providers.Select(i => GetImages(item, i, preferredLanguage, query.IncludeAllLanguages, cancellationToken, query.ImageType));
@@ -568,13 +573,7 @@ namespace MediaBrowser.Providers.Manager
/// <inheritdoc/>
public MetadataOptions GetMetadataOptions(BaseItem item)
- {
- var type = item.GetType().Name;
-
- return _configurationManager.Configuration.MetadataOptions
- .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ??
- new MetadataOptions();
- }
+ => _configurationManager.GetMetadataOptionsForType(item.GetType().Name) ?? new MetadataOptions();
/// <inheritdoc/>
public Task SaveMetadataAsync(BaseItem item, ItemUpdateType updateType)
@@ -809,27 +808,12 @@ namespace MediaBrowser.Providers.Manager
{
var results = await provider.GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
- var list = results.ToList();
-
- foreach (var item in list)
+ foreach (var item in results)
{
item.SearchProviderName = provider.Name;
}
- return list;
- }
-
- /// <inheritdoc/>
- public Task<HttpResponseMessage> GetSearchImage(string providerName, string url, CancellationToken cancellationToken)
- {
- var provider = _metadataProviders.OfType<IRemoteSearchProvider>().FirstOrDefault(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
-
- if (provider is null)
- {
- throw new ArgumentException("Search provider not found.");
- }
-
- return provider.GetImageResponse(url, cancellationToken);
+ return results;
}
private IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
@@ -1102,29 +1086,6 @@ namespace MediaBrowser.Providers.Manager
return RefreshItem(item, options, cancellationToken);
}
- /// <summary>
- /// Runs multiple metadata refreshes concurrently.
- /// </summary>
- /// <param name="action">The action to run.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>A <see cref="Task"/> representing the result of the asynchronous operation.</returns>
- public async Task RunMetadataRefresh(Func<Task> action, CancellationToken cancellationToken)
- {
- // create a variable for this since it is possible MetadataRefreshThrottler could change due to a config update during a scan
- var metadataRefreshThrottler = _baseItemManager.MetadataRefreshThrottler;
-
- await metadataRefreshThrottler.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
- {
- await action().ConfigureAwait(false);
- }
- finally
- {
- metadataRefreshThrottler.Release();
- }
- }
-
/// <inheritdoc/>
public void Dispose()
{
diff --git a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
index ae244da19..a8461e991 100644
--- a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
@@ -64,7 +64,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
{
var thumbsPath = Path.Combine(_config.ApplicationPaths.CachePath, "imagesbyname", "remotestudiothumbs.txt");
- thumbsPath = await EnsureThumbsList(thumbsPath, cancellationToken).ConfigureAwait(false);
+ await EnsureThumbsList(thumbsPath, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
@@ -107,7 +107,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
return string.Format(CultureInfo.InvariantCulture, "{0}/images/{1}/{2}.jpg", GetRepositoryUrl(), image, filename);
}
- private Task<string> EnsureThumbsList(string file, CancellationToken cancellationToken)
+ private Task EnsureThumbsList(string file, CancellationToken cancellationToken)
{
string url = string.Format(CultureInfo.InvariantCulture, "{0}/thumbs.txt", GetRepositoryUrl());
@@ -129,7 +129,7 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
/// <param name="fileSystem">The file system.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A Task to ensure existence of a file listing.</returns>
- public async Task<string> EnsureList(string url, string file, IFileSystem fileSystem, CancellationToken cancellationToken)
+ public async Task EnsureList(string url, string file, IFileSystem fileSystem, CancellationToken cancellationToken)
{
var fileInfo = fileSystem.GetFileInfo(file);
@@ -148,8 +148,6 @@ namespace MediaBrowser.Providers.Plugins.StudioImages
}
}
}
-
- return file;
}
/// <summary>
diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64
index 16bb99a3d..771675519 100644
--- a/deployment/Dockerfile.centos.amd64
+++ b/deployment/Dockerfile.centos.amd64
@@ -13,7 +13,7 @@ RUN yum update -yq \
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
# Install DotNET SDK
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ebfd0bf8-79bd-480a-9e81-0b217463738d/9adc6bf0614ce02670101e278a2d8555/dotnet-sdk-7.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/87a55ae3-917d-449e-a4e8-776f82976e91/03380e598c326c2f9465d262c6a88c45/dotnet-sdk-7.0.305-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64
index 8d708f902..c552f06b0 100644
--- a/deployment/Dockerfile.fedora.amd64
+++ b/deployment/Dockerfile.fedora.amd64
@@ -12,7 +12,7 @@ RUN dnf update -yq \
&& dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make
# Install DotNET SDK
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ebfd0bf8-79bd-480a-9e81-0b217463738d/9adc6bf0614ce02670101e278a2d8555/dotnet-sdk-7.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/87a55ae3-917d-449e-a4e8-776f82976e91/03380e598c326c2f9465d262c6a88c45/dotnet-sdk-7.0.305-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64
index b440a2142..30100d20d 100644
--- a/deployment/Dockerfile.ubuntu.amd64
+++ b/deployment/Dockerfile.ubuntu.amd64
@@ -17,7 +17,7 @@ RUN apt-get update -yqq \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ebfd0bf8-79bd-480a-9e81-0b217463738d/9adc6bf0614ce02670101e278a2d8555/dotnet-sdk-7.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/87a55ae3-917d-449e-a4e8-776f82976e91/03380e598c326c2f9465d262c6a88c45/dotnet-sdk-7.0.305-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64
index f195d7004..bac2adfaf 100644
--- a/deployment/Dockerfile.ubuntu.arm64
+++ b/deployment/Dockerfile.ubuntu.arm64
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
mmv build-essential lsb-release
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ebfd0bf8-79bd-480a-9e81-0b217463738d/9adc6bf0614ce02670101e278a2d8555/dotnet-sdk-7.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/87a55ae3-917d-449e-a4e8-776f82976e91/03380e598c326c2f9465d262c6a88c45/dotnet-sdk-7.0.305-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf
index 0fb59d5ca..37a1ed5ff 100644
--- a/deployment/Dockerfile.ubuntu.armhf
+++ b/deployment/Dockerfile.ubuntu.armhf
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
mmv build-essential lsb-release
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/ebfd0bf8-79bd-480a-9e81-0b217463738d/9adc6bf0614ce02670101e278a2d8555/dotnet-sdk-7.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/87a55ae3-917d-449e-a4e8-776f82976e91/03380e598c326c2f9465d262c6a88c45/dotnet-sdk-7.0.305-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index 3b0333299..034691322 100644
--- a/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/src/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -21,6 +21,8 @@
<PackageReference Include="SkiaSharp" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux" />
<PackageReference Include="SkiaSharp.Svg" />
+ <PackageReference Include="SkiaSharp.HarfBuzz" />
+ <PackageReference Include="HarfBuzzSharp.NativeAssets.Linux" />
</ItemGroup>
<ItemGroup>
diff --git a/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs b/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
index eee24c423..a7a3338df 100644
--- a/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
+++ b/src/Jellyfin.Drawing.Skia/StripCollageBuilder.cs
@@ -3,13 +3,14 @@ using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using SkiaSharp;
+using SkiaSharp.HarfBuzz;
namespace Jellyfin.Drawing.Skia;
/// <summary>
/// Used to build collages of multiple images arranged in vertical strips.
/// </summary>
-public class StripCollageBuilder
+public partial class StripCollageBuilder
{
private readonly SkiaEncoder _skiaEncoder;
@@ -22,6 +23,9 @@ public class StripCollageBuilder
_skiaEncoder = skiaEncoder;
}
+ [GeneratedRegex(@"\p{IsArabic}|\p{IsArmenian}|\p{IsHebrew}|\p{IsSyriac}|\p{IsThaana}")]
+ private static partial Regex IsRtlTextRegex();
+
/// <summary>
/// Check which format an image has been encoded with using its filename extension.
/// </summary>
@@ -144,7 +148,19 @@ public class StripCollageBuilder
textPaint.TextSize = 0.9f * width * textPaint.TextSize / textWidth;
}
- canvas.DrawText(libraryName, width / 2f, (height / 2f) + (textPaint.FontMetrics.XHeight / 2), textPaint);
+ if (string.IsNullOrWhiteSpace(libraryName))
+ {
+ return bitmap;
+ }
+
+ if (IsRtlTextRegex().IsMatch(libraryName))
+ {
+ canvas.DrawShapedText(libraryName, width / 2f, (height / 2f) + (textPaint.FontMetrics.XHeight / 2), textPaint);
+ }
+ else
+ {
+ canvas.DrawText(libraryName, width / 2f, (height / 2f) + (textPaint.FontMetrics.XHeight / 2), textPaint);
+ }
return bitmap;
}
diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs
index 533baba4f..4e5d3b4d5 100644
--- a/src/Jellyfin.Drawing/ImageProcessor.cs
+++ b/src/Jellyfin.Drawing/ImageProcessor.cs
@@ -50,14 +50,12 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
/// <param name="appPaths">The server application paths.</param>
/// <param name="fileSystem">The filesystem.</param>
/// <param name="imageEncoder">The image encoder.</param>
- /// <param name="mediaEncoder">The media encoder.</param>
/// <param name="config">The configuration.</param>
public ImageProcessor(
ILogger<ImageProcessor> logger,
IServerApplicationPaths appPaths,
IFileSystem fileSystem,
IImageEncoder imageEncoder,
- IMediaEncoder mediaEncoder,
IServerConfigurationManager config)
{
_logger = logger;