mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-15 06:42:18 +01:00
Compare commits
27 Commits
renovate/m
...
renovate/m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9c7b18fdd | ||
|
|
5bad7b8ae3 | ||
|
|
fb33b725e0 | ||
|
|
ce3fa80a28 | ||
|
|
ec9c94bd7a | ||
|
|
bb265cd403 | ||
|
|
22644075e7 | ||
|
|
6fc406f2c5 | ||
|
|
046023b9dd | ||
|
|
193a15ea45 | ||
|
|
45700f6f7d | ||
|
|
99f2129d91 | ||
|
|
29d11f6ecb | ||
|
|
22f0507258 | ||
|
|
c300651d0d | ||
|
|
eacdc83fda | ||
|
|
e3b5cf4996 | ||
|
|
553f38a237 | ||
|
|
68f26e5a34 | ||
|
|
fec78c8448 | ||
|
|
c22933260b | ||
|
|
965b602c68 | ||
|
|
8142bbd50e | ||
|
|
418beafebb | ||
|
|
434ebc8b11 | ||
|
|
300036c859 | ||
|
|
37983c943a |
4
.github/workflows/ci-compat.yml
vendored
4
.github/workflows/ci-compat.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
dotnet build Jellyfin.Server -o ./out
|
||||
|
||||
- name: Upload Head
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: abi-head
|
||||
retention-days: 14
|
||||
@@ -65,7 +65,7 @@ jobs:
|
||||
dotnet build Jellyfin.Server -o ./out
|
||||
|
||||
- name: Upload Head
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: abi-base
|
||||
retention-days: 14
|
||||
|
||||
2
.github/workflows/openapi-generate.yml
vendored
2
.github/workflows/openapi-generate.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter Jellyfin.Server.Integration.Tests.OpenApiSpecTests
|
||||
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: ${{ inputs.artifact }}
|
||||
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json
|
||||
|
||||
2
.github/workflows/openapi-pull-request.yml
vendored
2
.github/workflows/openapi-pull-request.yml
vendored
@@ -74,7 +74,7 @@ jobs:
|
||||
|
||||
docker run -v /tmp/openapi-report:/data openapitools/openapi-diff:2.1.6 /data/base.json /data/head.json --state -l ERROR --markdown /data/openapi-report.md
|
||||
- name: Upload Artifact
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: openapi-report
|
||||
path: /tmp/openapi-report/openapi-report.md
|
||||
|
||||
@@ -26,28 +26,28 @@
|
||||
<PackageVersion Include="libse" Version="4.0.12" />
|
||||
<PackageVersion Include="LrcParser" Version="2025.623.0" />
|
||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="8.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="5.3.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.5" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.6" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
|
||||
<PackageVersion Include="MimeTypes" Version="2.5.2" />
|
||||
<PackageVersion Include="Morestachio" Version="5.0.1.631" />
|
||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||
@@ -77,7 +77,7 @@
|
||||
<PackageVersion Include="Svg.Skia" Version="3.4.1" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="10.1.7" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.5" />
|
||||
<PackageVersion Include="System.Text.Json" Version="10.0.6" />
|
||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageVersion Include="z440.atl.core" Version="7.12.0" />
|
||||
<PackageVersion Include="TMDbLib" Version="3.0.0" />
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace Emby.Server.Implementations.IO
|
||||
_fileSystem = fileSystem;
|
||||
|
||||
appLifetime.ApplicationStarted.Register(Start);
|
||||
appLifetime.ApplicationStopping.Register(Stop);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -50,6 +50,10 @@ public class ArtistsValidator
|
||||
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var names = _itemRepo.GetAllArtistNames();
|
||||
var existingArtistIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = [BaseItemKind.MusicArtist]
|
||||
}).ToHashSet();
|
||||
|
||||
var numComplete = 0;
|
||||
var count = names.Count;
|
||||
@@ -59,8 +63,13 @@ public class ArtistsValidator
|
||||
try
|
||||
{
|
||||
var item = _libraryManager.GetArtist(name);
|
||||
var isNew = !existingArtistIds.Contains(item.Id);
|
||||
var neverRefreshed = item.DateLastRefreshed == default;
|
||||
|
||||
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||
if (isNew || neverRefreshed)
|
||||
{
|
||||
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
"Photos": "Fotos",
|
||||
"Playlists": "Llistes de reproducció",
|
||||
"Plugin": "Complement",
|
||||
"PluginInstalledWithName": "{0} s'ha instal·lat",
|
||||
"PluginUninstalledWithName": "{0} s'ha desinstal·lat",
|
||||
"PluginInstalledWithName": "S'ha instal·lat {0}",
|
||||
"PluginUninstalledWithName": "S'ha desinstal·lat {0}",
|
||||
"PluginUpdatedWithName": "S'ha actualitzat {0}",
|
||||
"ProviderValue": "Proveïdor: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} ha fallat",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"Inherit": "繼承",
|
||||
"ItemAddedWithName": "{0} 經已加咗入媒體櫃",
|
||||
"ItemRemovedWithName": "{0} 經已由媒體櫃移除咗",
|
||||
"LabelIpAddressValue": "IP 地址:{0}",
|
||||
"LabelIpAddressValue": "IP 位址:{0}",
|
||||
"LabelRunningTimeValue": "運行時間:{0}",
|
||||
"Latest": "最新",
|
||||
"MessageApplicationUpdated": "Jellyfin 經已更新咗",
|
||||
|
||||
@@ -138,7 +138,7 @@ namespace Emby.Server.Implementations.Localization
|
||||
string twoCharName = parts[2];
|
||||
if (string.IsNullOrWhiteSpace(twoCharName))
|
||||
{
|
||||
continue;
|
||||
twoCharName = string.Empty;
|
||||
}
|
||||
else if (twoCharName.Contains('-', StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
||||
@@ -50,6 +50,9 @@ public class PersonsController : BaseJellyfinApiController
|
||||
/// <param name="startIndex">Optional. All items with a lower index will be dropped from the response.</param>
|
||||
/// <param name="limit">Optional. The maximum number of records to return.</param>
|
||||
/// <param name="searchTerm">The search term.</param>
|
||||
/// <param name="nameStartsWith">Optional. Filter by items whose name starts with the given input string.</param>
|
||||
/// <param name="nameLessThan">Optional. Filter by items whose name will appear before this value when sorted alphabetically.</param>
|
||||
/// <param name="nameStartsWithOrGreater">Optional. Filter by items whose name will appear after this value when sorted alphabetically.</param>
|
||||
/// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
|
||||
/// <param name="filters">Optional. Specify additional filters to apply.</param>
|
||||
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not. userId is required.</param>
|
||||
@@ -70,6 +73,9 @@ public class PersonsController : BaseJellyfinApiController
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery] string? nameStartsWith,
|
||||
[FromQuery] string? nameLessThan,
|
||||
[FromQuery] string? nameStartsWithOrGreater,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
@@ -97,6 +103,9 @@ public class PersonsController : BaseJellyfinApiController
|
||||
excludePersonTypes)
|
||||
{
|
||||
NameContains = searchTerm,
|
||||
NameStartsWith = nameStartsWith,
|
||||
NameLessThan = nameLessThan,
|
||||
NameStartsWithOrGreater = nameStartsWithOrGreater,
|
||||
User = user,
|
||||
IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite,
|
||||
AppearsInItemId = appearsInItemId ?? Guid.Empty,
|
||||
|
||||
@@ -235,6 +235,21 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
query = query.Where(e => e.Name.ToUpper().Contains(nameContainsUpper));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
|
||||
{
|
||||
query = query.Where(e => e.Name.StartsWith(filter.NameStartsWith.ToLowerInvariant()));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.NameLessThan))
|
||||
{
|
||||
query = query.Where(e => e.Name.CompareTo(filter.NameLessThan.ToLowerInvariant()) < 0);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
|
||||
{
|
||||
query = query.Where(e => e.Name.CompareTo(filter.NameStartsWithOrGreater.ToLowerInvariant()) >= 0);
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,22 +28,44 @@ public static class StorageHelper
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the free space of a specific directory.
|
||||
/// Gets the free space of the parent filesystem of a specific directory.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to a folder.</param>
|
||||
/// <returns>The number of bytes available space.</returns>
|
||||
/// <returns>Various details about the parent filesystem containing the directory.</returns>
|
||||
public static FolderStorageInfo GetFreeSpaceOf(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var driveInfo = new DriveInfo(path);
|
||||
// Fully resolve the given path to an actual filesystem target, in case it's a symlink or similar.
|
||||
var resolvedPath = ResolvePath(path);
|
||||
// We iterate all filesystems reported by GetDrives() here, and attempt to find the best
|
||||
// match that contains, as deep as possible, the given path.
|
||||
// This is required because simply calling `DriveInfo` on a path returns that path as
|
||||
// the Name and RootDevice, which is not at all how this should work.
|
||||
var allDrives = DriveInfo.GetDrives();
|
||||
DriveInfo? bestMatch = null;
|
||||
foreach (DriveInfo d in allDrives)
|
||||
{
|
||||
if (resolvedPath.StartsWith(d.RootDirectory.FullName, StringComparison.InvariantCultureIgnoreCase) &&
|
||||
(bestMatch is null || d.RootDirectory.FullName.Length > bestMatch.RootDirectory.FullName.Length))
|
||||
{
|
||||
bestMatch = d;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch is null)
|
||||
{
|
||||
throw new InvalidOperationException($"The path `{path}` has no matching parent device. Space check invalid.");
|
||||
}
|
||||
|
||||
return new FolderStorageInfo()
|
||||
{
|
||||
Path = path,
|
||||
FreeSpace = driveInfo.AvailableFreeSpace,
|
||||
UsedSpace = driveInfo.TotalSize - driveInfo.AvailableFreeSpace,
|
||||
StorageType = driveInfo.DriveType.ToString(),
|
||||
DeviceId = driveInfo.Name,
|
||||
ResolvedPath = resolvedPath,
|
||||
FreeSpace = bestMatch.AvailableFreeSpace,
|
||||
UsedSpace = bestMatch.TotalSize - bestMatch.AvailableFreeSpace,
|
||||
StorageType = bestMatch.DriveType.ToString(),
|
||||
DeviceId = bestMatch.Name,
|
||||
};
|
||||
}
|
||||
catch
|
||||
@@ -51,6 +73,7 @@ public static class StorageHelper
|
||||
return new FolderStorageInfo()
|
||||
{
|
||||
Path = path,
|
||||
ResolvedPath = path,
|
||||
FreeSpace = -1,
|
||||
UsedSpace = -1,
|
||||
StorageType = null,
|
||||
@@ -59,6 +82,26 @@ public static class StorageHelper
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walk a path and fully resolve any symlinks within it.
|
||||
/// </summary>
|
||||
private static string ResolvePath(string path)
|
||||
{
|
||||
var parts = path.Split(Path.DirectorySeparatorChar, StringSplitOptions.RemoveEmptyEntries);
|
||||
var current = Path.DirectorySeparatorChar.ToString();
|
||||
foreach (var part in parts)
|
||||
{
|
||||
current = Path.Combine(current, part);
|
||||
var resolved = new DirectoryInfo(current).ResolveLinkTarget(returnFinalTarget: true);
|
||||
if (resolved is not null)
|
||||
{
|
||||
current = resolved.FullName;
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying drive data from a given path and checks if the available storage capacity matches the threshold.
|
||||
/// </summary>
|
||||
|
||||
@@ -161,7 +161,6 @@ namespace Jellyfin.Server
|
||||
_loggerFactory,
|
||||
options,
|
||||
startupConfig);
|
||||
_appHost = appHost;
|
||||
var configurationCompleted = false;
|
||||
try
|
||||
{
|
||||
@@ -207,6 +206,7 @@ namespace Jellyfin.Server
|
||||
await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.CoreInitialisation, appHost.ServiceProvider).ConfigureAwait(false);
|
||||
|
||||
await appHost.InitializeServices(startupConfig).ConfigureAwait(false);
|
||||
_appHost = appHost;
|
||||
|
||||
await jellyfinMigrationService.MigrateStepAsync(JellyfinMigrationStageTypes.AppInitialisation, appHost.ServiceProvider).ConfigureAwait(false);
|
||||
await jellyfinMigrationService.CleanupSystemAfterMigration(_logger).ConfigureAwait(false);
|
||||
@@ -263,6 +263,7 @@ namespace Jellyfin.Server
|
||||
|
||||
_appHost = null;
|
||||
_jellyfinHost?.Dispose();
|
||||
_jellyfinHost = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,7 @@ public sealed class SetupServer : IDisposable
|
||||
ThrowIfDisposed();
|
||||
var retryAfterValue = TimeSpan.FromSeconds(5);
|
||||
var config = _configurationManager.GetNetworkConfiguration()!;
|
||||
_startupServer?.Dispose();
|
||||
_startupServer = Host.CreateDefaultBuilder(["hostBuilder:reloadConfigOnChange=false"])
|
||||
.UseConsoleLifetime()
|
||||
.UseSerilog()
|
||||
|
||||
@@ -42,6 +42,12 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public string NameContains { get; set; }
|
||||
|
||||
public string NameStartsWith { get; set; }
|
||||
|
||||
public string NameLessThan { get; set; }
|
||||
|
||||
public string NameStartsWithOrGreater { get; set; }
|
||||
|
||||
public User User { get; set; }
|
||||
|
||||
public bool? IsFavorite { get; set; }
|
||||
|
||||
@@ -1331,8 +1331,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
public bool CanExtractSubtitles(string codec)
|
||||
{
|
||||
// TODO is there ever a case when a subtitle can't be extracted??
|
||||
return true;
|
||||
return _configurationManager.GetEncodingOptions().EnableSubtitleExtraction;
|
||||
}
|
||||
|
||||
private sealed class ProcessWrapper : IDisposable
|
||||
|
||||
@@ -729,6 +729,9 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
stream.Type = MediaStreamType.Audio;
|
||||
stream.LocalizedDefault = _localization.GetLocalizedString("Default");
|
||||
stream.LocalizedExternal = _localization.GetLocalizedString("External");
|
||||
stream.LocalizedLanguage = string.IsNullOrEmpty(stream.Language)
|
||||
? null
|
||||
: _localization.FindLanguageInfo(stream.Language)?.DisplayName;
|
||||
|
||||
stream.Channels = streamInfo.Channels;
|
||||
|
||||
@@ -767,6 +770,9 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
stream.LocalizedForced = _localization.GetLocalizedString("Forced");
|
||||
stream.LocalizedExternal = _localization.GetLocalizedString("External");
|
||||
stream.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
|
||||
stream.LocalizedLanguage = string.IsNullOrEmpty(stream.Language)
|
||||
? null
|
||||
: _localization.FindLanguageInfo(stream.Language)?.DisplayName;
|
||||
|
||||
if (string.IsNullOrEmpty(stream.Title))
|
||||
{
|
||||
|
||||
@@ -1555,7 +1555,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!subtitleStream.IsExternal && !transcoderSupport.CanExtractSubtitles(subtitleStream.Codec))
|
||||
if (!subtitleStream.IsExternal && playMethod == PlayMethod.Transcode && !transcoderSupport.CanExtractSubtitles(subtitleStream.Codec))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
||||
@@ -11,17 +11,22 @@ public record FolderStorageInfo
|
||||
public required string Path { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the free space of the underlying storage device of the <see cref="Path"/>.
|
||||
/// Gets the fully resolved path of the folder in question (interpolating any symlinks if present).
|
||||
/// </summary>
|
||||
public required string ResolvedPath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the free space of the underlying storage device of the <see cref="ResolvedPath"/>.
|
||||
/// </summary>
|
||||
public long FreeSpace { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the used space of the underlying storage device of the <see cref="Path"/>.
|
||||
/// Gets the used space of the underlying storage device of the <see cref="ResolvedPath"/>.
|
||||
/// </summary>
|
||||
public long UsedSpace { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the kind of storage device of the <see cref="Path"/>.
|
||||
/// Gets the kind of storage device of the <see cref="ResolvedPath"/>.
|
||||
/// </summary>
|
||||
public string? StorageType { get; init; }
|
||||
|
||||
|
||||
@@ -487,6 +487,13 @@ namespace MediaBrowser.Providers.Manager
|
||||
return true;
|
||||
}
|
||||
|
||||
// Artists without a folder structure that are derived from metadata have no real path in the library,
|
||||
// so GetLibraryOptions returns null. Allow all providers through rather than blocking them.
|
||||
if (item is MusicArtist && libraryTypeOptions is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _baseItemManager.IsMetadataFetcherEnabled(item, libraryTypeOptions, provider.Name);
|
||||
}
|
||||
|
||||
|
||||
@@ -366,6 +366,8 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
blurayVideoStream.ColorSpace = ffmpegVideoStream.ColorSpace;
|
||||
blurayVideoStream.ColorTransfer = ffmpegVideoStream.ColorTransfer;
|
||||
blurayVideoStream.ColorPrimaries = ffmpegVideoStream.ColorPrimaries;
|
||||
blurayVideoStream.BitDepth = ffmpegVideoStream.BitDepth;
|
||||
blurayVideoStream.PixelFormat = ffmpegVideoStream.PixelFormat;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,9 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
|
||||
if (string.IsNullOrWhiteSpace(overview))
|
||||
{
|
||||
overview = result.strBiographyEN;
|
||||
overview = string.IsNullOrWhiteSpace(result.strBiographyEN)
|
||||
? result.strBiography
|
||||
: result.strBiographyEN;
|
||||
}
|
||||
|
||||
item.Overview = (overview ?? string.Empty).StripHtml();
|
||||
@@ -224,6 +226,8 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
|
||||
public string strTwitter { get; set; }
|
||||
|
||||
public string strBiography { get; set; }
|
||||
|
||||
public string strBiographyEN { get; set; }
|
||||
|
||||
public string strBiographyDE { get; set; }
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||
/// <summary>
|
||||
/// MusicBrainz artist provider.
|
||||
/// </summary>
|
||||
public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable
|
||||
public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable, IHasOrder
|
||||
{
|
||||
private readonly ILogger<MusicBrainzArtistProvider> _logger;
|
||||
private Query _musicBrainzQuery;
|
||||
@@ -42,6 +42,10 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, Ar
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
/// Runs first to populate the MusicBrainz artist ID used by downstream providers.
|
||||
public int Order => 0;
|
||||
|
||||
private void ReloadConfig(object? sender, BasePluginConfiguration e)
|
||||
{
|
||||
var configuration = (PluginConfiguration)e;
|
||||
|
||||
@@ -617,5 +617,60 @@ namespace Jellyfin.Model.Tests
|
||||
|
||||
return (path, query, filename, extension);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
// EnableSubtitleExtraction = false, internal subtitles
|
||||
[InlineData("srt", "srt", false, false, PlayMethod.Transcode, SubtitleDeliveryMethod.Encode)]
|
||||
[InlineData("srt", "srt", false, false, PlayMethod.DirectPlay, SubtitleDeliveryMethod.External)]
|
||||
[InlineData("pgssub", "pgssub", false, false, PlayMethod.Transcode, SubtitleDeliveryMethod.Encode)]
|
||||
[InlineData("pgssub", "pgssub", false, false, PlayMethod.DirectPlay, SubtitleDeliveryMethod.External)]
|
||||
[InlineData("pgssub", "srt", false, false, PlayMethod.Transcode, SubtitleDeliveryMethod.Encode)]
|
||||
// EnableSubtitleExtraction = false, external subtitles
|
||||
[InlineData("srt", "srt", false, true, PlayMethod.Transcode, SubtitleDeliveryMethod.External)]
|
||||
// EnableSubtitleExtraction = true, internal subtitles
|
||||
[InlineData("srt", "srt", true, false, PlayMethod.Transcode, SubtitleDeliveryMethod.External)]
|
||||
[InlineData("pgssub", "pgssub", true, false, PlayMethod.Transcode, SubtitleDeliveryMethod.External)]
|
||||
[InlineData("pgssub", "pgssub", true, false, PlayMethod.DirectPlay, SubtitleDeliveryMethod.External)]
|
||||
[InlineData("pgssub", "srt", true, false, PlayMethod.Transcode, SubtitleDeliveryMethod.Encode)]
|
||||
// EnableSubtitleExtraction = true, external subtitles
|
||||
[InlineData("srt", "srt", true, true, PlayMethod.Transcode, SubtitleDeliveryMethod.External)]
|
||||
public void GetSubtitleProfile_RespectsExtractionSetting(
|
||||
string codec,
|
||||
string profileFormat,
|
||||
bool enableSubtitleExtraction,
|
||||
bool isExternal,
|
||||
PlayMethod playMethod,
|
||||
SubtitleDeliveryMethod expectedMethod)
|
||||
{
|
||||
var mediaSource = new MediaSourceInfo();
|
||||
var subtitleStream = new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Subtitle,
|
||||
Index = 0,
|
||||
IsExternal = isExternal,
|
||||
Path = isExternal ? "/media/sub." + codec : null,
|
||||
Codec = codec,
|
||||
SupportsExternalStream = MediaStream.IsTextFormat(codec)
|
||||
};
|
||||
|
||||
var subtitleProfiles = new[]
|
||||
{
|
||||
new SubtitleProfile { Format = profileFormat, Method = SubtitleDeliveryMethod.External }
|
||||
};
|
||||
|
||||
var transcoderSupport = new Mock<ITranscoderSupport>();
|
||||
transcoderSupport.Setup(t => t.CanExtractSubtitles(It.IsAny<string>())).Returns(enableSubtitleExtraction);
|
||||
|
||||
var result = StreamBuilder.GetSubtitleProfile(
|
||||
mediaSource,
|
||||
subtitleStream,
|
||||
subtitleProfiles,
|
||||
playMethod,
|
||||
transcoderSupport.Object,
|
||||
null,
|
||||
null);
|
||||
|
||||
Assert.Equal(expectedMethod, result.Method);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
|
||||
await localizationManager.LoadAll();
|
||||
var cultures = localizationManager.GetCultures().ToList();
|
||||
|
||||
Assert.Equal(194, cultures.Count);
|
||||
Assert.Equal(496, cultures.Count);
|
||||
|
||||
var germany = cultures.FirstOrDefault(x => x.TwoLetterISOLanguageName.Equals("de", StringComparison.Ordinal));
|
||||
Assert.NotNull(germany);
|
||||
@@ -99,6 +99,25 @@ namespace Jellyfin.Server.Implementations.Tests.Localization
|
||||
Assert.Contains("ger", germany.ThreeLetterISOLanguageNames);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("mul", "Multiple languages")]
|
||||
[InlineData("und", "Undetermined")]
|
||||
[InlineData("mis", "Uncoded languages")]
|
||||
[InlineData("zxx", "No linguistic content; Not applicable")]
|
||||
public async Task FindLanguageInfo_ISO6392Only_Success(string code, string expectedDisplayName)
|
||||
{
|
||||
var localizationManager = Setup(new ServerConfiguration
|
||||
{
|
||||
UICulture = "en-US"
|
||||
});
|
||||
await localizationManager.LoadAll();
|
||||
|
||||
var culture = localizationManager.FindLanguageInfo(code);
|
||||
Assert.NotNull(culture);
|
||||
Assert.Equal(expectedDisplayName, culture.DisplayName);
|
||||
Assert.Equal(code, culture.ThreeLetterISOLanguageName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetParentalRatings_Default_Success()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user