Compare commits

...

31 Commits

Author SHA1 Message Date
Jellyfin Release Bot
0eb5897100 Bump version to 10.9.9 2024-08-04 22:01:35 -04:00
Nyanmisaka
ee0094d889 Fix compatibility between TranscodingThrottler and FFmpeg 7.0 (#12374) 2024-08-04 20:00:57 -06:00
Bond-009
7051a18be0 Merge pull request #12368 from Bond-009/updateserilogasp
Update Serilog deps
2024-08-01 20:03:20 +02:00
Bond_009
fce3a5d241 Update Serilog deps 2024-08-01 16:40:13 +02:00
Niels van Velzen
900acc03aa Fix creating virtual seasons (again) (#12356) 2024-07-30 09:51:43 -06:00
renovate[bot]
a475a7d50a Update dependency libse to v4.0.7 2024-07-24 09:35:40 -06:00
Joshua M. Boniface
b7bc0e1c96 Merge pull request #11901 from gnattu/remove-efcore-secondlevelcache
Implement Device Cache to replace EFCoreSecondLevelCacheInterceptor
2024-07-21 01:19:35 -04:00
Jellyfin Release Bot
3c79d7a3f3 Bump version to 10.9.8 2024-07-21 01:11:35 -04:00
gnattu
1739962f52 Update Jellyfin.Server.Implementations/Devices/DeviceManager.cs
Co-authored-by: Bond-009 <bond.009@outlook.com>
2024-07-20 20:42:43 +08:00
gnattu
7f0f93eb4a Update Jellyfin.Server.Implementations/Devices/DeviceManager.cs
Co-authored-by: Bond-009 <bond.009@outlook.com>
2024-07-20 20:42:31 +08:00
gnattu
71c13057f4 Remove package reference of EFCoreSecondLevelCacheInterceptor
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:20:08 +08:00
gnattu
7f12677dc3 Directly add new device to cache
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
4f2b1736ab Add user not found message
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
c05049e54e Filter full device list only once
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
dd5f6406a2 Use FirstAsync for device creation
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
79c0a7d7f0 Don't cache user in DeviceManager, query from user cache instead
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
3c6485f0a1 Get device id from input
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
8a8b2c4380 Explicitly declare type of devices
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
7403428864 Always enumerate to get count
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
235da65a75 Use concrete ConcurrentDictionary Type
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
26eab7aa2e Remove env var for second level cache
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
d235378133 Query User on device creation
Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
gnattu
5a62c7a146 Implement Device Cache to replace EFCoreSecondLevelCacheInterceptor
The EFCoreSecondLevelCacheInterceptor will place a huge lock even for reading. Implement a ConcurrentDictionary cache to replace it.

Signed-off-by: gnattu <gnattuoc@me.com>
2024-07-20 20:19:31 +08:00
Bond-009
4afa6db108 Properly escape paths in concat file for BDMV (#12296) 2024-07-17 12:33:11 -04:00
Bond-009
f7a90b6383 Merge pull request #12278 from Bond-009/localizeaudio
Fix localization of audio title
2024-07-16 14:45:13 +02:00
Bond_009
b8e2d8e11a Fix tests 2024-07-15 14:40:12 +02:00
Niels van Velzen
c1f7ccbca4 Fix season handling ("Season Unknown" / unneccesary empty seasons) (#12240) 2024-07-15 08:27:19 -04:00
Bond_009
5bab02fa54 Fix localization of audio title
Maybe passing ILocalizationManager into the ctor of MediaStream is a better solution for master?
2024-07-15 14:27:12 +02:00
Bond-009
f2fa0b9025 Merge pull request #12246 from jellyfin/renovate/dotnet-monorepo
Update dotnet monorepo

(cherry picked from commit 5ef76a5e31)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2024-07-12 09:47:58 -04:00
Bond-009
9c0edd2905 Merge pull request #12259 from jellyfin/renovate/serilog.settings.configuration-8.x
Update dependency Serilog.Settings.Configuration to v8.0.2

(cherry picked from commit c050abf3e8)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2024-07-12 09:47:23 -04:00
Bond-009
62deebc04c Merge pull request #12248 from jellyfin/renovate/dotnet-monorepo
Update dotnet monorepo to v8.0.7

(cherry picked from commit f62af90ae3)
Signed-off-by: Joshua M. Boniface <joshua@boniface.me>
2024-07-12 09:44:46 -04:00
31 changed files with 212 additions and 188 deletions

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "8.0.4",
"version": "8.0.7",
"commands": [
"dotnet-ef"
]

View File

@@ -16,34 +16,33 @@
<PackageVersion Include="Diacritics" Version="3.3.29" />
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.5.0" />
<PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.2" />
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.7" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="4.0.5" />
<PackageVersion Include="libse" Version="4.0.7" />
<PackageVersion Include="LrcParser" Version="2023.524.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.6" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.6" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.7" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.6" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.6" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.6" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.6" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.7" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
@@ -59,12 +58,12 @@
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.1" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.2" />
<PackageVersion Include="Serilog.Sinks.Async" Version="2.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
<PackageVersion Include="SharpFuzz" Version="2.1.1" />
@@ -79,8 +78,8 @@
<PackageVersion Include="System.Globalization" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.3" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.1" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="TMDbLib" Version="2.2.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />

View File

@@ -36,7 +36,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.9.7</VersionPrefix>
<VersionPrefix>10.9.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -19,8 +19,7 @@ namespace Emby.Server.Implementations
{ FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.FalseString },
{ BindToUnixSocketKey, bool.FalseString },
{ SqliteCacheSizeKey, "20000" },
{ SqliteDisableSecondLevelCacheKey, bool.FalseString }
{ SqliteCacheSizeKey, "20000" }
};
}
}

View File

@@ -5694,13 +5694,17 @@ AND Type = @InternalPersonType)");
item.IsHearingImpaired = reader.TryGetBoolean(43, out var result) && result;
if (item.Type == MediaStreamType.Subtitle)
if (item.Type is MediaStreamType.Audio or MediaStreamType.Subtitle)
{
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
item.LocalizedDefault = _localization.GetLocalizedString("Default");
item.LocalizedForced = _localization.GetLocalizedString("Forced");
item.LocalizedExternal = _localization.GetLocalizedString("External");
item.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
if (item.Type is MediaStreamType.Subtitle)
{
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
item.LocalizedForced = _localization.GetLocalizedString("Forced");
item.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
}
}
return item;

View File

@@ -237,7 +237,7 @@ namespace Emby.Server.Implementations.Session
ArgumentException.ThrowIfNullOrEmpty(deviceId);
var activityDate = DateTime.UtcNow;
var session = await GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false);
var session = GetSessionInfo(appName, appVersion, deviceId, deviceName, remoteEndPoint, user);
var lastActivityDate = session.LastActivityDate;
session.LastActivityDate = activityDate;
@@ -435,7 +435,7 @@ namespace Emby.Server.Implementations.Session
/// <param name="remoteEndPoint">The remote end point.</param>
/// <param name="user">The user.</param>
/// <returns>SessionInfo.</returns>
private async Task<SessionInfo> GetSessionInfo(
private SessionInfo GetSessionInfo(
string appName,
string appVersion,
string deviceId,
@@ -453,7 +453,7 @@ namespace Emby.Server.Implementations.Session
if (!_activeConnections.TryGetValue(key, out var sessionInfo))
{
sessionInfo = await CreateSession(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user).ConfigureAwait(false);
sessionInfo = CreateSession(key, appName, appVersion, deviceId, deviceName, remoteEndPoint, user);
_activeConnections[key] = sessionInfo;
}
@@ -478,7 +478,7 @@ namespace Emby.Server.Implementations.Session
return sessionInfo;
}
private async Task<SessionInfo> CreateSession(
private SessionInfo CreateSession(
string key,
string appName,
string appVersion,
@@ -508,7 +508,7 @@ namespace Emby.Server.Implementations.Session
deviceName = "Network Device";
}
var deviceOptions = await _deviceManager.GetDeviceOptions(deviceId).ConfigureAwait(false);
var deviceOptions = _deviceManager.GetDeviceOptions(deviceId);
if (string.IsNullOrEmpty(deviceOptions.CustomName))
{
sessionInfo.DeviceName = deviceName;
@@ -1297,7 +1297,7 @@ namespace Emby.Server.Implementations.Session
return new[] { item };
}
private IEnumerable<BaseItem> TranslateItemForInstantMix(Guid id, User user)
private List<BaseItem> TranslateItemForInstantMix(Guid id, User user)
{
var item = _libraryManager.GetItemById(id);
@@ -1307,7 +1307,7 @@ namespace Emby.Server.Implementations.Session
return new List<BaseItem>();
}
return _musicManager.GetInstantMixFromItem(item, user, new DtoOptions(false) { EnableImages = false });
return _musicManager.GetInstantMixFromItem(item, user, new DtoOptions(false) { EnableImages = false }).ToList();
}
/// <inheritdoc />
@@ -1520,12 +1520,12 @@ namespace Emby.Server.Implementations.Session
// This should be validated above, but if it isn't don't delete all tokens.
ArgumentException.ThrowIfNullOrEmpty(deviceId);
var existing = (await _deviceManager.GetDevices(
var existing = _deviceManager.GetDevices(
new DeviceQuery
{
DeviceId = deviceId,
UserId = user.Id
}).ConfigureAwait(false)).Items;
}).Items;
foreach (var auth in existing)
{
@@ -1553,12 +1553,12 @@ namespace Emby.Server.Implementations.Session
ArgumentException.ThrowIfNullOrEmpty(accessToken);
var existing = (await _deviceManager.GetDevices(
var existing = _deviceManager.GetDevices(
new DeviceQuery
{
Limit = 1,
AccessToken = accessToken
}).ConfigureAwait(false)).Items;
}).Items;
if (existing.Count > 0)
{
@@ -1597,10 +1597,10 @@ namespace Emby.Server.Implementations.Session
{
CheckDisposed();
var existing = await _deviceManager.GetDevices(new DeviceQuery
var existing = _deviceManager.GetDevices(new DeviceQuery
{
UserId = userId
}).ConfigureAwait(false);
});
foreach (var info in existing.Items)
{
@@ -1787,11 +1787,11 @@ namespace Emby.Server.Implementations.Session
/// <inheritdoc />
public async Task<SessionInfo> GetSessionByAuthenticationToken(string token, string deviceId, string remoteEndpoint)
{
var items = (await _deviceManager.GetDevices(new DeviceQuery
var items = _deviceManager.GetDevices(new DeviceQuery
{
AccessToken = token,
Limit = 1
}).ConfigureAwait(false)).Items;
}).Items;
if (items.Count == 0)
{

View File

@@ -47,10 +47,10 @@ public class DevicesController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the list of devices.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] Guid? userId)
public ActionResult<QueryResult<DeviceInfo>> GetDevices([FromQuery] Guid? userId)
{
userId = RequestHelpers.GetUserId(User, userId);
return await _deviceManager.GetDevicesForUser(userId).ConfigureAwait(false);
return _deviceManager.GetDevicesForUser(userId);
}
/// <summary>
@@ -63,9 +63,9 @@ public class DevicesController : BaseJellyfinApiController
[HttpGet("Info")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<DeviceInfo>> GetDeviceInfo([FromQuery, Required] string id)
public ActionResult<DeviceInfo> GetDeviceInfo([FromQuery, Required] string id)
{
var deviceInfo = await _deviceManager.GetDevice(id).ConfigureAwait(false);
var deviceInfo = _deviceManager.GetDevice(id);
if (deviceInfo is null)
{
return NotFound();
@@ -84,9 +84,9 @@ public class DevicesController : BaseJellyfinApiController
[HttpGet("Options")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<DeviceOptions>> GetDeviceOptions([FromQuery, Required] string id)
public ActionResult<DeviceOptions> GetDeviceOptions([FromQuery, Required] string id)
{
var deviceInfo = await _deviceManager.GetDeviceOptions(id).ConfigureAwait(false);
var deviceInfo = _deviceManager.GetDeviceOptions(id);
if (deviceInfo is null)
{
return NotFound();
@@ -124,13 +124,13 @@ public class DevicesController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeleteDevice([FromQuery, Required] string id)
{
var existingDevice = await _deviceManager.GetDevice(id).ConfigureAwait(false);
var existingDevice = _deviceManager.GetDevice(id);
if (existingDevice is null)
{
return NotFound();
}
var sessions = await _deviceManager.GetDevices(new DeviceQuery { DeviceId = id }).ConfigureAwait(false);
var sessions = _deviceManager.GetDevices(new DeviceQuery { DeviceId = id });
foreach (var session in sessions.Items)
{

View File

@@ -18,7 +18,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Data</PackageId>
<VersionPrefix>10.9.7</VersionPrefix>
<VersionPrefix>10.9.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -27,6 +27,8 @@ namespace Jellyfin.Server.Implementations.Devices
private readonly IDbContextFactory<JellyfinDbContext> _dbProvider;
private readonly IUserManager _userManager;
private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new();
private readonly ConcurrentDictionary<int, Device> _devices;
private readonly ConcurrentDictionary<string, DeviceOptions> _deviceOptions;
/// <summary>
/// Initializes a new instance of the <see cref="DeviceManager"/> class.
@@ -37,6 +39,23 @@ namespace Jellyfin.Server.Implementations.Devices
{
_dbProvider = dbProvider;
_userManager = userManager;
_devices = new ConcurrentDictionary<int, Device>();
_deviceOptions = new ConcurrentDictionary<string, DeviceOptions>();
using var dbContext = _dbProvider.CreateDbContext();
foreach (var device in dbContext.Devices
.OrderBy(d => d.Id)
.AsEnumerable())
{
_devices.TryAdd(device.Id, device);
}
foreach (var deviceOption in dbContext.DeviceOptions
.OrderBy(d => d.Id)
.AsEnumerable())
{
_deviceOptions.TryAdd(deviceOption.DeviceId, deviceOption);
}
}
/// <inheritdoc />
@@ -66,6 +85,8 @@ namespace Jellyfin.Server.Implementations.Devices
await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
_deviceOptions[deviceId] = deviceOptions;
DeviceOptionsUpdated?.Invoke(this, new GenericEventArgs<Tuple<string, DeviceOptions>>(new Tuple<string, DeviceOptions>(deviceId, deviceOptions)));
}
@@ -76,25 +97,17 @@ namespace Jellyfin.Server.Implementations.Devices
await using (dbContext.ConfigureAwait(false))
{
dbContext.Devices.Add(device);
await dbContext.SaveChangesAsync().ConfigureAwait(false);
_devices.TryAdd(device.Id, device);
}
return device;
}
/// <inheritdoc />
public async Task<DeviceOptions> GetDeviceOptions(string deviceId)
public DeviceOptions GetDeviceOptions(string deviceId)
{
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
DeviceOptions? deviceOptions;
await using (dbContext.ConfigureAwait(false))
{
deviceOptions = await dbContext.DeviceOptions
.AsNoTracking()
.FirstOrDefaultAsync(d => d.DeviceId == deviceId)
.ConfigureAwait(false);
}
_deviceOptions.TryGetValue(deviceId, out var deviceOptions);
return deviceOptions ?? new DeviceOptions(deviceId);
}
@@ -108,57 +121,43 @@ namespace Jellyfin.Server.Implementations.Devices
}
/// <inheritdoc />
public async Task<DeviceInfo?> GetDevice(string id)
public DeviceInfo? GetDevice(string id)
{
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
var device = await dbContext.Devices
.Where(d => d.DeviceId == id)
.OrderByDescending(d => d.DateLastActivity)
.Include(d => d.User)
.SelectMany(d => dbContext.DeviceOptions.Where(o => o.DeviceId == d.DeviceId).DefaultIfEmpty(), (d, o) => new { Device = d, Options = o })
.FirstOrDefaultAsync()
.ConfigureAwait(false);
var device = _devices.Values.Where(d => d.DeviceId == id).OrderByDescending(d => d.DateLastActivity).FirstOrDefault();
_deviceOptions.TryGetValue(id, out var deviceOption);
var deviceInfo = device is null ? null : ToDeviceInfo(device.Device, device.Options);
return deviceInfo;
}
var deviceInfo = device is null ? null : ToDeviceInfo(device, deviceOption);
return deviceInfo;
}
/// <inheritdoc />
public async Task<QueryResult<Device>> GetDevices(DeviceQuery query)
public QueryResult<Device> GetDevices(DeviceQuery query)
{
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
IEnumerable<Device> devices = _devices.Values
.Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value))
.Where(device => query.DeviceId == null || device.DeviceId == query.DeviceId)
.Where(device => query.AccessToken == null || device.AccessToken == query.AccessToken)
.OrderBy(d => d.Id)
.ToList();
var count = devices.Count();
if (query.Skip.HasValue)
{
var devices = dbContext.Devices
.OrderBy(d => d.Id)
.Where(device => !query.UserId.HasValue || device.UserId.Equals(query.UserId.Value))
.Where(device => query.DeviceId == null || device.DeviceId == query.DeviceId)
.Where(device => query.AccessToken == null || device.AccessToken == query.AccessToken);
var count = await devices.CountAsync().ConfigureAwait(false);
if (query.Skip.HasValue)
{
devices = devices.Skip(query.Skip.Value);
}
if (query.Limit.HasValue)
{
devices = devices.Take(query.Limit.Value);
}
return new QueryResult<Device>(query.Skip, count, await devices.ToListAsync().ConfigureAwait(false));
devices = devices.Skip(query.Skip.Value);
}
if (query.Limit.HasValue)
{
devices = devices.Take(query.Limit.Value);
}
return new QueryResult<Device>(query.Skip, count, devices.ToList());
}
/// <inheritdoc />
public async Task<QueryResult<DeviceInfo>> GetDeviceInfos(DeviceQuery query)
public QueryResult<DeviceInfo> GetDeviceInfos(DeviceQuery query)
{
var devices = await GetDevices(query).ConfigureAwait(false);
var devices = GetDevices(query);
return new QueryResult<DeviceInfo>(
devices.StartIndex,
@@ -167,38 +166,36 @@ namespace Jellyfin.Server.Implementations.Devices
}
/// <inheritdoc />
public async Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId)
public QueryResult<DeviceInfo> GetDevicesForUser(Guid? userId)
{
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
IEnumerable<Device> devices = _devices.Values
.OrderByDescending(d => d.DateLastActivity)
.ThenBy(d => d.DeviceId);
if (!userId.IsNullOrEmpty())
{
var sessions = dbContext.Devices
.Include(d => d.User)
.OrderByDescending(d => d.DateLastActivity)
.ThenBy(d => d.DeviceId)
.SelectMany(d => dbContext.DeviceOptions.Where(o => o.DeviceId == d.DeviceId).DefaultIfEmpty(), (d, o) => new { Device = d, Options = o })
.AsAsyncEnumerable();
if (!userId.IsNullOrEmpty())
var user = _userManager.GetUserById(userId.Value);
if (user is null)
{
var user = _userManager.GetUserById(userId.Value);
if (user is null)
{
throw new ResourceNotFoundException();
}
sessions = sessions.Where(i => CanAccessDevice(user, i.Device.DeviceId));
throw new ResourceNotFoundException();
}
var array = await sessions.Select(device => ToDeviceInfo(device.Device, device.Options)).ToArrayAsync().ConfigureAwait(false);
return new QueryResult<DeviceInfo>(array);
devices = devices.Where(i => CanAccessDevice(user, i.DeviceId));
}
var array = devices.Select(device =>
{
_deviceOptions.TryGetValue(device.DeviceId, out var option);
return ToDeviceInfo(device, option);
}).ToArray();
return new QueryResult<DeviceInfo>(array);
}
/// <inheritdoc />
public async Task DeleteDevice(Device device)
{
_devices.TryRemove(device.Id, out _);
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
@@ -207,6 +204,19 @@ namespace Jellyfin.Server.Implementations.Devices
}
}
/// <inheritdoc />
public async Task UpdateDevice(Device device)
{
var dbContext = await _dbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
dbContext.Devices.Update(device);
await dbContext.SaveChangesAsync().ConfigureAwait(false);
}
_devices[device.Id] = device;
}
/// <inheritdoc />
public bool CanAccessDevice(User user, string deviceId)
{
@@ -225,6 +235,11 @@ namespace Jellyfin.Server.Implementations.Devices
private DeviceInfo ToDeviceInfo(Device authInfo, DeviceOptions? options = null)
{
var caps = GetCapabilities(authInfo.DeviceId);
var user = _userManager.GetUserById(authInfo.UserId);
if (user is null)
{
throw new ResourceNotFoundException("User with UserId " + authInfo.UserId + " not found");
}
return new DeviceInfo
{
@@ -232,7 +247,7 @@ namespace Jellyfin.Server.Implementations.Devices
AppVersion = authInfo.AppVersion,
Id = authInfo.DeviceId,
LastUserId = authInfo.UserId,
LastUserName = authInfo.User.Username,
LastUserName = user.Username,
Name = authInfo.DeviceName,
DateLastActivity = authInfo.DateLastActivity,
IconUrl = caps.IconUrl,

View File

@@ -1,6 +1,5 @@
using System;
using System.IO;
using EFCoreSecondLevelCacheInterceptor;
using MediaBrowser.Common.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
@@ -16,28 +15,13 @@ public static class ServiceCollectionExtensions
/// Adds the <see cref="IDbContextFactory{TContext}"/> interface to the service collection with second level caching enabled.
/// </summary>
/// <param name="serviceCollection">An instance of the <see cref="IServiceCollection"/> interface.</param>
/// <param name="disableSecondLevelCache">Whether second level cache disabled..</param>
/// <returns>The updated service collection.</returns>
public static IServiceCollection AddJellyfinDbContext(this IServiceCollection serviceCollection, bool disableSecondLevelCache)
public static IServiceCollection AddJellyfinDbContext(this IServiceCollection serviceCollection)
{
if (!disableSecondLevelCache)
{
serviceCollection.AddEFSecondLevelCache(options =>
options.UseMemoryCacheProvider()
.CacheAllQueries(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(10))
.UseCacheKeyPrefix("EF_")
// Don't cache null values. Remove this optional setting if it's not necessary.
.SkipCachingResults(result => result.Value is null or EFTableRows { RowsCount: 0 }));
}
serviceCollection.AddPooledDbContextFactory<JellyfinDbContext>((serviceProvider, opt) =>
{
var applicationPaths = serviceProvider.GetRequiredService<IApplicationPaths>();
var dbOpt = opt.UseSqlite($"Filename={Path.Combine(applicationPaths.DataPath, "jellyfin.db")}");
if (!disableSecondLevelCache)
{
dbOpt.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>());
}
opt.UseSqlite($"Filename={Path.Combine(applicationPaths.DataPath, "jellyfin.db")}");
});
return serviceCollection;

View File

@@ -27,7 +27,6 @@
<ItemGroup>
<PackageReference Include="AsyncKeyedLock" />
<PackageReference Include="EFCoreSecondLevelCacheInterceptor" />
<PackageReference Include="System.Linq.Async" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />

View File

@@ -4,7 +4,10 @@ using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Jellyfin.Data.Queries;
using Jellyfin.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using Microsoft.AspNetCore.Http;
@@ -17,15 +20,18 @@ namespace Jellyfin.Server.Implementations.Security
{
private readonly IDbContextFactory<JellyfinDbContext> _jellyfinDbProvider;
private readonly IUserManager _userManager;
private readonly IDeviceManager _deviceManager;
private readonly IServerApplicationHost _serverApplicationHost;
public AuthorizationContext(
IDbContextFactory<JellyfinDbContext> jellyfinDb,
IUserManager userManager,
IDeviceManager deviceManager,
IServerApplicationHost serverApplicationHost)
{
_jellyfinDbProvider = jellyfinDb;
_userManager = userManager;
_deviceManager = deviceManager;
_serverApplicationHost = serverApplicationHost;
}
@@ -121,7 +127,11 @@ namespace Jellyfin.Server.Implementations.Security
var dbContext = await _jellyfinDbProvider.CreateDbContextAsync().ConfigureAwait(false);
await using (dbContext.ConfigureAwait(false))
{
var device = await dbContext.Devices.FirstOrDefaultAsync(d => d.AccessToken == token).ConfigureAwait(false);
var device = _deviceManager.GetDevices(
new DeviceQuery
{
AccessToken = token
}).Items.FirstOrDefault();
if (device is not null)
{
@@ -178,8 +188,7 @@ namespace Jellyfin.Server.Implementations.Security
if (updateToken)
{
dbContext.Devices.Update(device);
await dbContext.SaveChangesAsync().ConfigureAwait(false);
await _deviceManager.UpdateDevice(device).ConfigureAwait(false);
}
}
else

View File

@@ -60,10 +60,10 @@ public sealed class DeviceAccessHost : IHostedService
private async Task UpdateDeviceAccess(User user)
{
var existing = (await _deviceManager.GetDevices(new DeviceQuery
var existing = _deviceManager.GetDevices(new DeviceQuery
{
UserId = user.Id
}).ConfigureAwait(false)).Items;
}).Items;
foreach (var device in existing)
{

View File

@@ -85,6 +85,6 @@ public static class WebHostBuilderExtensions
logger.LogInformation("Kestrel listening to unix socket {SocketPath}", socketPath);
}
})
.UseStartup(_ => new Startup(appHost, startupConfig));
.UseStartup(_ => new Startup(appHost));
}
}

View File

@@ -40,18 +40,15 @@ namespace Jellyfin.Server
{
private readonly CoreAppHost _serverApplicationHost;
private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IConfiguration _startupConfig;
/// <summary>
/// Initializes a new instance of the <see cref="Startup" /> class.
/// </summary>
/// <param name="appHost">The server application host.</param>
/// <param name="startupConfig">The server startupConfig.</param>
public Startup(CoreAppHost appHost, IConfiguration startupConfig)
public Startup(CoreAppHost appHost)
{
_serverApplicationHost = appHost;
_serverConfigurationManager = appHost.ConfigurationManager;
_startupConfig = startupConfig;
}
/// <summary>
@@ -70,7 +67,7 @@ namespace Jellyfin.Server
// TODO remove once this is fixed upstream https://github.com/dotnet/aspnetcore/issues/34371
services.AddSingleton<IActionResultExecutor<PhysicalFileResult>, SymlinkFollowingPhysicalFileResultExecutor>();
services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.GetNetworkConfiguration());
services.AddJellyfinDbContext(_startupConfig.GetSqliteSecondLevelCacheDisabled());
services.AddJellyfinDbContext();
services.AddJellyfinApiSwagger();
// configure custom legacy authentication

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Common</PackageId>
<VersionPrefix>10.9.7</VersionPrefix>
<VersionPrefix>10.9.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -44,26 +44,28 @@ namespace MediaBrowser.Controller.Devices
/// </summary>
/// <param name="id">The identifier.</param>
/// <returns>DeviceInfo.</returns>
Task<DeviceInfo> GetDevice(string id);
DeviceInfo GetDevice(string id);
/// <summary>
/// Gets devices based on the provided query.
/// </summary>
/// <param name="query">The device query.</param>
/// <returns>A <see cref="Task{QueryResult}"/> representing the retrieval of the devices.</returns>
Task<QueryResult<Device>> GetDevices(DeviceQuery query);
QueryResult<Device> GetDevices(DeviceQuery query);
Task<QueryResult<DeviceInfo>> GetDeviceInfos(DeviceQuery query);
QueryResult<DeviceInfo> GetDeviceInfos(DeviceQuery query);
/// <summary>
/// Gets the devices.
/// </summary>
/// <param name="userId">The user's id, or <c>null</c>.</param>
/// <returns>IEnumerable&lt;DeviceInfo&gt;.</returns>
Task<QueryResult<DeviceInfo>> GetDevicesForUser(Guid? userId);
QueryResult<DeviceInfo> GetDevicesForUser(Guid? userId);
Task DeleteDevice(Device device);
Task UpdateDevice(Device device);
/// <summary>
/// Determines whether this instance [can access device] the specified user identifier.
/// </summary>
@@ -74,6 +76,6 @@ namespace MediaBrowser.Controller.Devices
Task UpdateDeviceOptions(string deviceId, string deviceName);
Task<DeviceOptions> GetDeviceOptions(string deviceId);
DeviceOptions GetDeviceOptions(string deviceId);
}
}

View File

@@ -350,10 +350,17 @@ namespace MediaBrowser.Controller.Entities.TV
public List<BaseItem> GetSeasonEpisodes(Season parentSeason, User user, DtoOptions options, bool shouldIncludeMissingEpisodes)
{
var queryFromSeries = ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons;
// add optimization when this setting is not enabled
var seriesKey = queryFromSeries ?
GetUniqueSeriesKey(this) :
GetUniqueSeriesKey(parentSeason);
var query = new InternalItemsQuery(user)
{
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = GetUniqueSeriesKey(this),
AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey,
SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null,
IncludeItemTypes = new[] { BaseItemKind.Episode },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
DtoOptions = options

View File

@@ -64,11 +64,6 @@ namespace MediaBrowser.Controller.Extensions
/// </summary>
public const string SqliteCacheSizeKey = "sqlite:cacheSize";
/// <summary>
/// Disable second level cache of sqlite.
/// </summary>
public const string SqliteDisableSecondLevelCacheKey = "sqlite:disableSecondLevelCache";
/// <summary>
/// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
/// </summary>
@@ -133,15 +128,5 @@ namespace MediaBrowser.Controller.Extensions
/// <returns>The sqlite cache size.</returns>
public static int? GetSqliteCacheSize(this IConfiguration configuration)
=> configuration.GetValue<int?>(SqliteCacheSizeKey);
/// <summary>
/// Gets whether second level cache disabled from the <see cref="IConfiguration" />.
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>Whether second level cache disabled.</returns>
public static bool GetSqliteSecondLevelCacheDisabled(this IConfiguration configuration)
{
return configuration.GetValue<bool>(SqliteDisableSecondLevelCacheKey);
}
}
}

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Controller</PackageId>
<VersionPrefix>10.9.7</VersionPrefix>
<VersionPrefix>10.9.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -477,7 +477,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false;
}
public bool CheckSupportedRuntimeKey(string keyDesc)
private readonly Version _minFFmpegMultiThreadedCli = new Version(7, 0);
public bool CheckSupportedRuntimeKey(string keyDesc, Version? ffmpegVersion)
{
if (string.IsNullOrEmpty(keyDesc))
{
@@ -487,7 +489,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
output = GetProcessOutput(_encoderPath, "-hide_banner -f lavfi -i nullsrc=s=1x1:d=500 -f null -", true, "?");
// With multi-threaded cli support, FFmpeg 7 is less sensitive to keyboard input
var duration = ffmpegVersion >= _minFFmpegMultiThreadedCli ? 10000 : 1000;
output = GetProcessOutput(_encoderPath, $"-hide_banner -f lavfi -i nullsrc=s=1x1:d={duration} -f null -", true, "?");
}
catch (Exception ex)
{

View File

@@ -193,7 +193,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
_threads = EncodingHelper.GetNumberOfThreads(null, options, null);
_isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding");
_isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding", _ffmpegVersion);
// Check the Vaapi device vendor
if (OperatingSystem.IsLinux()
@@ -1216,7 +1216,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
var duration = TimeSpan.FromTicks(mediaInfoResult.RunTimeTicks.Value).TotalSeconds;
// Add file path stanza to concat configuration
sw.WriteLine("file '{0}'", path);
sw.WriteLine("file '{0}'", path.Replace("'", @"'\''", StringComparison.Ordinal));
// Add duration stanza to concat configuration
sw.WriteLine("duration {0}", duration);

View File

@@ -721,6 +721,8 @@ namespace MediaBrowser.MediaEncoding.Probing
if (streamInfo.CodecType == CodecType.Audio)
{
stream.Type = MediaStreamType.Audio;
stream.LocalizedDefault = _localization.GetLocalizedString("Default");
stream.LocalizedExternal = _localization.GetLocalizedString("External");
stream.Channels = streamInfo.Channels;

View File

@@ -51,6 +51,8 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
o.PoolInitialFill = 1;
});
private readonly Version _maxFFmpegCkeyPauseSupported = new Version(6, 1);
/// <summary>
/// Initializes a new instance of the <see cref="TranscodeManager"/> class.
/// </summary>
@@ -559,7 +561,9 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
{
if (EnableThrottling(state))
if (EnableThrottling(state)
&& (_mediaEncoder.IsPkeyPauseSupported
|| _mediaEncoder.EncoderVersion <= _maxFFmpegCkeyPauseSupported))
{
transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, _loggerFactory.CreateLogger<TranscodingThrottler>(), _serverConfigurationManager, _fileSystem, _mediaEncoder);
transcodingJob.TranscodingThrottler.Start();

View File

@@ -8,7 +8,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Model</PackageId>
<VersionPrefix>10.9.7</VersionPrefix>
<VersionPrefix>10.9.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -61,8 +61,8 @@ namespace MediaBrowser.Providers.TV
await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
RemoveObsoleteEpisodes(item);
await CreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
RemoveObsoleteSeasons(item);
await CreateSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />

View File

@@ -1,4 +1,4 @@
using System.Reflection;
[assembly: AssemblyVersion("10.9.7")]
[assembly: AssemblyFileVersion("10.9.7")]
[assembly: AssemblyVersion("10.9.9")]
[assembly: AssemblyFileVersion("10.9.9")]

View File

@@ -15,7 +15,7 @@
<PropertyGroup>
<Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Extensions</PackageId>
<VersionPrefix>10.9.7</VersionPrefix>
<VersionPrefix>10.9.9</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>

View File

@@ -17,6 +17,7 @@ namespace Jellyfin.MediaEncoding.Tests
}
[Theory]
[InlineData(EncoderValidatorTestsData.FFmpegV701Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV611Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV60Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV512Output, true)]
@@ -33,6 +34,7 @@ namespace Jellyfin.MediaEncoding.Tests
{
public GetFFmpegVersionTestData()
{
Add(EncoderValidatorTestsData.FFmpegV701Output, new Version(7, 0, 1));
Add(EncoderValidatorTestsData.FFmpegV611Output, new Version(6, 1, 1));
Add(EncoderValidatorTestsData.FFmpegV60Output, new Version(6, 0));
Add(EncoderValidatorTestsData.FFmpegV512Output, new Version(5, 1, 2));

View File

@@ -2,6 +2,18 @@ namespace Jellyfin.MediaEncoding.Tests
{
internal static class EncoderValidatorTestsData
{
public const string FFmpegV701Output = @"ffmpeg version 7.0.1-Jellyfin Copyright (c) 2000-2024 the FFmpeg developers
built with clang version 18.1.8
configuration: --cc=clang --pkg-config-flags=--static --extra-cflags=-I/clang64/ffbuild/include --extra-ldflags=-L/clang64/ffbuild/lib --prefix=/clang64/ffbuild/jellyfin-ffmpeg --extra-version=Jellyfin --disable-ffplay --disable-debug --disable-doc --disable-sdl2 --disable-ptx-compression --enable-lto=thin --enable-gpl --enable-version3 --enable-schannel --enable-iconv --enable-libxml2 --enable-zlib --enable-lzma --enable-gmp --enable-chromaprint --enable-libfreetype --enable-libfribidi --enable-libfontconfig --enable-libharfbuzz --enable-libass --enable-libbluray --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libopenmpt --enable-libwebp --enable-libvpx --enable-libzimg --enable-libx264 --enable-libx265 --enable-libsvtav1 --enable-libdav1d --enable-libfdk-aac --enable-opencl --enable-dxva2 --enable-d3d11va --enable-amf --enable-libvpl --enable-ffnvcodec --enable-cuda --enable-cuda-llvm --enable-cuvid --enable-nvdec --enable-nvenc
libavutil 59. 8.100 / 59. 8.100
libavcodec 61. 3.100 / 61. 3.100
libavformat 61. 1.100 / 61. 1.100
libavdevice 61. 1.100 / 61. 1.100
libavfilter 10. 1.100 / 10. 1.100
libswscale 8. 1.100 / 8. 1.100
libswresample 5. 1.100 / 5. 1.100
libpostproc 58. 1.100 / 58. 1.100";
public const string FFmpegV611Output = @"ffmpeg version n6.1.1-16-g33efa50fa4-20240317 Copyright (c) 2000-2023 the FFmpeg developers
built with gcc 13.2.0 (crosstool-NG 1.26.0.65_ecc5e41)
configuration: --prefix=/ffbuild/prefix --pkg-config-flags=--static --pkg-config=pkg-config --cross-prefix=x86_64-w64-mingw32- --arch=x86_64 --target-os=mingw32 --enable-gpl --enable-version3 --disable-debug --enable-shared --disable-static --disable-w32threads --enable-pthreads --enable-iconv --enable-libxml2 --enable-zlib --enable-libfreetype --enable-libfribidi --enable-gmp --enable-lzma --enable-fontconfig --enable-libharfbuzz --enable-libvorbis --enable-opencl --disable-libpulse --enable-libvmaf --disable-libxcb --disable-xlib --enable-amf --enable-libaom --enable-libaribb24 --enable-avisynth --enable-chromaprint --enable-libdav1d --enable-libdavs2 --disable-libfdk-aac --enable-ffnvcodec --enable-cuda-llvm --enable-frei0r --enable-libgme --enable-libkvazaar --enable-libaribcaption --enable-libass --enable-libbluray --enable-libjxl --enable-libmp3lame --enable-libopus --enable-librist --enable-libssh --enable-libtheora --enable-libvpx --enable-libwebp --enable-lv2 --enable-libvpl --enable-openal --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopenmpt --enable-librav1e --enable-librubberband --enable-schannel --enable-sdl2 --enable-libsoxr --enable-libsrt --enable-libsvtav1 --enable-libtwolame --enable-libuavs3d --disable-libdrm --enable-vaapi --enable-libvidstab --enable-vulkan --enable-libshaderc --enable-libplacebo --enable-libx264 --enable-libx265 --enable-libxavs2 --enable-libxvid --enable-libzimg --enable-libzvbi --extra-cflags='$FF_CFLAGS' --extra-cxxflags='$FF_CXXFLAGS' --extra-ldflags='$FF_LDFLAGS' --extra-ldexeflags='$FF_LDEXEFLAGS' --extra-libs='$FF_LIBS' --extra-version=20240317

View File

@@ -18,7 +18,7 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
public class ProbeResultNormalizerTests
{
private readonly JsonSerializerOptions _jsonOptions;
private readonly ProbeResultNormalizer _probeResultNormalizer = new ProbeResultNormalizer(new NullLogger<EncoderValidatorTests>(), null);
private readonly ProbeResultNormalizer _probeResultNormalizer = new ProbeResultNormalizer(new NullLogger<EncoderValidatorTests>(), new Mock<ILocalizationManager>().Object);
public ProbeResultNormalizerTests()
{