aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/automation.yml10
-rw-r--r--Emby.Notifications/CoreNotificationTypes.cs123
-rw-r--r--Emby.Notifications/Emby.Notifications.csproj35
-rw-r--r--Emby.Notifications/NotificationConfigurationFactory.cs23
-rw-r--r--Emby.Notifications/NotificationEntryPoint.cs314
-rw-r--r--Emby.Notifications/NotificationManager.cs224
-rw-r--r--Emby.Notifications/Properties/AssemblyInfo.cs21
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs9
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj1
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs4
-rw-r--r--Emby.Server.Implementations/Localization/Core/fa.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json2
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs45
-rw-r--r--Jellyfin.Api/Controllers/NotificationsController.cs53
-rw-r--r--Jellyfin.Api/Controllers/PlaystateController.cs37
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs2
-rw-r--r--Jellyfin.sln6
-rw-r--r--Jellyfin.sln.DotSettings3
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs3
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs5
-rw-r--r--MediaBrowser.Controller/Notifications/INotificationManager.cs43
-rw-r--r--MediaBrowser.Controller/Notifications/INotificationService.cs34
-rw-r--r--MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs16
-rw-r--r--MediaBrowser.Controller/Notifications/UserNotification.cs25
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Probing/CodecType.cs32
-rw-r--r--MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs6
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs99
-rw-r--r--MediaBrowser.Model/Dto/ImageOptions.cs111
-rw-r--r--MediaBrowser.Model/Notifications/NotificationLevel.cs11
-rw-r--r--MediaBrowser.Model/Notifications/NotificationOption.cs55
-rw-r--r--MediaBrowser.Model/Notifications/NotificationOptions.cs131
-rw-r--r--MediaBrowser.Model/Notifications/NotificationRequest.cs35
-rw-r--r--MediaBrowser.Model/Notifications/NotificationTypeInfo.cs18
-rw-r--r--MediaBrowser.Model/Notifications/SendToUserType.cs11
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs41
-rw-r--r--src/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs47
-rw-r--r--src/Jellyfin.Drawing.Skia/SkiaEncoder.cs8
-rw-r--r--src/Jellyfin.Drawing/ImageProcessor.cs9
-rw-r--r--src/Jellyfin.Extensions/Json/Converters/JsonBoolStringConverter.cs34
-rw-r--r--src/Jellyfin.Extensions/Json/JsonDefaults.cs1
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolStringTests.cs37
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs25
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs22
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_ts.json (renamed from tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json)0
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs50
46 files changed, 297 insertions, 1532 deletions
diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml
index 2dc7fb5a3..4b5571c77 100644
--- a/.github/workflows/automation.yml
+++ b/.github/workflows/automation.yml
@@ -27,7 +27,7 @@ jobs:
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- name: Remove from 'Current Release' project
- uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
+ uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true
with:
@@ -36,7 +36,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Release Next' project
- uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
+ uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
continue-on-error: true
with:
@@ -45,7 +45,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Current Release' project
- uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
+ uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true
with:
@@ -59,7 +59,7 @@ jobs:
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
- name: Move issue to needs triage
- uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
+ uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
continue-on-error: true
with:
@@ -68,7 +68,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add issue to triage project
- uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
+ uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
continue-on-error: true
with:
diff --git a/Emby.Notifications/CoreNotificationTypes.cs b/Emby.Notifications/CoreNotificationTypes.cs
deleted file mode 100644
index 35aac3a11..000000000
--- a/Emby.Notifications/CoreNotificationTypes.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using MediaBrowser.Controller.Notifications;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Notifications;
-
-namespace Emby.Notifications
-{
- public class CoreNotificationTypes : INotificationTypeFactory
- {
- private readonly ILocalizationManager _localization;
-
- public CoreNotificationTypes(ILocalizationManager localization)
- {
- _localization = localization;
- }
-
- public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
- {
- var knownTypes = new NotificationTypeInfo[]
- {
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.ApplicationUpdateInstalled)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.InstallationFailed)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.PluginInstalled)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.PluginError)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.PluginUninstalled)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.PluginUpdateInstalled)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.ServerRestartRequired)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.TaskFailed)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.NewLibraryContent)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.AudioPlayback)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.VideoPlayback)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.AudioPlaybackStopped)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.VideoPlaybackStopped)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.UserLockedOut)
- },
- new NotificationTypeInfo
- {
- Type = nameof(NotificationType.ApplicationUpdateAvailable)
- }
- };
-
- foreach (var type in knownTypes)
- {
- Update(type);
- }
-
- var systemName = _localization.GetLocalizedString("System");
-
- return knownTypes.OrderByDescending(i => string.Equals(i.Category, systemName, StringComparison.OrdinalIgnoreCase))
- .ThenBy(i => i.Category)
- .ThenBy(i => i.Name);
- }
-
- private void Update(NotificationTypeInfo note)
- {
- note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type);
-
- note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;
-
- if (note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1)
- {
- note.Category = _localization.GetLocalizedString("User");
- }
- else if (note.Type.IndexOf("Plugin", StringComparison.OrdinalIgnoreCase) != -1)
- {
- note.Category = _localization.GetLocalizedString("Plugin");
- }
- else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1)
- {
- note.Category = _localization.GetLocalizedString("User");
- }
- else
- {
- note.Category = _localization.GetLocalizedString("System");
- }
- }
- }
-}
diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj
deleted file mode 100644
index eb269183e..000000000
--- a/Emby.Notifications/Emby.Notifications.csproj
+++ /dev/null
@@ -1,35 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
- <!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
- <PropertyGroup>
- <ProjectGuid>{2E030C33-6923-4530-9E54-FA29FA6AD1A9}</ProjectGuid>
- </PropertyGroup>
-
- <PropertyGroup>
- <TargetFramework>net7.0</TargetFramework>
- <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
- <GenerateDocumentationFile>true</GenerateDocumentationFile>
- </PropertyGroup>
-
- <ItemGroup>
- <Compile Include="..\SharedVersion.cs" />
- </ItemGroup>
-
- <ItemGroup>
- <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
- <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
- <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
- </ItemGroup>
-
- <!-- Code analyzers-->
- <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4">
- <PrivateAssets>all</PrivateAssets>
- <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
- </PackageReference>
- <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
- <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
- </ItemGroup>
-
-</Project>
diff --git a/Emby.Notifications/NotificationConfigurationFactory.cs b/Emby.Notifications/NotificationConfigurationFactory.cs
deleted file mode 100644
index 3fb3553d0..000000000
--- a/Emby.Notifications/NotificationConfigurationFactory.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Model.Notifications;
-
-namespace Emby.Notifications
-{
- public class NotificationConfigurationFactory : IConfigurationFactory
- {
- public IEnumerable<ConfigurationStore> GetConfigurations()
- {
- return new ConfigurationStore[]
- {
- new ConfigurationStore
- {
- Key = "notifications",
- ConfigurationType = typeof(NotificationOptions)
- }
- };
- }
- }
-}
diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs
deleted file mode 100644
index 3763b1e92..000000000
--- a/Emby.Notifications/NotificationEntryPoint.cs
+++ /dev/null
@@ -1,314 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Data.Events;
-using Jellyfin.Extensions;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Notifications;
-using MediaBrowser.Controller.Plugins;
-using MediaBrowser.Model.Activity;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Notifications;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Notifications
-{
- /// <summary>
- /// Creates notifications for various system events.
- /// </summary>
- public class NotificationEntryPoint : IServerEntryPoint
- {
- private readonly ILogger<NotificationEntryPoint> _logger;
- private readonly IActivityManager _activityManager;
- private readonly ILocalizationManager _localization;
- private readonly INotificationManager _notificationManager;
- private readonly ILibraryManager _libraryManager;
- private readonly IServerApplicationHost _appHost;
- private readonly IConfigurationManager _config;
-
- private readonly object _libraryChangedSyncLock = new object();
- private readonly List<BaseItem> _itemsAdded = new List<BaseItem>();
-
- private Timer? _libraryUpdateTimer;
-
- private string[] _coreNotificationTypes;
-
- private bool _disposed = false;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="NotificationEntryPoint" /> class.
- /// </summary>
- /// <param name="logger">The logger.</param>
- /// <param name="activityManager">The activity manager.</param>
- /// <param name="localization">The localization manager.</param>
- /// <param name="notificationManager">The notification manager.</param>
- /// <param name="libraryManager">The library manager.</param>
- /// <param name="appHost">The application host.</param>
- /// <param name="config">The configuration manager.</param>
- public NotificationEntryPoint(
- ILogger<NotificationEntryPoint> logger,
- IActivityManager activityManager,
- ILocalizationManager localization,
- INotificationManager notificationManager,
- ILibraryManager libraryManager,
- IServerApplicationHost appHost,
- IConfigurationManager config)
- {
- _logger = logger;
- _activityManager = activityManager;
- _localization = localization;
- _notificationManager = notificationManager;
- _libraryManager = libraryManager;
- _appHost = appHost;
- _config = config;
-
- _coreNotificationTypes = new CoreNotificationTypes(localization).GetNotificationTypes().Select(i => i.Type).ToArray();
- }
-
- /// <inheritdoc />
- public Task RunAsync()
- {
- _libraryManager.ItemAdded += OnLibraryManagerItemAdded;
- _appHost.HasPendingRestartChanged += OnAppHostHasPendingRestartChanged;
- _activityManager.EntryCreated += OnActivityManagerEntryCreated;
-
- return Task.CompletedTask;
- }
-
- private async void OnAppHostHasPendingRestartChanged(object? sender, EventArgs e)
- {
- var type = NotificationType.ServerRestartRequired.ToString();
-
- var notification = new NotificationRequest
- {
- NotificationType = type,
- Name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("ServerNameNeedsToBeRestarted"),
- _appHost.Name)
- };
-
- await SendNotification(notification, null).ConfigureAwait(false);
- }
-
- private async void OnActivityManagerEntryCreated(object? sender, GenericEventArgs<ActivityLogEntry> e)
- {
- var entry = e.Argument;
-
- var type = entry.Type;
-
- if (string.IsNullOrEmpty(type) || !_coreNotificationTypes.Contains(type, StringComparison.OrdinalIgnoreCase))
- {
- return;
- }
-
- var userId = e.Argument.UserId;
-
- if (!userId.Equals(default) && !GetOptions().IsEnabledToMonitorUser(type, userId))
- {
- return;
- }
-
- var notification = new NotificationRequest
- {
- NotificationType = type,
- Name = entry.Name,
- Description = entry.Overview
- };
-
- await SendNotification(notification, null).ConfigureAwait(false);
- }
-
- private NotificationOptions GetOptions()
- {
- return _config.GetConfiguration<NotificationOptions>("notifications");
- }
-
- private void OnLibraryManagerItemAdded(object? sender, ItemChangeEventArgs e)
- {
- if (!FilterItem(e.Item))
- {
- return;
- }
-
- lock (_libraryChangedSyncLock)
- {
- if (_libraryUpdateTimer is null)
- {
- _libraryUpdateTimer = new Timer(
- LibraryUpdateTimerCallback,
- null,
- 5000,
- Timeout.Infinite);
- }
- else
- {
- _libraryUpdateTimer.Change(5000, Timeout.Infinite);
- }
-
- _itemsAdded.Add(e.Item);
- }
- }
-
- private bool FilterItem(BaseItem item)
- {
- if (item.IsFolder)
- {
- return false;
- }
-
- if (!item.HasPathProtocol)
- {
- return false;
- }
-
- if (item is IItemByName)
- {
- return false;
- }
-
- return item.SourceType == SourceType.Library;
- }
-
- private async void LibraryUpdateTimerCallback(object? state)
- {
- List<BaseItem> items;
-
- lock (_libraryChangedSyncLock)
- {
- items = _itemsAdded.ToList();
- _itemsAdded.Clear();
- _libraryUpdateTimer!.Dispose(); // Shouldn't be null as it just set off this callback
- _libraryUpdateTimer = null;
- }
-
- if (items.Count > 10)
- {
- items = items.GetRange(0, 10);
- }
-
- foreach (var item in items)
- {
- var notification = new NotificationRequest
- {
- NotificationType = NotificationType.NewLibraryContent.ToString(),
- Name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("ValueHasBeenAddedToLibrary"),
- GetItemName(item)),
- Description = item.Overview
- };
-
- await SendNotification(notification, item).ConfigureAwait(false);
- }
- }
-
- /// <summary>
- /// Creates a human readable name for the item.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns>A human readable name for the item.</returns>
- public static string GetItemName(BaseItem item)
- {
- var name = item.Name;
- if (item is Episode episode)
- {
- if (episode.IndexNumber.HasValue)
- {
- name = string.Format(
- CultureInfo.InvariantCulture,
- "Ep{0} - {1}",
- episode.IndexNumber.Value,
- name);
- }
-
- if (episode.ParentIndexNumber.HasValue)
- {
- name = string.Format(
- CultureInfo.InvariantCulture,
- "S{0}, {1}",
- episode.ParentIndexNumber.Value,
- name);
- }
- }
-
- if (item is IHasSeries hasSeries)
- {
- name = hasSeries.SeriesName + " - " + name;
- }
-
- if (item is IHasAlbumArtist hasAlbumArtist)
- {
- var artists = hasAlbumArtist.AlbumArtists;
-
- if (artists.Count > 0)
- {
- name = artists[0] + " - " + name;
- }
- }
- else if (item is IHasArtist hasArtist)
- {
- var artists = hasArtist.Artists;
-
- if (artists.Count > 0)
- {
- name = artists[0] + " - " + name;
- }
- }
-
- return name;
- }
-
- private async Task SendNotification(NotificationRequest notification, BaseItem? relatedItem)
- {
- try
- {
- await _notificationManager.SendNotification(notification, relatedItem, CancellationToken.None).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error sending notification");
- }
- }
-
- /// <inheritdoc />
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- /// <summary>
- /// Releases unmanaged and optionally managed resources.
- /// </summary>
- /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
- protected virtual void Dispose(bool disposing)
- {
- if (_disposed)
- {
- return;
- }
-
- if (disposing)
- {
- _libraryUpdateTimer?.Dispose();
- }
-
- _libraryUpdateTimer = null;
-
- _libraryManager.ItemAdded -= OnLibraryManagerItemAdded;
- _appHost.HasPendingRestartChanged -= OnAppHostHasPendingRestartChanged;
- _activityManager.EntryCreated -= OnActivityManagerEntryCreated;
-
- _disposed = true;
- }
- }
-}
diff --git a/Emby.Notifications/NotificationManager.cs b/Emby.Notifications/NotificationManager.cs
deleted file mode 100644
index a5a76b2af..000000000
--- a/Emby.Notifications/NotificationManager.cs
+++ /dev/null
@@ -1,224 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Notifications;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Notifications;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Notifications
-{
- /// <summary>
- /// NotificationManager class.
- /// </summary>
- public class NotificationManager : INotificationManager
- {
- private readonly ILogger<NotificationManager> _logger;
- private readonly IUserManager _userManager;
- private readonly IServerConfigurationManager _config;
-
- private INotificationService[] _services = Array.Empty<INotificationService>();
- private INotificationTypeFactory[] _typeFactories = Array.Empty<INotificationTypeFactory>();
-
- /// <summary>
- /// Initializes a new instance of the <see cref="NotificationManager" /> class.
- /// </summary>
- /// <param name="logger">The logger.</param>
- /// <param name="userManager">The user manager.</param>
- /// <param name="config">The server configuration manager.</param>
- public NotificationManager(
- ILogger<NotificationManager> logger,
- IUserManager userManager,
- IServerConfigurationManager config)
- {
- _logger = logger;
- _userManager = userManager;
- _config = config;
- }
-
- private NotificationOptions GetConfiguration()
- {
- return _config.GetConfiguration<NotificationOptions>("notifications");
- }
-
- /// <inheritdoc />
- public Task SendNotification(NotificationRequest request, CancellationToken cancellationToken)
- {
- return SendNotification(request, null, cancellationToken);
- }
-
- /// <inheritdoc />
- public Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken)
- {
- var notificationType = request.NotificationType;
-
- var options = string.IsNullOrEmpty(notificationType) ?
- null :
- GetConfiguration().GetOptions(notificationType);
-
- var users = GetUserIds(request, options)
- .Select(i => _userManager.GetUserById(i))
- .Where(i => relatedItem is null || relatedItem.IsVisibleStandalone(i))
- .ToArray();
-
- var title = request.Name;
- var description = request.Description;
-
- var tasks = _services.Where(i => IsEnabled(i, notificationType))
- .Select(i => SendNotification(request, i, users, title, description, cancellationToken));
-
- return Task.WhenAll(tasks);
- }
-
- private Task SendNotification(
- NotificationRequest request,
- INotificationService service,
- IEnumerable<User> users,
- string title,
- string description,
- CancellationToken cancellationToken)
- {
- users = users.Where(i => IsEnabledForUser(service, i));
-
- var tasks = users.Select(i => SendNotification(request, service, title, description, i, cancellationToken));
-
- return Task.WhenAll(tasks);
- }
-
- private IEnumerable<Guid> GetUserIds(NotificationRequest request, NotificationOption? options)
- {
- if (request.SendToUserMode.HasValue)
- {
- switch (request.SendToUserMode.Value)
- {
- case SendToUserType.Admins:
- return _userManager.Users.Where(i => i.HasPermission(PermissionKind.IsAdministrator))
- .Select(i => i.Id);
- case SendToUserType.All:
- return _userManager.UsersIds;
- case SendToUserType.Custom:
- return request.UserIds;
- default:
- throw new ArgumentException("Unrecognized SendToUserMode: " + request.SendToUserMode.Value);
- }
- }
-
- if (options is not null && !string.IsNullOrEmpty(request.NotificationType))
- {
- var config = GetConfiguration();
-
- return _userManager.Users
- .Where(i => config.IsEnabledToSendToUser(request.NotificationType, i.Id.ToString("N", CultureInfo.InvariantCulture), i))
- .Select(i => i.Id);
- }
-
- return request.UserIds;
- }
-
- private async Task SendNotification(
- NotificationRequest request,
- INotificationService service,
- string title,
- string description,
- User user,
- CancellationToken cancellationToken)
- {
- var notification = new UserNotification
- {
- Date = request.Date,
- Description = description,
- Level = request.Level,
- Name = title,
- Url = request.Url,
- User = user
- };
-
- _logger.LogDebug("Sending notification via {0} to user {1}", service.Name, user.Username);
-
- try
- {
- await service.SendNotification(notification, cancellationToken).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error sending notification to {0}", service.Name);
- }
- }
-
- private bool IsEnabledForUser(INotificationService service, User user)
- {
- try
- {
- return service.IsEnabledForUser(user);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error in IsEnabledForUser");
- return false;
- }
- }
-
- private bool IsEnabled(INotificationService service, string notificationType)
- {
- if (string.IsNullOrEmpty(notificationType))
- {
- return true;
- }
-
- return GetConfiguration().IsServiceEnabled(service.Name, notificationType);
- }
-
- /// <inheritdoc />
- public void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories)
- {
- _services = services.ToArray();
- _typeFactories = notificationTypeFactories.ToArray();
- }
-
- /// <inheritdoc />
- public List<NotificationTypeInfo> GetNotificationTypes()
- {
- var list = _typeFactories.Select(i =>
- {
- try
- {
- return i.GetNotificationTypes().ToList();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error in GetNotificationTypes");
- return new List<NotificationTypeInfo>();
- }
- }).SelectMany(i => i).ToList();
-
- var config = GetConfiguration();
-
- foreach (var i in list)
- {
- i.Enabled = config.IsEnabled(i.Type);
- }
-
- return list;
- }
-
- /// <inheritdoc />
- public IEnumerable<NameIdPair> GetNotificationServices()
- {
- return _services.Select(i => new NameIdPair
- {
- Name = i.Name,
- Id = i.Name.GetMD5().ToString("N", CultureInfo.InvariantCulture)
- }).OrderBy(i => i.Name);
- }
- }
-}
diff --git a/Emby.Notifications/Properties/AssemblyInfo.cs b/Emby.Notifications/Properties/AssemblyInfo.cs
deleted file mode 100644
index 5c82c90c4..000000000
--- a/Emby.Notifications/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Reflection;
-using System.Resources;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("Emby.Notifications")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Jellyfin Project")]
-[assembly: AssemblyProduct("Jellyfin Server")]
-[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: NeutralResourcesLanguage("en")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 0b2dd00b9..37a9e7715 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -18,7 +18,6 @@ using Emby.Dlna;
using Emby.Dlna.Main;
using Emby.Dlna.Ssdp;
using Emby.Naming.Common;
-using Emby.Notifications;
using Emby.Photos;
using Emby.Server.Implementations.Channels;
using Emby.Server.Implementations.Collections;
@@ -69,7 +68,6 @@ using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Lyrics;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
-using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Plugins;
@@ -586,8 +584,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
- serviceCollection.AddSingleton<INotificationManager, NotificationManager>();
-
serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
@@ -727,8 +723,6 @@ namespace Emby.Server.Implementations
Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
-
- Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
}
/// <summary>
@@ -927,9 +921,6 @@ namespace Emby.Server.Implementations
// Local metadata
yield return typeof(BoxSetXmlSaver).Assembly;
- // Notifications
- yield return typeof(NotificationManager).Assembly;
-
// Xbmc
yield return typeof(ArtistNfoProvider).Assembly;
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 7eaef094b..1b5c879be 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -7,7 +7,6 @@
<ItemGroup>
<ProjectReference Include="..\Emby.Naming\Emby.Naming.csproj" />
- <ProjectReference Include="..\Emby.Notifications\Emby.Notifications.csproj" />
<ProjectReference Include="..\Jellyfin.Api\Jellyfin.Api.csproj" />
<ProjectReference Include="..\Jellyfin.Server.Implementations\Jellyfin.Server.Implementations.csproj" />
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 5327b3d74..98bbc1540 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -14,6 +14,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
+using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
@@ -58,7 +59,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_socketFactory = socketFactory;
_streamHelper = streamHelper;
- _jsonOptions = JsonDefaults.Options;
+ _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
+ _jsonOptions.Converters.Add(new JsonBoolNumberConverter());
}
public string Name => "HD Homerun";
diff --git a/Emby.Server.Implementations/Localization/Core/fa.json b/Emby.Server.Implementations/Localization/Core/fa.json
index 026648af4..8e4bba25b 100644
--- a/Emby.Server.Implementations/Localization/Core/fa.json
+++ b/Emby.Server.Implementations/Localization/Core/fa.json
@@ -123,5 +123,6 @@
"TaskOptimizeDatabaseDescription": "فشرده سازی پایگاه داده و باز کردن فضای آزاد.اجرای این گزینه بعد از اسکن کردن کتابخانه یا تغییرات دیگر که روی پایگاه داده تأثیر میگذارند میتواند کارایی را بهبود ببخشد.",
"TaskKeyframeExtractorDescription": "فریم های کلیدی را از فایل های ویدئویی استخراج می کند تا لیست های پخش HLS دقیق تری ایجاد کند. این کار ممکن است برای مدت طولانی اجرا شود.",
"TaskKeyframeExtractor": "استخراج کننده فریم کلیدی",
- "External": "خارجی"
+ "External": "خارجی",
+ "HearingImpaired": "مشکل شنوایی"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index 318a0f3cf..785e6b226 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -66,7 +66,7 @@
"PluginInstalledWithName": "{0} installerades",
"PluginUninstalledWithName": "{0} avinstallerades",
"PluginUpdatedWithName": "{0} uppdaterades",
- "ProviderValue": "Källa: {0}",
+ "ProviderValue": "Leverantör: {0}",
"ScheduledTaskFailedWithName": "{0} misslyckades",
"ScheduledTaskStartedWithName": "{0} startades",
"ServerNameNeedsToBeRestarted": "{0} behöver startas om",
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index f866655c0..996dc0819 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -505,7 +505,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
/// <param name="blur">Optional. Blur image.</param>
@@ -536,7 +535,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] ImageFormat? format,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] double? percentPlayed,
[FromQuery] int? unplayedCount,
[FromQuery] int? blur,
@@ -565,7 +563,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -589,7 +586,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
/// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
/// <param name="blur">Optional. Blur image.</param>
@@ -620,7 +616,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? tag,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromQuery] ImageFormat? format,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] double? percentPlayed,
[FromQuery] int? unplayedCount,
[FromQuery] int? blur,
@@ -648,7 +643,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -671,7 +665,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
/// <param name="blur">Optional. Blur image.</param>
@@ -702,7 +695,6 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string tag,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
[FromRoute, Required] ImageFormat format,
- [FromQuery] bool? addPlayedIndicator,
[FromRoute, Required] double percentPlayed,
[FromRoute, Required] int unplayedCount,
[FromQuery] int? blur,
@@ -731,7 +723,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -756,7 +747,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -787,7 +777,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
@@ -814,7 +803,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -839,7 +827,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -870,7 +857,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
@@ -897,7 +883,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -923,7 +908,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -954,7 +938,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer)
@@ -980,7 +963,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -1005,7 +987,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -1036,7 +1017,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
@@ -1063,7 +1043,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -1089,7 +1068,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -1120,7 +1098,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer)
@@ -1146,7 +1123,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -1171,7 +1147,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -1202,7 +1177,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
@@ -1229,7 +1203,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -1255,7 +1228,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -1286,7 +1258,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer)
@@ -1312,7 +1283,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -1337,7 +1307,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -1368,7 +1337,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
@@ -1395,7 +1363,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -1421,7 +1388,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -1452,7 +1418,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer)
@@ -1478,7 +1443,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -1503,7 +1467,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -1534,7 +1497,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
@@ -1578,7 +1540,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -1605,7 +1566,6 @@ namespace Jellyfin.Api.Controllers
/// <param name="fillWidth">Width of box to fill.</param>
/// <param name="fillHeight">Height of box to fill.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
- /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="blur">Optional. Blur image.</param>
/// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param>
/// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param>
@@ -1636,7 +1596,6 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? fillWidth,
[FromQuery] int? fillHeight,
[FromQuery, ParameterObsolete] bool? cropWhitespace,
- [FromQuery] bool? addPlayedIndicator,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer)
@@ -1679,7 +1638,6 @@ namespace Jellyfin.Api.Controllers
quality,
fillWidth,
fillHeight,
- addPlayedIndicator,
blur,
backgroundColor,
foregroundLayer,
@@ -1924,7 +1882,6 @@ namespace Jellyfin.Api.Controllers
int? quality,
int? fillWidth,
int? fillHeight,
- bool? addPlayedIndicator,
int? blur,
string? backgroundColor,
string? foregroundLayer,
@@ -1940,7 +1897,6 @@ namespace Jellyfin.Api.Controllers
else if (percentPlayed.Value >= 100)
{
percentPlayed = null;
- addPlayedIndicator = true;
}
}
@@ -1997,7 +1953,6 @@ namespace Jellyfin.Api.Controllers
FillWidth = fillWidth,
Quality = quality ?? 100,
Width = width,
- AddPlayedIndicator = addPlayedIndicator ?? false,
PercentPlayed = percentPlayed ?? 0,
UnplayedCount = unplayedCount,
Blur = blur,
diff --git a/Jellyfin.Api/Controllers/NotificationsController.cs b/Jellyfin.Api/Controllers/NotificationsController.cs
deleted file mode 100644
index a28556476..000000000
--- a/Jellyfin.Api/Controllers/NotificationsController.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System.Collections.Generic;
-using Jellyfin.Api.Constants;
-using MediaBrowser.Controller.Notifications;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Notifications;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-
-namespace Jellyfin.Api.Controllers
-{
- /// <summary>
- /// The notification controller.
- /// </summary>
- [Authorize(Policy = Policies.DefaultAuthorization)]
- public class NotificationsController : BaseJellyfinApiController
- {
- private readonly INotificationManager _notificationManager;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="NotificationsController" /> class.
- /// </summary>
- /// <param name="notificationManager">The notification manager.</param>
- public NotificationsController(INotificationManager notificationManager)
- {
- _notificationManager = notificationManager;
- }
-
- /// <summary>
- /// Gets notification types.
- /// </summary>
- /// <response code="200">All notification types returned.</response>
- /// <returns>An <cref see="OkResult"/> containing a list of all notification types.</returns>
- [HttpGet("Types")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public IEnumerable<NotificationTypeInfo> GetNotificationTypes()
- {
- return _notificationManager.GetNotificationTypes();
- }
-
- /// <summary>
- /// Gets notification services.
- /// </summary>
- /// <response code="200">All notification services returned.</response>
- /// <returns>An <cref see="OkResult"/> containing a list of all notification services.</returns>
- [HttpGet("Services")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- public IEnumerable<NameIdPair> GetNotificationServices()
- {
- return _notificationManager.GetNotificationServices();
- }
- }
-}
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index 58f9b7d35..0260f9e2a 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -7,6 +7,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
+using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
@@ -65,9 +66,11 @@ namespace Jellyfin.Api.Controllers
/// <param name="itemId">Item id.</param>
/// <param name="datePlayed">Optional. The date the item was played.</param>
/// <response code="200">Item marked as played.</response>
- /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
+ /// <response code="404">Item not found.</response>
+ /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>, or a <see cref="NotFoundResult"/> if item was not found.</returns>
[HttpPost("Users/{userId}/PlayedItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<UserItemDataDto>> MarkPlayedItem(
[FromRoute, Required] Guid userId,
[FromRoute, Required] Guid itemId,
@@ -75,11 +78,18 @@ namespace Jellyfin.Api.Controllers
{
var user = _userManager.GetUserById(userId);
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
- var dto = UpdatePlayedStatus(user, itemId, true, datePlayed);
+
+ var item = _libraryManager.GetItemById(itemId);
+ if (item is null)
+ {
+ return NotFound();
+ }
+
+ var dto = UpdatePlayedStatus(user, item, true, datePlayed);
foreach (var additionalUserInfo in session.AdditionalUsers)
{
var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
- UpdatePlayedStatus(additionalUser, itemId, true, datePlayed);
+ UpdatePlayedStatus(additionalUser, item, true, datePlayed);
}
return dto;
@@ -91,18 +101,27 @@ namespace Jellyfin.Api.Controllers
/// <param name="userId">User id.</param>
/// <param name="itemId">Item id.</param>
/// <response code="200">Item marked as unplayed.</response>
- /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
+ /// <response code="404">Item not found.</response>
+ /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>, or a <see cref="NotFoundResult"/> if item was not found.</returns>
[HttpDelete("Users/{userId}/PlayedItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<UserItemDataDto>> MarkUnplayedItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
- var dto = UpdatePlayedStatus(user, itemId, false, null);
+ var item = _libraryManager.GetItemById(itemId);
+
+ if (item is null)
+ {
+ return NotFound();
+ }
+
+ var dto = UpdatePlayedStatus(user, item, false, null);
foreach (var additionalUserInfo in session.AdditionalUsers)
{
var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
- UpdatePlayedStatus(additionalUser, itemId, false, null);
+ UpdatePlayedStatus(additionalUser, item, false, null);
}
return dto;
@@ -328,14 +347,12 @@ namespace Jellyfin.Api.Controllers
/// Updates the played status.
/// </summary>
/// <param name="user">The user.</param>
- /// <param name="itemId">The item id.</param>
+ /// <param name="item">The item.</param>
/// <param name="wasPlayed">if set to <c>true</c> [was played].</param>
/// <param name="datePlayed">The date played.</param>
/// <returns>Task.</returns>
- private UserItemDataDto UpdatePlayedStatus(User user, Guid itemId, bool wasPlayed, DateTime? datePlayed)
+ private UserItemDataDto UpdatePlayedStatus(User user, BaseItem item, bool wasPlayed, DateTime? datePlayed)
{
- var item = _libraryManager.GetItemById(itemId);
-
if (wasPlayed)
{
item.MarkPlayed(user, datePlayed, true);
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 568224a42..06f2227b8 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -277,7 +277,7 @@ namespace Jellyfin.Api.Controllers
}
else
{
- if (!User.IsInRole(UserRoles.Administrator))
+ if (!User.IsInRole(UserRoles.Administrator) || User.GetUserId().Equals(userId))
{
var success = await _userManager.AuthenticateUser(
user.Username,
diff --git a/Jellyfin.sln b/Jellyfin.sln
index f3fb7efa9..cad23fc5e 100644
--- a/Jellyfin.sln
+++ b/Jellyfin.sln
@@ -27,8 +27,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RSSDP", "RSSDP\RSSDP.csproj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Dlna", "Emby.Dlna\Emby.Dlna.csproj", "{805844AB-E92F-45E6-9D99-4F6D48D129A5}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Notifications", "Emby.Notifications\Emby.Notifications.csproj", "{2E030C33-6923-4530-9E54-FA29FA6AD1A9}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Emby.Naming", "Emby.Naming\Emby.Naming.csproj", "{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MediaBrowser.MediaEncoding", "MediaBrowser.MediaEncoding\MediaBrowser.MediaEncoding.csproj", "{960295EE-4AF4-4440-A525-B4C295B01A61}"
@@ -149,10 +147,6 @@ Global
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{805844AB-E92F-45E6-9D99-4F6D48D129A5}.Release|Any CPU.Build.0 = Release|Any CPU
- {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2E030C33-6923-4530-9E54-FA29FA6AD1A9}.Release|Any CPU.Build.0 = Release|Any CPU
{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
diff --git a/Jellyfin.sln.DotSettings b/Jellyfin.sln.DotSettings
new file mode 100644
index 000000000..b56741648
--- /dev/null
+++ b/Jellyfin.sln.DotSettings
@@ -0,0 +1,3 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+ <s:Boolean x:Key="/Default/UserDictionary/Words/=Jellyfin/@EntryIndexedValue">True</s:Boolean>
+ <s:Boolean x:Key="/Default/UserDictionary/Words/=Playstate/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> \ No newline at end of file
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
index 11e663301..7912c5e87 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
@@ -42,8 +42,6 @@ namespace MediaBrowser.Controller.Drawing
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
- public bool AddPlayedIndicator { get; set; }
-
public int? UnplayedCount { get; set; }
public int? Blur { get; set; }
@@ -111,7 +109,6 @@ namespace MediaBrowser.Controller.Drawing
{
return (Quality >= 90) &&
IsFormatSupported(originalImagePath) &&
- !AddPlayedIndicator &&
PercentPlayed.Equals(0) &&
!UnplayedCount.HasValue &&
!Blur.HasValue &&
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 9f7be977f..a844e6443 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -5762,6 +5762,11 @@ namespace MediaBrowser.Controller.MediaEncoding
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
+ if (!string.IsNullOrEmpty(state.OutputAudioCodec))
+ {
+ audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
+ }
+
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
// opus only supports specific sampling rates
diff --git a/MediaBrowser.Controller/Notifications/INotificationManager.cs b/MediaBrowser.Controller/Notifications/INotificationManager.cs
deleted file mode 100644
index 7caba1097..000000000
--- a/MediaBrowser.Controller/Notifications/INotificationManager.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Notifications;
-
-namespace MediaBrowser.Controller.Notifications
-{
- public interface INotificationManager
- {
- /// <summary>
- /// Sends the notification.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task SendNotification(NotificationRequest request, CancellationToken cancellationToken);
-
- Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken);
-
- /// <summary>
- /// Adds the parts.
- /// </summary>
- /// <param name="services">The services.</param>
- /// <param name="notificationTypeFactories">The notification type factories.</param>
- void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories);
-
- /// <summary>
- /// Gets the notification types.
- /// </summary>
- /// <returns>IEnumerable{NotificationTypeInfo}.</returns>
- List<NotificationTypeInfo> GetNotificationTypes();
-
- /// <summary>
- /// Gets the notification services.
- /// </summary>
- /// <returns>IEnumerable{NotificationServiceInfo}.</returns>
- IEnumerable<NameIdPair> GetNotificationServices();
- }
-}
diff --git a/MediaBrowser.Controller/Notifications/INotificationService.cs b/MediaBrowser.Controller/Notifications/INotificationService.cs
deleted file mode 100644
index 535c08795..000000000
--- a/MediaBrowser.Controller/Notifications/INotificationService.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
-
-namespace MediaBrowser.Controller.Notifications
-{
- public interface INotificationService
- {
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- string Name { get; }
-
- /// <summary>
- /// Sends the notification.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task SendNotification(UserNotification request, CancellationToken cancellationToken);
-
- /// <summary>
- /// Determines whether [is enabled for user] [the specified user identifier].
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns><c>true</c> if [is enabled for user] [the specified user identifier]; otherwise, <c>false</c>.</returns>
- bool IsEnabledForUser(User user);
- }
-}
diff --git a/MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs b/MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs
deleted file mode 100644
index 52a3e120b..000000000
--- a/MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Model.Notifications;
-
-namespace MediaBrowser.Controller.Notifications
-{
- public interface INotificationTypeFactory
- {
- /// <summary>
- /// Gets the notification types.
- /// </summary>
- /// <returns>IEnumerable{NotificationTypeInfo}.</returns>
- IEnumerable<NotificationTypeInfo> GetNotificationTypes();
- }
-}
diff --git a/MediaBrowser.Controller/Notifications/UserNotification.cs b/MediaBrowser.Controller/Notifications/UserNotification.cs
deleted file mode 100644
index 4be0e09ae..000000000
--- a/MediaBrowser.Controller/Notifications/UserNotification.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Model.Notifications;
-
-namespace MediaBrowser.Controller.Notifications
-{
- public class UserNotification
- {
- public string Name { get; set; }
-
- public string Description { get; set; }
-
- public string Url { get; set; }
-
- public NotificationLevel Level { get; set; }
-
- public DateTime Date { get; set; }
-
- public User User { get; set; }
- }
-}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index d2240b5af..cef02d5f8 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 Jellyfin.Extensions.Json.Converters;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
@@ -105,7 +106,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
_config = config;
_serverConfig = serverConfig;
_startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
- _jsonSerializerOptions = JsonDefaults.Options;
+
+ _jsonSerializerOptions = new JsonSerializerOptions(JsonDefaults.Options);
+ _jsonSerializerOptions.Converters.Add(new JsonBoolStringConverter());
}
/// <inheritdoc />
diff --git a/MediaBrowser.MediaEncoding/Probing/CodecType.cs b/MediaBrowser.MediaEncoding/Probing/CodecType.cs
new file mode 100644
index 000000000..d7c68e5f3
--- /dev/null
+++ b/MediaBrowser.MediaEncoding/Probing/CodecType.cs
@@ -0,0 +1,32 @@
+namespace MediaBrowser.MediaEncoding.Probing;
+
+/// <summary>
+/// FFmpeg Codec Type.
+/// </summary>
+public enum CodecType
+{
+ /// <summary>
+ /// Video.
+ /// </summary>
+ Video,
+
+ /// <summary>
+ /// Audio.
+ /// </summary>
+ Audio,
+
+ /// <summary>
+ /// Opaque data information usually continuous.
+ /// </summary>
+ Data,
+
+ /// <summary>
+ /// Subtitles.
+ /// </summary>
+ Subtitle,
+
+ /// <summary>
+ /// Opaque data information usually sparse.
+ /// </summary>
+ Attachment
+}
diff --git a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
index eab8f79bb..294442324 100644
--- a/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
+++ b/MediaBrowser.MediaEncoding/Probing/MediaStreamInfo.cs
@@ -43,7 +43,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// </summary>
/// <value>The codec_type.</value>
[JsonPropertyName("codec_type")]
- public string CodecType { get; set; }
+ public CodecType CodecType { get; set; }
/// <summary>
/// Gets or sets the sample_rate.
@@ -228,11 +228,11 @@ namespace MediaBrowser.MediaEncoding.Probing
public long StartPts { get; set; }
/// <summary>
- /// Gets or sets the is_avc.
+ /// Gets or sets a value indicating whether the stream is AVC.
/// </summary>
/// <value>The is_avc.</value>
[JsonPropertyName("is_avc")]
- public string IsAvc { get; set; }
+ public bool IsAvc { get; set; }
/// <summary>
/// Gets or sets the nal_length_size.
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index c667f5f57..99310a75d 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -107,9 +107,9 @@ namespace MediaBrowser.MediaEncoding.Probing
}
var tags = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
- var tagStreamType = isAudio ? "audio" : "video";
+ var tagStreamType = isAudio ? CodecType.Audio : CodecType.Video;
- var tagStream = data.Streams?.FirstOrDefault(i => string.Equals(i.CodecType, tagStreamType, StringComparison.OrdinalIgnoreCase));
+ var tagStream = data.Streams?.FirstOrDefault(i => i.CodecType == tagStreamType);
if (tagStream?.Tags is not null)
{
@@ -599,7 +599,7 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <returns>MediaAttachments.</returns>
private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo)
{
- if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase)
+ if (streamInfo.CodecType != CodecType.Attachment
&& streamInfo.Disposition?.GetValueOrDefault("attached_pic") != 1)
{
return null;
@@ -651,20 +651,10 @@ namespace MediaBrowser.MediaEncoding.Probing
PixelFormat = streamInfo.PixelFormat,
NalLengthSize = streamInfo.NalLengthSize,
TimeBase = streamInfo.TimeBase,
- CodecTimeBase = streamInfo.CodecTimeBase
+ CodecTimeBase = streamInfo.CodecTimeBase,
+ IsAVC = streamInfo.IsAvc
};
- if (string.Equals(streamInfo.IsAvc, "true", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(streamInfo.IsAvc, "1", StringComparison.OrdinalIgnoreCase))
- {
- stream.IsAVC = true;
- }
- else if (string.Equals(streamInfo.IsAvc, "false", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(streamInfo.IsAvc, "0", StringComparison.OrdinalIgnoreCase))
- {
- stream.IsAVC = false;
- }
-
// Filter out junk
if (!string.IsNullOrWhiteSpace(streamInfo.CodecTagString) && !streamInfo.CodecTagString.Contains("[0]", StringComparison.OrdinalIgnoreCase))
{
@@ -678,18 +668,15 @@ namespace MediaBrowser.MediaEncoding.Probing
stream.Title = GetDictionaryValue(streamInfo.Tags, "title");
}
- if (string.Equals(streamInfo.CodecType, "audio", StringComparison.OrdinalIgnoreCase))
+ if (streamInfo.CodecType == CodecType.Audio)
{
stream.Type = MediaStreamType.Audio;
stream.Channels = streamInfo.Channels;
- if (!string.IsNullOrEmpty(streamInfo.SampleRate))
+ if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value))
{
- if (int.TryParse(streamInfo.SampleRate, NumberStyles.Any, CultureInfo.InvariantCulture, out var value))
- {
- stream.SampleRate = value;
- }
+ stream.SampleRate = value;
}
stream.ChannelLayout = ParseChannelLayout(streamInfo.ChannelLayout);
@@ -713,7 +700,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
}
- else if (string.Equals(streamInfo.CodecType, "subtitle", StringComparison.OrdinalIgnoreCase))
+ else if (streamInfo.CodecType == CodecType.Subtitle)
{
stream.Type = MediaStreamType.Subtitle;
stream.Codec = NormalizeSubtitleCodec(stream.Codec);
@@ -733,7 +720,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
}
- else if (string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase))
+ else if (streamInfo.CodecType == CodecType.Video)
{
stream.AverageFrameRate = GetFrameRate(streamInfo.AverageFrameRate);
stream.RealFrameRate = GetFrameRate(streamInfo.RFrameRate);
@@ -854,13 +841,12 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
}
- else if (string.Equals(streamInfo.CodecType, "data", StringComparison.OrdinalIgnoreCase))
+ else if (streamInfo.CodecType == CodecType.Data)
{
stream.Type = MediaStreamType.Data;
}
else
{
- _logger.LogError("Codec Type {CodecType} unknown. The stream (index: {Index}) will be ignored. Warning: Subsequential streams will have a wrong stream specifier!", streamInfo.CodecType, streamInfo.Index);
return null;
}
@@ -895,29 +881,26 @@ namespace MediaBrowser.MediaEncoding.Probing
// Extract bitrate info from tag "BPS" if possible.
if (!stream.BitRate.HasValue
- && (string.Equals(streamInfo.CodecType, "audio", StringComparison.OrdinalIgnoreCase)
- || string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase)))
+ && (streamInfo.CodecType == CodecType.Audio
+ || streamInfo.CodecType == CodecType.Video))
{
var bps = GetBPSFromTags(streamInfo);
if (bps > 0)
{
stream.BitRate = bps;
}
- }
-
- // Get average bitrate info from tag "NUMBER_OF_BYTES" and "DURATION" if possible.
- if (!stream.BitRate.HasValue
- && (string.Equals(streamInfo.CodecType, "audio", StringComparison.OrdinalIgnoreCase)
- || string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase)))
- {
- var durationInSeconds = GetRuntimeSecondsFromTags(streamInfo);
- var bytes = GetNumberOfBytesFromTags(streamInfo);
- if (durationInSeconds is not null && bytes is not null)
+ else
{
- var bps = Convert.ToInt32(bytes * 8 / durationInSeconds, CultureInfo.InvariantCulture);
- if (bps > 0)
+ // Get average bitrate info from tag "NUMBER_OF_BYTES" and "DURATION" if possible.
+ var durationInSeconds = GetRuntimeSecondsFromTags(streamInfo);
+ var bytes = GetNumberOfBytesFromTags(streamInfo);
+ if (durationInSeconds is not null && bytes is not null)
{
- stream.BitRate = bps;
+ bps = Convert.ToInt32(bytes * 8 / durationInSeconds, CultureInfo.InvariantCulture);
+ if (bps > 0)
+ {
+ stream.BitRate = bps;
+ }
}
}
}
@@ -948,12 +931,8 @@ namespace MediaBrowser.MediaEncoding.Probing
private void NormalizeStreamTitle(MediaStream stream)
{
- if (string.Equals(stream.Title, "cc", StringComparison.OrdinalIgnoreCase))
- {
- stream.Title = null;
- }
-
- if (stream.Type == MediaStreamType.EmbeddedImage)
+ if (string.Equals(stream.Title, "cc", StringComparison.OrdinalIgnoreCase)
+ || stream.Type == MediaStreamType.EmbeddedImage)
{
stream.Title = null;
}
@@ -984,7 +963,7 @@ namespace MediaBrowser.MediaEncoding.Probing
return null;
}
- return input.Split('(').FirstOrDefault();
+ return input.AsSpan().LeftPart('(').ToString();
}
private string GetAspectRatio(MediaStreamInfo info)
@@ -992,11 +971,11 @@ namespace MediaBrowser.MediaEncoding.Probing
var original = info.DisplayAspectRatio;
var parts = (original ?? string.Empty).Split(':');
- if (!(parts.Length == 2 &&
- 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))
+ if (!(parts.Length == 2
+ && 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))
{
width = info.Width;
height = info.Height;
@@ -1077,12 +1056,6 @@ namespace MediaBrowser.MediaEncoding.Probing
int index = value.IndexOf('/');
if (index == -1)
{
- // REVIEW: is this branch actually required? (i.e. does ffprobe ever output something other than a fraction?)
- if (float.TryParse(value, NumberStyles.AllowThousands | NumberStyles.Float, CultureInfo.InvariantCulture, out var result))
- {
- return result;
- }
-
return null;
}
@@ -1098,7 +1071,7 @@ namespace MediaBrowser.MediaEncoding.Probing
private void SetAudioRuntimeTicks(InternalMediaInfoResult result, MediaInfo data)
{
// Get the first info stream
- var stream = result.Streams?.FirstOrDefault(s => string.Equals(s.CodecType, "audio", StringComparison.OrdinalIgnoreCase));
+ var stream = result.Streams?.FirstOrDefault(s => s.CodecType == CodecType.Audio);
if (stream is null)
{
return;
@@ -1128,8 +1101,7 @@ namespace MediaBrowser.MediaEncoding.Probing
}
var bps = GetDictionaryValue(streamInfo.Tags, "BPS-eng") ?? GetDictionaryValue(streamInfo.Tags, "BPS");
- if (!string.IsNullOrEmpty(bps)
- && int.TryParse(bps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBps))
+ if (int.TryParse(bps, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBps))
{
return parsedBps;
}
@@ -1162,8 +1134,7 @@ namespace MediaBrowser.MediaEncoding.Probing
var numberOfBytes = GetDictionaryValue(streamInfo.Tags, "NUMBER_OF_BYTES-eng")
?? GetDictionaryValue(streamInfo.Tags, "NUMBER_OF_BYTES");
- if (!string.IsNullOrEmpty(numberOfBytes)
- && long.TryParse(numberOfBytes, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBytes))
+ if (long.TryParse(numberOfBytes, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedBytes))
{
return parsedBytes;
}
@@ -1455,7 +1426,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{
var disc = tags.GetValueOrDefault(tagName);
- if (!string.IsNullOrEmpty(disc) && int.TryParse(disc.AsSpan().LeftPart('/'), out var discNum))
+ if (int.TryParse(disc.AsSpan().LeftPart('/'), out var discNum))
{
return discNum;
}
diff --git a/MediaBrowser.Model/Dto/ImageOptions.cs b/MediaBrowser.Model/Dto/ImageOptions.cs
deleted file mode 100644
index 3f4405f1e..000000000
--- a/MediaBrowser.Model/Dto/ImageOptions.cs
+++ /dev/null
@@ -1,111 +0,0 @@
-#nullable disable
-using MediaBrowser.Model.Drawing;
-using MediaBrowser.Model.Entities;
-
-namespace MediaBrowser.Model.Dto
-{
- /// <summary>
- /// Class ImageOptions.
- /// </summary>
- public class ImageOptions
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="ImageOptions" /> class.
- /// </summary>
- public ImageOptions()
- {
- EnableImageEnhancers = true;
- }
-
- /// <summary>
- /// Gets or sets the type of the image.
- /// </summary>
- /// <value>The type of the image.</value>
- public ImageType ImageType { get; set; }
-
- /// <summary>
- /// Gets or sets the index of the image.
- /// </summary>
- /// <value>The index of the image.</value>
- public int? ImageIndex { get; set; }
-
- /// <summary>
- /// Gets or sets the width.
- /// </summary>
- /// <value>The width.</value>
- public int? Width { get; set; }
-
- /// <summary>
- /// Gets or sets the height.
- /// </summary>
- /// <value>The height.</value>
- public int? Height { get; set; }
-
- /// <summary>
- /// Gets or sets the width of the max.
- /// </summary>
- /// <value>The width of the max.</value>
- public int? MaxWidth { get; set; }
-
- /// <summary>
- /// Gets or sets the height of the max.
- /// </summary>
- /// <value>The height of the max.</value>
- public int? MaxHeight { get; set; }
-
- /// <summary>
- /// Gets or sets the quality.
- /// </summary>
- /// <value>The quality.</value>
- public int? Quality { get; set; }
-
- /// <summary>
- /// Gets or sets the image tag.
- /// If set this will result in strong, unconditional response caching.
- /// </summary>
- /// <value>The hash.</value>
- public string Tag { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether [crop whitespace].
- /// </summary>
- /// <value><c>null</c> if [crop whitespace] contains no value, <c>true</c> if [crop whitespace]; otherwise, <c>false</c>.</value>
- public bool? CropWhitespace { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether [enable image enhancers].
- /// </summary>
- /// <value><c>true</c> if [enable image enhancers]; otherwise, <c>false</c>.</value>
- public bool EnableImageEnhancers { get; set; }
-
- /// <summary>
- /// Gets or sets the format.
- /// </summary>
- /// <value>The format.</value>
- public ImageFormat? Format { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether [add played indicator].
- /// </summary>
- /// <value><c>true</c> if [add played indicator]; otherwise, <c>false</c>.</value>
- public bool AddPlayedIndicator { get; set; }
-
- /// <summary>
- /// Gets or sets the percent played.
- /// </summary>
- /// <value>The percent played.</value>
- public int? PercentPlayed { get; set; }
-
- /// <summary>
- /// Gets or sets the un played count.
- /// </summary>
- /// <value>The un played count.</value>
- public int? UnPlayedCount { get; set; }
-
- /// <summary>
- /// Gets or sets the color of the background.
- /// </summary>
- /// <value>The color of the background.</value>
- public string BackgroundColor { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Notifications/NotificationLevel.cs b/MediaBrowser.Model/Notifications/NotificationLevel.cs
deleted file mode 100644
index 14fead3f0..000000000
--- a/MediaBrowser.Model/Notifications/NotificationLevel.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Notifications
-{
- public enum NotificationLevel
- {
- Normal = 0,
- Warning = 1,
- Error = 2
- }
-}
diff --git a/MediaBrowser.Model/Notifications/NotificationOption.cs b/MediaBrowser.Model/Notifications/NotificationOption.cs
deleted file mode 100644
index 58aecb3d3..000000000
--- a/MediaBrowser.Model/Notifications/NotificationOption.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-#pragma warning disable CA1819 // Properties should not return arrays
-#pragma warning disable CS1591
-
-using System;
-
-namespace MediaBrowser.Model.Notifications
-{
- public class NotificationOption
- {
- public NotificationOption(string type)
- {
- Type = type;
- DisabledServices = Array.Empty<string>();
- DisabledMonitorUsers = Array.Empty<string>();
- SendToUsers = Array.Empty<string>();
- }
-
- public NotificationOption()
- {
- DisabledServices = Array.Empty<string>();
- DisabledMonitorUsers = Array.Empty<string>();
- SendToUsers = Array.Empty<string>();
- }
-
- public string? Type { get; set; }
-
- /// <summary>
- /// Gets or sets user Ids to not monitor (it's opt out).
- /// </summary>
- public string[] DisabledMonitorUsers { get; set; }
-
- /// <summary>
- /// Gets or sets user Ids to send to (if SendToUserMode == Custom).
- /// </summary>
- public string[] SendToUsers { get; set; }
-
- /// <summary>
- /// Gets or sets a value indicating whether this <see cref="NotificationOption"/> is enabled.
- /// </summary>
- /// <value><c>true</c> if enabled; otherwise, <c>false</c>.</value>
- public bool Enabled { get; set; }
-
- /// <summary>
- /// Gets or sets the disabled services.
- /// </summary>
- /// <value>The disabled services.</value>
- public string[] DisabledServices { get; set; }
-
- /// <summary>
- /// Gets or sets the send to user mode.
- /// </summary>
- /// <value>The send to user mode.</value>
- public SendToUserType SendToUserMode { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs
deleted file mode 100644
index 804f51e16..000000000
--- a/MediaBrowser.Model/Notifications/NotificationOptions.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System;
-using Jellyfin.Data.Entities;
-using Jellyfin.Data.Enums;
-using Jellyfin.Extensions;
-
-namespace MediaBrowser.Model.Notifications
-{
- public class NotificationOptions
- {
- public NotificationOptions()
- {
- Options = new[]
- {
- new NotificationOption(NotificationType.TaskFailed.ToString())
- {
- Enabled = true,
- SendToUserMode = SendToUserType.Admins
- },
- new NotificationOption(NotificationType.ServerRestartRequired.ToString())
- {
- Enabled = true,
- SendToUserMode = SendToUserType.Admins
- },
- new NotificationOption(NotificationType.ApplicationUpdateAvailable.ToString())
- {
- Enabled = true,
- SendToUserMode = SendToUserType.Admins
- },
- new NotificationOption(NotificationType.ApplicationUpdateInstalled.ToString())
- {
- Enabled = true,
- SendToUserMode = SendToUserType.Admins
- },
- new NotificationOption(NotificationType.PluginUpdateInstalled.ToString())
- {
- Enabled = true,
- SendToUserMode = SendToUserType.Admins
- },
- new NotificationOption(NotificationType.PluginUninstalled.ToString())
- {
- Enabled = true,
- SendToUserMode = SendToUserType.Admins
- },
- new NotificationOption(NotificationType.InstallationFailed.ToString())
- {
- Enabled = true,
- SendToUserMode = SendToUserType.Admins
- },
- new NotificationOption(NotificationType.PluginInstalled.ToString())
- {
- Enabled = true,
- SendToUserMode = SendToUserType.Admins
- },
- new NotificationOption(NotificationType.PluginError.ToString())
- {
- Enabled = true,
- SendToUserMode = SendToUserType.Admins
- },
- new NotificationOption(NotificationType.UserLockedOut.ToString())
- {
- Enabled = true,
- SendToUserMode = SendToUserType.Admins
- }
- };
- }
-
- public NotificationOption[] Options { get; set; }
-
- public NotificationOption GetOptions(string type)
- {
- foreach (NotificationOption i in Options)
- {
- if (string.Equals(type, i.Type, StringComparison.OrdinalIgnoreCase))
- {
- return i;
- }
- }
-
- return null;
- }
-
- public bool IsEnabled(string type)
- {
- NotificationOption opt = GetOptions(type);
-
- return opt is not null && opt.Enabled;
- }
-
- public bool IsServiceEnabled(string service, string notificationType)
- {
- NotificationOption opt = GetOptions(notificationType);
-
- return opt is null
- || !opt.DisabledServices.Contains(service, StringComparison.OrdinalIgnoreCase);
- }
-
- public bool IsEnabledToMonitorUser(string type, Guid userId)
- {
- NotificationOption opt = GetOptions(type);
-
- return opt is not null
- && opt.Enabled
- && !opt.DisabledMonitorUsers.Contains(userId.ToString("N"), StringComparison.OrdinalIgnoreCase);
- }
-
- public bool IsEnabledToSendToUser(string type, string userId, User user)
- {
- NotificationOption opt = GetOptions(type);
-
- if (opt is not null && opt.Enabled)
- {
- if (opt.SendToUserMode == SendToUserType.All)
- {
- return true;
- }
-
- if (opt.SendToUserMode == SendToUserType.Admins && user.HasPermission(PermissionKind.IsAdministrator))
- {
- return true;
- }
-
- return opt.SendToUsers.Contains(userId, StringComparison.OrdinalIgnoreCase);
- }
-
- return false;
- }
- }
-}
diff --git a/MediaBrowser.Model/Notifications/NotificationRequest.cs b/MediaBrowser.Model/Notifications/NotificationRequest.cs
deleted file mode 100644
index 622c50cd8..000000000
--- a/MediaBrowser.Model/Notifications/NotificationRequest.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-using System;
-
-namespace MediaBrowser.Model.Notifications
-{
- public class NotificationRequest
- {
- public NotificationRequest()
- {
- UserIds = Array.Empty<Guid>();
- Date = DateTime.UtcNow;
- }
-
- public string Name { get; set; }
-
- public string Description { get; set; }
-
- public string Url { get; set; }
-
- public NotificationLevel Level { get; set; }
-
- public Guid[] UserIds { get; set; }
-
- public DateTime Date { get; set; }
-
- /// <summary>
- /// Gets or sets the corresponding type name used in configuration. Not for display.
- /// </summary>
- public string NotificationType { get; set; }
-
- public SendToUserType? SendToUserMode { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs b/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs
deleted file mode 100644
index 402fbe81a..000000000
--- a/MediaBrowser.Model/Notifications/NotificationTypeInfo.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-#nullable disable
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Notifications
-{
- public class NotificationTypeInfo
- {
- public string Type { get; set; }
-
- public string Name { get; set; }
-
- public bool Enabled { get; set; }
-
- public string Category { get; set; }
-
- public bool IsBasedOnUserEvent { get; set; }
- }
-}
diff --git a/MediaBrowser.Model/Notifications/SendToUserType.cs b/MediaBrowser.Model/Notifications/SendToUserType.cs
deleted file mode 100644
index 65fc4e1ab..000000000
--- a/MediaBrowser.Model/Notifications/SendToUserType.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Model.Notifications
-{
- public enum SendToUserType
- {
- All = 0,
- Admins = 1,
- Custom = 2
- }
-}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 0ce696edc..c07839ff2 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -910,19 +910,34 @@ namespace MediaBrowser.Providers.Manager
/// <inheritdoc/>
public void OnRefreshStart(BaseItem item)
{
- _logger.LogDebug("OnRefreshStart {Item}", item.Id.ToString("N", CultureInfo.InvariantCulture));
+ _logger.LogDebug("OnRefreshStart {Item:N}", item.Id);
_activeRefreshes[item.Id] = 0;
- RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
+ try
+ {
+ RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
+ }
+ catch (Exception ex)
+ {
+ // EventHandlers should never propagate exceptions, but we have little control over plugins...
+ _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshStarted));
+ }
}
/// <inheritdoc/>
public void OnRefreshComplete(BaseItem item)
{
- _logger.LogDebug("OnRefreshComplete {Item}", item.Id.ToString("N", CultureInfo.InvariantCulture));
-
- _activeRefreshes.Remove(item.Id, out _);
+ _logger.LogDebug("OnRefreshComplete {Item:N}", item.Id);
+ _activeRefreshes.TryRemove(item.Id, out _);
- RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
+ try
+ {
+ RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
+ }
+ catch (Exception ex)
+ {
+ // EventHandlers should never propagate exceptions, but we have little control over plugins...
+ _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshCompleted));
+ }
}
/// <inheritdoc/>
@@ -940,12 +955,12 @@ namespace MediaBrowser.Providers.Manager
public void OnRefreshProgress(BaseItem item, double progress)
{
var id = item.Id;
- _logger.LogDebug("OnRefreshProgress {Id} {Progress}", id.ToString("N", CultureInfo.InvariantCulture), progress);
+ _logger.LogDebug("OnRefreshProgress {Id:N} {Progress}", id, progress);
// TODO: Need to hunt down the conditions for this happening
_activeRefreshes.AddOrUpdate(
id,
- (_) => throw new InvalidOperationException(
+ _ => throw new InvalidOperationException(
string.Format(
CultureInfo.InvariantCulture,
"Cannot update refresh progress of item '{0}' ({1}) because a refresh for this item is not running",
@@ -953,7 +968,15 @@ namespace MediaBrowser.Providers.Manager
item.Id.ToString("N", CultureInfo.InvariantCulture))),
(_, _) => progress);
- RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
+ try
+ {
+ RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
+ }
+ catch (Exception ex)
+ {
+ // EventHandlers should never propagate exceptions, but we have little control over plugins...
+ _logger.LogError(ex, "Invoking {RefreshEvent} event handlers failed", nameof(RefreshProgress));
+ }
}
/// <inheritdoc/>
diff --git a/src/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs b/src/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs
deleted file mode 100644
index 5bb42fb99..000000000
--- a/src/Jellyfin.Drawing.Skia/PlayedIndicatorDrawer.cs
+++ /dev/null
@@ -1,47 +0,0 @@
-using MediaBrowser.Model.Drawing;
-using SkiaSharp;
-
-namespace Jellyfin.Drawing.Skia;
-
-/// <summary>
-/// Static helper class for drawing 'played' indicators.
-/// </summary>
-public static class PlayedIndicatorDrawer
-{
- private const int OffsetFromTopRightCorner = 38;
-
- /// <summary>
- /// Draw a 'played' indicator in the top right corner of a canvas.
- /// </summary>
- /// <param name="canvas">The canvas to draw the indicator on.</param>
- /// <param name="imageSize">
- /// The dimensions of the image to draw the indicator on. The width is used to determine the x-position of the
- /// indicator.
- /// </param>
- public static void DrawPlayedIndicator(SKCanvas canvas, ImageDimensions imageSize)
- {
- var x = imageSize.Width - OffsetFromTopRightCorner;
-
- using var paint = new SKPaint
- {
- Color = SKColor.Parse("#CC00A4DC"),
- Style = SKPaintStyle.Fill
- };
-
- canvas.DrawCircle(x, OffsetFromTopRightCorner, 20, paint);
-
- paint.Color = new SKColor(255, 255, 255, 255);
- paint.TextSize = 30;
- paint.IsAntialias = true;
-
- // or:
- // var emojiChar = 0x1F680;
- const string Text = "✔️";
- var emojiChar = StringUtilities.GetUnicodeCharacterCode(Text, SKTextEncoding.Utf32);
-
- // ask the font manager for a font with that character
- paint.Typeface = SKFontManager.Default.MatchCharacter(emojiChar);
-
- canvas.DrawText(Text, (float)x - 12, OffsetFromTopRightCorner + 12, paint);
- }
-}
diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
index ddb8a98d4..6da77ad95 100644
--- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
+++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs
@@ -399,7 +399,7 @@ public class SkiaEncoder : IImageEncoder
var hasBackgroundColor = !string.IsNullOrWhiteSpace(options.BackgroundColor);
var hasForegroundColor = !string.IsNullOrWhiteSpace(options.ForegroundLayer);
var blur = options.Blur ?? 0;
- var hasIndicator = options.AddPlayedIndicator || options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0);
+ var hasIndicator = options.UnplayedCount.HasValue || !options.PercentPlayed.Equals(0);
using var bitmap = GetBitmap(inputPath, autoOrient, orientation);
if (bitmap is null)
@@ -522,11 +522,7 @@ public class SkiaEncoder : IImageEncoder
{
var currentImageSize = new ImageDimensions(imageWidth, imageHeight);
- if (options.AddPlayedIndicator)
- {
- PlayedIndicatorDrawer.DrawPlayedIndicator(canvas, currentImageSize);
- }
- else if (options.UnplayedCount.HasValue)
+ if (options.UnplayedCount.HasValue)
{
UnplayedCountIndicator.DrawUnplayedCountIndicator(canvas, currentImageSize, options.UnplayedCount.Value);
}
diff --git a/src/Jellyfin.Drawing/ImageProcessor.cs b/src/Jellyfin.Drawing/ImageProcessor.cs
index 353a27b25..533baba4f 100644
--- a/src/Jellyfin.Drawing/ImageProcessor.cs
+++ b/src/Jellyfin.Drawing/ImageProcessor.cs
@@ -38,7 +38,6 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
private readonly IImageEncoder _imageEncoder;
- private readonly IMediaEncoder _mediaEncoder;
private readonly SemaphoreSlim _parallelEncodingLimit;
@@ -64,7 +63,6 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
_logger = logger;
_fileSystem = fileSystem;
_imageEncoder = imageEncoder;
- _mediaEncoder = mediaEncoder;
_appPaths = appPaths;
var semaphoreCount = config.Configuration.ParallelImageEncodingLimit;
@@ -202,7 +200,6 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
quality,
dateModified,
outputFormat,
- options.AddPlayedIndicator,
options.PercentPlayed,
options.UnplayedCount,
options.Blur,
@@ -295,7 +292,6 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
int quality,
DateTime dateModified,
ImageFormat format,
- bool addPlayedIndicator,
double percentPlayed,
int? unwatchedCount,
int? blur,
@@ -350,11 +346,6 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
filename.Append(fillHeight.Value);
}
- if (addPlayedIndicator)
- {
- filename.Append(",pl=true");
- }
-
if (percentPlayed > 0)
{
filename.Append(",p=");
diff --git a/src/Jellyfin.Extensions/Json/Converters/JsonBoolStringConverter.cs b/src/Jellyfin.Extensions/Json/Converters/JsonBoolStringConverter.cs
new file mode 100644
index 000000000..2936fe4d6
--- /dev/null
+++ b/src/Jellyfin.Extensions/Json/Converters/JsonBoolStringConverter.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Buffers;
+using System.Buffers.Text;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Jellyfin.Extensions.Json.Converters;
+
+/// <summary>
+/// Converts a string to a boolean.
+/// This is needed for FFprobe.
+/// </summary>
+public class JsonBoolStringConverter : JsonConverter<bool>
+{
+ /// <inheritdoc />
+ public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType == JsonTokenType.String)
+ {
+ ReadOnlySpan<byte> utf8Span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
+ if (Utf8Parser.TryParse(utf8Span, out bool val, out _, 'l'))
+ {
+ return val;
+ }
+ }
+
+ return reader.GetBoolean();
+ }
+
+ /// <inheritdoc />
+ public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options)
+ => writer.WriteBooleanValue(value);
+}
diff --git a/src/Jellyfin.Extensions/Json/JsonDefaults.cs b/src/Jellyfin.Extensions/Json/JsonDefaults.cs
index 97cbee971..4d56ca615 100644
--- a/src/Jellyfin.Extensions/Json/JsonDefaults.cs
+++ b/src/Jellyfin.Extensions/Json/JsonDefaults.cs
@@ -39,7 +39,6 @@ namespace Jellyfin.Extensions.Json
new JsonFlagEnumConverterFactory(),
new JsonStringEnumConverter(),
new JsonNullableStructConverterFactory(),
- new JsonBoolNumberConverter(),
new JsonDateTimeConverter(),
new JsonStringConverter()
}
diff --git a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolStringTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolStringTests.cs
new file mode 100644
index 000000000..be256da2e
--- /dev/null
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonBoolStringTests.cs
@@ -0,0 +1,37 @@
+using System.Text.Json;
+using Jellyfin.Extensions.Json.Converters;
+using Xunit;
+
+namespace Jellyfin.Extensions.Tests.Json.Converters
+{
+ public class JsonBoolStringTests
+ {
+ private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions()
+ {
+ Converters =
+ {
+ new JsonBoolStringConverter()
+ }
+ };
+
+ [Theory]
+ [InlineData(@"{ ""Value"": ""true"" }", true)]
+ [InlineData(@"{ ""Value"": ""false"" }", false)]
+ public void Deserialize_String_Valid_Success(string input, bool output)
+ {
+ var s = JsonSerializer.Deserialize<TestStruct>(input, _jsonOptions);
+ Assert.Equal(s.Value, output);
+ }
+
+ [Theory]
+ [InlineData(true, "true")]
+ [InlineData(false, "false")]
+ public void Serialize_Bool_Success(bool input, string output)
+ {
+ var value = JsonSerializer.Serialize(input, _jsonOptions);
+ Assert.Equal(value, output);
+ }
+
+ private readonly record struct TestStruct(bool Value);
+ }
+}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs b/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
deleted file mode 100644
index 97dbb3be0..000000000
--- a/tests/Jellyfin.MediaEncoding.Tests/FFprobeParserTests.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.IO;
-using System.Text.Json;
-using System.Threading.Tasks;
-using Jellyfin.Extensions.Json;
-using MediaBrowser.MediaEncoding.Probing;
-using MediaBrowser.Model.IO;
-using Xunit;
-
-namespace Jellyfin.MediaEncoding.Tests
-{
- public class FFprobeParserTests
- {
- [Theory]
- [InlineData("ffprobe1.json")]
- public async Task Test(string fileName)
- {
- var path = Path.Join("Test Data", fileName);
- await using (var stream = AsyncFile.OpenRead(path))
- {
- var res = await JsonSerializer.DeserializeAsync<InternalMediaInfoResult>(stream, JsonDefaults.Options).ConfigureAwait(false);
- Assert.NotNull(res);
- }
- }
- }
-}
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
index a64604e99..6cb98b2b8 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.IO;
using System.Text.Json;
using Jellyfin.Extensions.Json;
+using Jellyfin.Extensions.Json.Converters;
using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
@@ -15,9 +16,15 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
{
public class ProbeResultNormalizerTests
{
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
+ private readonly JsonSerializerOptions _jsonOptions;
private readonly ProbeResultNormalizer _probeResultNormalizer = new ProbeResultNormalizer(new NullLogger<EncoderValidatorTests>(), null);
+ public ProbeResultNormalizerTests()
+ {
+ _jsonOptions = new JsonSerializerOptions(JsonDefaults.Options);
+ _jsonOptions.Converters.Add(new JsonBoolStringConverter());
+ }
+
[Theory]
[InlineData("2997/125", 23.976f)]
[InlineData("1/50", 0.02f)]
@@ -149,6 +156,19 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
}
[Fact]
+ public void GetMediaInfo_TS_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/Probing/video_ts.json");
+ var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions);
+
+ MediaInfo res = _probeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/video_metadata.mkv", MediaProtocol.File);
+
+ Assert.Equal(2, res.MediaStreams.Count);
+
+ Assert.False(res.MediaStreams[0].IsAVC);
+ }
+
+ [Fact]
public void GetMediaInfo_ProgressiveVideoNoFieldOrder_Success()
{
var bytes = File.ReadAllBytes("Test Data/Probing/video_progressive_no_field_order.json");
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_ts.json
index cdad5df50..cdad5df50 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Test Data/ffprobe1.json
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_ts.json
diff --git a/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs b/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs
new file mode 100644
index 000000000..f8f5fecec
--- /dev/null
+++ b/tests/Jellyfin.Server.Integration.Tests/Controllers/PlaystateControllerTests.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Net;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Xunit;
+using Xunit.Priority;
+
+namespace Jellyfin.Server.Integration.Tests.Controllers;
+
+[TestCaseOrderer(PriorityOrderer.Name, PriorityOrderer.Assembly)]
+public class PlaystateControllerTests : IClassFixture<JellyfinApplicationFactory>
+{
+ private readonly JellyfinApplicationFactory _factory;
+ private static readonly Guid _testUserId = Guid.NewGuid();
+ private static readonly Guid _testItemId = Guid.NewGuid();
+ private static string? _accessToken;
+
+ public PlaystateControllerTests(JellyfinApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ private Task<HttpResponseMessage> DeleteUserPlayedItems(HttpClient httpClient, Guid userId, Guid itemId)
+ => httpClient.DeleteAsync($"Users/{userId}/PlayedItems/{itemId}");
+
+ private Task<HttpResponseMessage> PostUserPlayedItems(HttpClient httpClient, Guid userId, Guid itemId)
+ => httpClient.PostAsync($"Users/{userId}/PlayedItems/{itemId}", null);
+
+ [Fact]
+ [Priority(0)]
+ public async Task DeleteMarkUnplayedItem_DoesNotExist_NotFound()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var response = await DeleteUserPlayedItems(client, _testUserId, _testItemId).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+
+ [Fact]
+ [Priority(0)]
+ public async Task PostMarkPlayedItem_DoesNotExist_NotFound()
+ {
+ var client = _factory.CreateClient();
+ client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
+
+ using var response = await PostUserPlayedItems(client, _testUserId, _testItemId).ConfigureAwait(false);
+ Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
+ }
+}