Compare commits
157 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 383d514353 | |||
| 6fc8237242 | |||
| 79d7a4d4df | |||
| e90031b4cc | |||
| 4f3d562d75 | |||
| 6c8b40f413 | |||
| ec81dc9be2 | |||
| 45f3fb1cfc | |||
| f83a24ec43 | |||
| 84c03a2d93 | |||
| bc8e249080 | |||
| 5ea9a74289 | |||
| 987d31ea16 | |||
| f850779781 | |||
| 3bdc2bff5f | |||
| 5c6a84549a | |||
| 48da35f91f | |||
| 39b29eb9f1 | |||
| a6740bf51e | |||
| c7797d3ead | |||
| c86d5838be | |||
| 43223b9036 | |||
| c71385d2db | |||
| ad5becc524 | |||
| 7c75dcfb9c | |||
| 7937e31a9b | |||
| 1f1f26306b | |||
| 577399ca05 | |||
| e7ea7c0383 | |||
| 9fe7751d05 | |||
| bf129ab9b8 | |||
| 6d23de64c0 | |||
| e4f48bb486 | |||
| 866b4460b1 | |||
| 9db0b275ff | |||
| 14008fd7d0 | |||
| 8532d88a71 | |||
| 737c739d33 | |||
| 679e83082f | |||
| d8e53f35a5 | |||
| 7a0e7b3cf8 | |||
| 373c63bcc7 | |||
| be5d343efb | |||
| c9c91cc34e | |||
| 774b4a0d3f | |||
| a26cded0f5 | |||
| 4ec82ec662 | |||
| 879787212e | |||
| 23100c9b86 | |||
| e6124bc154 | |||
| 8753b7200f | |||
| 88d5230bab | |||
| 2920c52d61 | |||
| de196a7687 | |||
| ba026716c1 | |||
| 125ee88311 | |||
| c53f6a2890 | |||
| 649b4c49e0 | |||
| 848ea703bc | |||
| 0adadff3e7 | |||
| ffdc3a6734 | |||
| a51cd4f8db | |||
| af87706379 | |||
| 8422ab687b | |||
| 64753cfc7f | |||
| 632fb05f46 | |||
| 527ed0607d | |||
| b59daab273 | |||
| 8f28d52929 | |||
| 749b263c48 | |||
| 80c68b8948 | |||
| a5687793c9 | |||
| b344771f8a | |||
| 3ff78b687d | |||
| d260f30810 | |||
| 7ffdde9a0b | |||
| e14194bfe2 | |||
| 3bf1a7e445 | |||
| 1faee43b11 | |||
| 31f9938e3a | |||
| ae9fd4ab35 | |||
| 71ed7f7676 | |||
| 3b6e003029 | |||
| 9357d610b1 | |||
| 1d4755894e | |||
| 2320f06666 | |||
| 8296f07a39 | |||
| 30f6263806 | |||
| a9249393e1 | |||
| f49a051a5f | |||
| 5bcab0f0f8 | |||
| c5a2ff8ac4 | |||
| 494ed7e4d2 | |||
| dd97e6bc45 | |||
| 7323ccfc23 | |||
| d258a87fda | |||
| 77a007a24d | |||
| a380153f92 | |||
| 56c81696d3 | |||
| 7297431f23 | |||
| f2c7bccb89 | |||
| b0b4068ddf | |||
| 3bd2cc9860 | |||
| feb035b9e0 | |||
| 82f362abd9 | |||
| 04b73cace6 | |||
| 3b69f38a1f | |||
| 126da94020 | |||
| f9dffa767f | |||
| 444b0ea310 | |||
| 484427b4aa | |||
| c3f0649fde | |||
| e877486056 | |||
| 9854751137 | |||
| 057e8ef240 | |||
| 205783f46f | |||
| b2fb96ffed | |||
| ee22feb89a | |||
| ca5979cd77 | |||
| d36f49589a | |||
| 70f37f0527 | |||
| dfe0aef530 | |||
| 9e31d5a73f | |||
| f088ca5555 | |||
| 2b46917dcf | |||
| 7bae6eff95 | |||
| d0fd23bb4b | |||
| d694a6c09a | |||
| 58f61ed118 | |||
| b9da0e7f83 | |||
| 7eaa0600e0 | |||
| 47c2c536e4 | |||
| 7ef9e95d75 | |||
| f8ea4577ab | |||
| 72da42cb0a | |||
| dbfa0f3027 | |||
| 78f437401b | |||
| 1db748399c | |||
| a41c67d16b | |||
| 84a1674f39 | |||
| 81e535fc62 | |||
| f9d26ea1bc | |||
| 5f3dbd8294 | |||
| 9cebdfdec0 | |||
| 891ccd7bb2 | |||
| 7efa4e38c1 | |||
| 0f7ba42987 | |||
| 3e8fe1ce11 | |||
| 044ff0542b | |||
| abfbd04782 | |||
| 8d0024ec49 | |||
| 8f761a64f5 | |||
| a64eebe79f | |||
| 8bb4cd017c | |||
| c5f09ab650 | |||
| 99df9c8fcd | |||
| 4b563f4d7e |
@@ -36,6 +36,7 @@
|
|||||||
- [dmitrylyzo](https://github.com/dmitrylyzo)
|
- [dmitrylyzo](https://github.com/dmitrylyzo)
|
||||||
- [DMouse10462](https://github.com/DMouse10462)
|
- [DMouse10462](https://github.com/DMouse10462)
|
||||||
- [DrPandemic](https://github.com/DrPandemic)
|
- [DrPandemic](https://github.com/DrPandemic)
|
||||||
|
- [eglia](https://github.com/eglia)
|
||||||
- [EraYaN](https://github.com/EraYaN)
|
- [EraYaN](https://github.com/EraYaN)
|
||||||
- [escabe](https://github.com/escabe)
|
- [escabe](https://github.com/escabe)
|
||||||
- [excelite](https://github.com/excelite)
|
- [excelite](https://github.com/excelite)
|
||||||
@@ -157,6 +158,7 @@
|
|||||||
- [jonas-resch](https://github.com/jonas-resch)
|
- [jonas-resch](https://github.com/jonas-resch)
|
||||||
- [vgambier](https://github.com/vgambier)
|
- [vgambier](https://github.com/vgambier)
|
||||||
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
|
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
|
||||||
|
- [RealGreenDragon](https://github.com/RealGreenDragon)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -72,7 +72,7 @@ COPY . .
|
|||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
|
||||||
|
|
||||||
FROM app
|
FROM app
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -64,7 +64,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
|||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# Build
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
|
||||||
|
|
||||||
FROM app
|
FROM app
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -55,7 +55,7 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
|||||||
# Discard objs - may cause failures if exists
|
# Discard objs - may cause failures if exists
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
RUN find . -type d -name obj | xargs -r rm -r
|
||||||
# Build
|
# Build
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
|
||||||
|
|
||||||
FROM app
|
FROM app
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Naming</PackageId>
|
<PackageId>Jellyfin.Naming</PackageId>
|
||||||
<VersionPrefix>10.8.1</VersionPrefix>
|
<VersionPrefix>10.8.8</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="TagLibSharp" Version="2.2.0" />
|
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class CompositionRoot.
|
/// Class CompositionRoot.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ApplicationHost : IServerApplicationHost, IDisposable
|
public abstract class ApplicationHost : IServerApplicationHost, IAsyncDisposable, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The environment variable prefixes to log at server startup.
|
/// The environment variable prefixes to log at server startup.
|
||||||
@@ -1088,15 +1088,7 @@ namespace Emby.Server.Implementations
|
|||||||
return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort);
|
return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Published server ends with a /
|
return GetSmartApiUrl(request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
|
||||||
if (!string.IsNullOrEmpty(PublishedServerUrl))
|
|
||||||
{
|
|
||||||
// Published server ends with a '/', so we need to remove it.
|
|
||||||
return PublishedServerUrl.Trim('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
string smart = NetManager.GetBindInterface(request, out var port);
|
|
||||||
return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@@ -1232,5 +1224,49 @@ namespace Emby.Server.Implementations
|
|||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await DisposeAsyncCore().ConfigureAwait(false);
|
||||||
|
Dispose(false);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to perform asynchronous cleanup of managed resources or for cascading calls to <see cref="DisposeAsync"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A ValueTask.</returns>
|
||||||
|
protected virtual async ValueTask DisposeAsyncCore()
|
||||||
|
{
|
||||||
|
var type = GetType();
|
||||||
|
|
||||||
|
Logger.LogInformation("Disposing {Type}", type.Name);
|
||||||
|
|
||||||
|
foreach (var (part, _) in _disposableParts)
|
||||||
|
{
|
||||||
|
var partType = part.GetType();
|
||||||
|
if (partType == type)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.LogInformation("Disposing {Type}", partType.Name);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
part.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.LogError(ex, "Error disposing {Type}", partType.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// used for closing websockets
|
||||||
|
foreach (var session in _sessionManager.Sessions)
|
||||||
|
{
|
||||||
|
await session.DisposeAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3541,6 +3541,13 @@ namespace Emby.Server.Implementations.Data
|
|||||||
statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (query.MinParentAndIndexNumber.HasValue)
|
||||||
|
{
|
||||||
|
whereClauses.Add("((ParentIndexNumber=@MinParentAndIndexNumberParent and IndexNumber>=@MinParentAndIndexNumberIndex) or ParentIndexNumber>@MinParentAndIndexNumberParent)");
|
||||||
|
statement?.TryBind("@MinParentAndIndexNumberParent", query.MinParentAndIndexNumber.Value.ParentIndexNumber);
|
||||||
|
statement?.TryBind("@MinParentAndIndexNumberIndex", query.MinParentAndIndexNumber.Value.IndexNumber);
|
||||||
|
}
|
||||||
|
|
||||||
if (query.MinDateCreated.HasValue)
|
if (query.MinDateCreated.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("DateCreated>=@MinDateCreated");
|
whereClauses.Add("DateCreated>=@MinDateCreated");
|
||||||
@@ -4934,6 +4941,7 @@ SELECT key FROM UserDatas WHERE isFavorite=@IsFavorite AND userId=@UserId)
|
|||||||
AND Type = @InternalPersonType)");
|
AND Type = @InternalPersonType)");
|
||||||
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
|
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
|
||||||
statement?.TryBind("@InternalPersonType", typeof(Person).FullName);
|
statement?.TryBind("@InternalPersonType", typeof(Person).FullName);
|
||||||
|
statement?.TryBind("@UserId", query.User.InternalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!query.ItemId.Equals(default))
|
if (!query.ItemId.Equals(default))
|
||||||
@@ -4988,11 +4996,6 @@ AND Type = @InternalPersonType)");
|
|||||||
statement?.TryBind("@NameContains", "%" + query.NameContains + "%");
|
statement?.TryBind("@NameContains", "%" + query.NameContains + "%");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.User != null)
|
|
||||||
{
|
|
||||||
statement?.TryBind("@UserId", query.User.InternalId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return whereClauses;
|
return whereClauses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
|
|
||||||
if (options.ContainsField(ItemFields.People))
|
if (options.ContainsField(ItemFields.People))
|
||||||
{
|
{
|
||||||
AttachPeople(dto, item);
|
AttachPeople(dto, item, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
|
if (options.ContainsField(ItemFields.PrimaryImageAspectRatio))
|
||||||
@@ -503,7 +503,8 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="dto">The dto.</param>
|
/// <param name="dto">The dto.</param>
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
private void AttachPeople(BaseItemDto dto, BaseItem item)
|
/// <param name="user">The requesting user.</param>
|
||||||
|
private void AttachPeople(BaseItemDto dto, BaseItem item, User user = null)
|
||||||
{
|
{
|
||||||
// Ordering by person type to ensure actors and artists are at the front.
|
// Ordering by person type to ensure actors and artists are at the front.
|
||||||
// This is taking advantage of the fact that they both begin with A
|
// This is taking advantage of the fact that they both begin with A
|
||||||
@@ -560,6 +561,9 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).Where(i => i != null)
|
}).Where(i => i != null)
|
||||||
|
.Where(i => user == null ?
|
||||||
|
true :
|
||||||
|
i.IsVisible(user))
|
||||||
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
|
.GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(x => x.First())
|
.Select(x => x.First())
|
||||||
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
|
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|||||||
@@ -29,10 +29,10 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" />
|
||||||
<PackageReference Include="Mono.Nat" Version="3.0.3" />
|
<PackageReference Include="Mono.Nat" Version="3.0.3" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.32.1" />
|
<PackageReference Include="sharpcompress" Version="0.32.2" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||||
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class WebSocketConnection.
|
/// Class WebSocketConnection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class WebSocketConnection : IWebSocketConnection, IDisposable
|
public class WebSocketConnection : IWebSocketConnection
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logger.
|
/// The logger.
|
||||||
@@ -36,6 +36,8 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly WebSocket _socket;
|
private readonly WebSocket _socket;
|
||||||
|
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
|
/// Initializes a new instance of the <see cref="WebSocketConnection" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -244,10 +246,39 @@ namespace Emby.Server.Implementations.HttpServer
|
|||||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||||
protected virtual void Dispose(bool dispose)
|
protected virtual void Dispose(bool dispose)
|
||||||
{
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (dispose)
|
if (dispose)
|
||||||
{
|
{
|
||||||
_socket.Dispose();
|
_socket.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await DisposeAsyncCore().ConfigureAwait(false);
|
||||||
|
Dispose(false);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to perform asynchronous cleanup of managed resources or for cascading calls to <see cref="DisposeAsync"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A ValueTask.</returns>
|
||||||
|
protected virtual async ValueTask DisposeAsyncCore()
|
||||||
|
{
|
||||||
|
if (_socket.State == WebSocketState.Open)
|
||||||
|
{
|
||||||
|
await _socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "System Shutdown", CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_socket.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2453,6 +2453,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return RootFolder;
|
return RootFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void QueueLibraryScan()
|
||||||
|
{
|
||||||
|
_taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int? GetSeasonNumberFromPath(string path)
|
public int? GetSeasonNumberFromPath(string path)
|
||||||
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
|
=> SeasonPathParser.Parse(path, true, true).SeasonNumber;
|
||||||
@@ -2760,7 +2766,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public List<Person> GetPeopleItems(InternalPeopleQuery query)
|
public List<Person> GetPeopleItems(InternalPeopleQuery query)
|
||||||
{
|
{
|
||||||
return _itemRepository.GetPeopleNames(query).Select(i =>
|
return _itemRepository.GetPeopleNames(query)
|
||||||
|
.Select(i =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -2771,7 +2778,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_logger.LogError(ex, "Error getting person");
|
_logger.LogError(ex, "Error getting person");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).Where(i => i != null).ToList();
|
})
|
||||||
|
.Where(i => i != null)
|
||||||
|
.Where(i => query.User == null ?
|
||||||
|
true :
|
||||||
|
i.IsVisible(query.User))
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<string> GetPeopleNames(InternalPeopleQuery query)
|
public List<string> GetPeopleNames(InternalPeopleQuery query)
|
||||||
|
|||||||
@@ -165,12 +165,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
|
|
||||||
const double DesiredAspect = 2.0 / 3;
|
const double DesiredAspect = 2.0 / 3;
|
||||||
|
|
||||||
programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, DesiredAspect) ??
|
programEntry.PrimaryImage = GetProgramImage(ApiUrl, imagesWithText, DesiredAspect, token) ??
|
||||||
GetProgramImage(ApiUrl, allImages, DesiredAspect);
|
GetProgramImage(ApiUrl, allImages, DesiredAspect, token);
|
||||||
|
|
||||||
const double WideAspect = 16.0 / 9;
|
const double WideAspect = 16.0 / 9;
|
||||||
|
|
||||||
programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, WideAspect);
|
programEntry.ThumbImage = GetProgramImage(ApiUrl, imagesWithText, WideAspect, token);
|
||||||
|
|
||||||
// Don't supply the same image twice
|
// Don't supply the same image twice
|
||||||
if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal))
|
if (string.Equals(programEntry.PrimaryImage, programEntry.ThumbImage, StringComparison.Ordinal))
|
||||||
@@ -178,7 +178,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
programEntry.ThumbImage = null;
|
programEntry.ThumbImage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, WideAspect);
|
programEntry.BackdropImage = GetProgramImage(ApiUrl, imagesWithoutText, WideAspect, token);
|
||||||
|
|
||||||
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
|
// programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
|
||||||
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
|
// GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
|
||||||
@@ -399,7 +399,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, double desiredAspect)
|
private static string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, double desiredAspect, string token)
|
||||||
{
|
{
|
||||||
var match = images
|
var match = images
|
||||||
.OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
|
.OrderBy(i => Math.Abs(desiredAspect - GetAspectRatio(i)))
|
||||||
@@ -423,7 +423,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return apiUrl + "/image/" + uri;
|
return apiUrl + "/image/" + uri + "?token=" + token;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,6 +457,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
IReadOnlyList<string> programIds,
|
IReadOnlyList<string> programIds,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var token = await GetToken(info, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (programIds.Count == 0)
|
if (programIds.Count == 0)
|
||||||
{
|
{
|
||||||
return Array.Empty<ShowImagesDto>();
|
return Array.Empty<ShowImagesDto>();
|
||||||
@@ -478,6 +480,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
|||||||
{
|
{
|
||||||
Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json)
|
Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json)
|
||||||
};
|
};
|
||||||
|
message.Headers.TryAddWithoutValidation("token", token);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -386,6 +386,7 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
yield return new LocalizationOption("Español (Dominicana)", "es_DO");
|
yield return new LocalizationOption("Español (Dominicana)", "es_DO");
|
||||||
yield return new LocalizationOption("Español (México)", "es-MX");
|
yield return new LocalizationOption("Español (México)", "es-MX");
|
||||||
yield return new LocalizationOption("Eesti", "et");
|
yield return new LocalizationOption("Eesti", "et");
|
||||||
|
yield return new LocalizationOption("Basque", "eu");
|
||||||
yield return new LocalizationOption("فارسی", "fa");
|
yield return new LocalizationOption("فارسی", "fa");
|
||||||
yield return new LocalizationOption("Suomi", "fi");
|
yield return new LocalizationOption("Suomi", "fi");
|
||||||
yield return new LocalizationOption("Filipino", "fil");
|
yield return new LocalizationOption("Filipino", "fil");
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ using Microsoft.Extensions.Logging;
|
|||||||
|
|
||||||
namespace Emby.Server.Implementations.Session
|
namespace Emby.Server.Implementations.Session
|
||||||
{
|
{
|
||||||
public sealed class WebSocketController : ISessionController, IDisposable
|
public sealed class WebSocketController : ISessionController, IAsyncDisposable, IDisposable
|
||||||
{
|
{
|
||||||
private readonly ILogger<WebSocketController> _logger;
|
private readonly ILogger<WebSocketController> _logger;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
@@ -99,6 +99,23 @@ namespace Emby.Server.Implementations.Session
|
|||||||
foreach (var socket in _sockets)
|
foreach (var socket in _sockets)
|
||||||
{
|
{
|
||||||
socket.Closed -= OnConnectionClosed;
|
socket.Closed -= OnConnectionClosed;
|
||||||
|
socket.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var socket in _sockets)
|
||||||
|
{
|
||||||
|
socket.Closed -= OnConnectionClosed;
|
||||||
|
await socket.DisposeAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
|||||||
@@ -135,20 +135,15 @@ namespace Emby.Server.Implementations.TV
|
|||||||
return GetResult(episodes, request);
|
return GetResult(episodes, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys, DtoOptions dtoOptions)
|
private IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IReadOnlyList<string> seriesKeys, DtoOptions dtoOptions)
|
||||||
{
|
{
|
||||||
// Avoid implicitly captured closure
|
var allNextUp = seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, false));
|
||||||
var currentUser = user;
|
|
||||||
|
|
||||||
var allNextUp = seriesKeys
|
|
||||||
.Select(i => GetNextUp(i, currentUser, dtoOptions, false));
|
|
||||||
|
|
||||||
if (request.EnableRewatching)
|
if (request.EnableRewatching)
|
||||||
{
|
{
|
||||||
allNextUp = allNextUp.Concat(
|
allNextUp = allNextUp.Concat(
|
||||||
seriesKeys.Select(i => GetNextUp(i, currentUser, dtoOptions, true))
|
seriesKeys.Select(i => GetNextUp(i, user, dtoOptions, true)))
|
||||||
)
|
.OrderByDescending(i => i.LastWatchedDate);
|
||||||
.OrderByDescending(i => i.Item1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If viewing all next up for all series, remove first episodes
|
// If viewing all next up for all series, remove first episodes
|
||||||
@@ -161,23 +156,18 @@ namespace Emby.Server.Implementations.TV
|
|||||||
{
|
{
|
||||||
if (request.DisableFirstEpisode)
|
if (request.DisableFirstEpisode)
|
||||||
{
|
{
|
||||||
return i.Item1 != DateTime.MinValue;
|
return i.LastWatchedDate != DateTime.MinValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alwaysEnableFirstEpisode || (i.Item1 != DateTime.MinValue && i.Item1.Date >= request.NextUpDateCutoff))
|
if (alwaysEnableFirstEpisode || (i.LastWatchedDate != DateTime.MinValue && i.LastWatchedDate.Date >= request.NextUpDateCutoff))
|
||||||
{
|
{
|
||||||
anyFound = true;
|
anyFound = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!anyFound && i.Item1 == DateTime.MinValue)
|
return !anyFound && i.LastWatchedDate == DateTime.MinValue;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
})
|
})
|
||||||
.Select(i => i.Item2())
|
.Select(i => i.GetEpisodeFunction())
|
||||||
.Where(i => i != null);
|
.Where(i => i != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,14 +185,13 @@ namespace Emby.Server.Implementations.TV
|
|||||||
/// Gets the next up.
|
/// Gets the next up.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Task{Episode}.</returns>
|
/// <returns>Task{Episode}.</returns>
|
||||||
private Tuple<DateTime, Func<Episode>> GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool rewatching)
|
private (DateTime LastWatchedDate, Func<Episode> GetEpisodeFunction) GetNextUp(string seriesKey, User user, DtoOptions dtoOptions, bool rewatching)
|
||||||
{
|
{
|
||||||
var lastQuery = new InternalItemsQuery(user)
|
var lastQuery = new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
AncestorWithPresentationUniqueKey = null,
|
AncestorWithPresentationUniqueKey = null,
|
||||||
SeriesPresentationUniqueKey = seriesKey,
|
SeriesPresentationUniqueKey = seriesKey,
|
||||||
IncludeItemTypes = new[] { BaseItemKind.Episode },
|
IncludeItemTypes = new[] { BaseItemKind.Episode },
|
||||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Descending) },
|
|
||||||
IsPlayed = true,
|
IsPlayed = true,
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
ParentIndexNumberNotEquals = 0,
|
ParentIndexNumberNotEquals = 0,
|
||||||
@@ -213,42 +202,38 @@ namespace Emby.Server.Implementations.TV
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (rewatching)
|
// If rewatching is enabled, sort first by date played and then by season and episode numbers
|
||||||
{
|
lastQuery.OrderBy = rewatching
|
||||||
// find last watched by date played, not by newest episode watched
|
? new[] { (ItemSortBy.DatePlayed, SortOrder.Descending), (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) }
|
||||||
lastQuery.OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) };
|
: new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Descending), (ItemSortBy.IndexNumber, SortOrder.Descending) };
|
||||||
}
|
|
||||||
|
|
||||||
var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault();
|
var lastWatchedEpisode = _libraryManager.GetItemList(lastQuery).Cast<Episode>().FirstOrDefault();
|
||||||
|
|
||||||
Func<Episode> getEpisode = () =>
|
Episode GetEpisode()
|
||||||
{
|
{
|
||||||
var nextQuery = new InternalItemsQuery(user)
|
var nextQuery = new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
AncestorWithPresentationUniqueKey = null,
|
AncestorWithPresentationUniqueKey = null,
|
||||||
SeriesPresentationUniqueKey = seriesKey,
|
SeriesPresentationUniqueKey = seriesKey,
|
||||||
IncludeItemTypes = new[] { BaseItemKind.Episode },
|
IncludeItemTypes = new[] { BaseItemKind.Episode },
|
||||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
|
OrderBy = new[] { (ItemSortBy.ParentIndexNumber, SortOrder.Ascending), (ItemSortBy.IndexNumber, SortOrder.Ascending) },
|
||||||
Limit = 1,
|
Limit = 1,
|
||||||
IsPlayed = rewatching,
|
IsPlayed = rewatching,
|
||||||
IsVirtualItem = false,
|
IsVirtualItem = false,
|
||||||
ParentIndexNumberNotEquals = 0,
|
ParentIndexNumberNotEquals = 0,
|
||||||
MinSortName = lastWatchedEpisode?.SortName,
|
|
||||||
DtoOptions = dtoOptions
|
DtoOptions = dtoOptions
|
||||||
};
|
};
|
||||||
|
|
||||||
Episode nextEpisode;
|
// Locate the next up episode based on the last watched episode's season and episode number
|
||||||
if (rewatching)
|
var lastWatchedParentIndexNumber = lastWatchedEpisode?.ParentIndexNumber;
|
||||||
|
var lastWatchedIndexNumber = lastWatchedEpisode?.IndexNumberEnd ?? lastWatchedEpisode?.IndexNumber;
|
||||||
|
if (lastWatchedParentIndexNumber.HasValue && lastWatchedIndexNumber.HasValue)
|
||||||
{
|
{
|
||||||
nextQuery.Limit = 2;
|
nextQuery.MinParentAndIndexNumber = (lastWatchedParentIndexNumber.Value, lastWatchedIndexNumber.Value + 1);
|
||||||
// get watched episode after most recently watched
|
|
||||||
nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().ElementAtOrDefault(1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nextEpisode = _libraryManager.GetItemList(nextQuery).Cast<Episode>().FirstOrDefault();
|
||||||
|
|
||||||
if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons)
|
if (_configurationManager.Configuration.DisplaySpecialsWithinSeasons)
|
||||||
{
|
{
|
||||||
var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
var consideredEpisodes = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
@@ -297,7 +282,7 @@ namespace Emby.Server.Implementations.TV
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nextEpisode;
|
return nextEpisode;
|
||||||
};
|
}
|
||||||
|
|
||||||
if (lastWatchedEpisode != null)
|
if (lastWatchedEpisode != null)
|
||||||
{
|
{
|
||||||
@@ -305,11 +290,11 @@ namespace Emby.Server.Implementations.TV
|
|||||||
|
|
||||||
var lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue.AddDays(1);
|
var lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue.AddDays(1);
|
||||||
|
|
||||||
return new Tuple<DateTime, Func<Episode>>(lastWatchedDate, getEpisode);
|
return (lastWatchedDate, GetEpisode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the first episode
|
// Return the first episode
|
||||||
return new Tuple<DateTime, Func<Episode>>(DateTime.MinValue, getEpisode);
|
return (DateTime.MinValue, GetEpisode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query)
|
private static QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, NextUpQuery query)
|
||||||
|
|||||||
@@ -1790,7 +1790,8 @@ namespace Jellyfin.Api.Controllers
|
|||||||
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (EncodingHelper.IsCopyCodec(codec)
|
if (EncodingHelper.IsCopyCodec(codec)
|
||||||
&& (string.Equals(state.VideoStream.CodecTag, "dovi", StringComparison.OrdinalIgnoreCase)
|
&& (string.Equals(state.VideoStream.VideoRangeType, "DOVI", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(state.VideoStream.CodecTag, "dovi", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(state.VideoStream.CodecTag, "dvh1", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase)))
|
|| string.Equals(state.VideoStream.CodecTag, "dvhe", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -270,30 +270,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
includeItemTypes = new[] { BaseItemKind.Playlist };
|
includeItemTypes = new[] { BaseItemKind.Playlist };
|
||||||
}
|
}
|
||||||
|
|
||||||
var enabledChannels = user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
|
|
||||||
|
|
||||||
bool isInEnabledFolder = Array.IndexOf(user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1
|
|
||||||
// Assume all folders inside an EnabledChannel are enabled
|
|
||||||
|| Array.IndexOf(enabledChannels, item.Id) != -1
|
|
||||||
// Assume all items inside an EnabledChannel are enabled
|
|
||||||
|| Array.IndexOf(enabledChannels, item.ChannelId) != -1;
|
|
||||||
|
|
||||||
var collectionFolders = _libraryManager.GetCollectionFolders(item);
|
|
||||||
foreach (var collectionFolder in collectionFolders)
|
|
||||||
{
|
|
||||||
if (user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id))
|
|
||||||
{
|
|
||||||
isInEnabledFolder = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is not UserRootFolder
|
if (item is not UserRootFolder
|
||||||
&& !isInEnabledFolder
|
// api keys can always access all folders
|
||||||
&& !user.HasPermission(PermissionKind.EnableAllFolders)
|
&& !ClaimHelpers.GetIsApiKey(User)
|
||||||
&& !user.HasPermission(PermissionKind.EnableAllChannels)
|
// check the item is visible for the user
|
||||||
&& !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase))
|
&& !item.IsVisible(user))
|
||||||
{
|
{
|
||||||
_logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name);
|
_logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user!.Username, item.Name);
|
||||||
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
|
return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -492,7 +492,7 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// <response code="200">Media folders returned.</response>
|
/// <response code="200">Media folders returned.</response>
|
||||||
/// <returns>List of user media folders.</returns>
|
/// <returns>List of user media folders.</returns>
|
||||||
[HttpGet("Library/MediaFolders")]
|
[HttpGet("Library/MediaFolders")]
|
||||||
[Authorize(Policy = Policies.DefaultAuthorization)]
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public ActionResult<QueryResult<BaseItemDto>> GetMediaFolders([FromQuery] bool? isHidden)
|
public ActionResult<QueryResult<BaseItemDto>> GetMediaFolders([FromQuery] bool? isHidden)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -98,7 +98,10 @@ namespace Jellyfin.Api.Controllers
|
|||||||
Limit = limit ?? 0
|
Limit = limit ?? 0
|
||||||
});
|
});
|
||||||
|
|
||||||
return new QueryResult<BaseItemDto>(peopleItems.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user)).ToArray());
|
return new QueryResult<BaseItemDto>(
|
||||||
|
peopleItems
|
||||||
|
.Select(person => _dtoService.GetItemByNameDto(person, dtoOptions, null, user))
|
||||||
|
.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -117,7 +117,13 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[FromQuery] bool enableRedirection = true)
|
[FromQuery] bool enableRedirection = true)
|
||||||
{
|
{
|
||||||
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
|
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
|
||||||
(await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false)).DeviceId = deviceId;
|
var authorizationInfo = await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||||
|
authorizationInfo.DeviceId = deviceId;
|
||||||
|
|
||||||
|
if (!userId.HasValue || userId.Value.Equals(Guid.Empty))
|
||||||
|
{
|
||||||
|
userId = authorizationInfo.UserId;
|
||||||
|
}
|
||||||
|
|
||||||
var authInfo = await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
var authInfo = await _authorizationContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|||||||
@@ -282,16 +282,19 @@ namespace Jellyfin.Api.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var success = await _userManager.AuthenticateUser(
|
if (!HttpContext.User.IsInRole(UserRoles.Administrator))
|
||||||
user.Username,
|
|
||||||
request.CurrentPw,
|
|
||||||
request.CurrentPw,
|
|
||||||
HttpContext.GetNormalizedRemoteIp().ToString(),
|
|
||||||
false).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (success == null)
|
|
||||||
{
|
{
|
||||||
return StatusCode(StatusCodes.Status403Forbidden, "Invalid user or password entered.");
|
var success = await _userManager.AuthenticateUser(
|
||||||
|
user.Username,
|
||||||
|
request.CurrentPw,
|
||||||
|
request.CurrentPw,
|
||||||
|
HttpContext.GetNormalizedRemoteIp().ToString(),
|
||||||
|
false).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (success == null)
|
||||||
|
{
|
||||||
|
return StatusCode(StatusCodes.Status403Forbidden, "Invalid user or password entered.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false);
|
await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -654,7 +654,7 @@ namespace Jellyfin.Api.Helpers
|
|||||||
{
|
{
|
||||||
if (EnableThrottling(state))
|
if (EnableThrottling(state))
|
||||||
{
|
{
|
||||||
transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem);
|
transcodingJob.TranscodingThrottler = new TranscodingThrottler(transcodingJob, new Logger<TranscodingThrottler>(new LoggerFactory()), _serverConfigurationManager, _fileSystem, _mediaEncoder);
|
||||||
transcodingJob.TranscodingThrottler.Start();
|
transcodingJob.TranscodingThrottler.Start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.3.1" />
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -17,6 +18,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
|
|||||||
private readonly ILogger<TranscodingThrottler> _logger;
|
private readonly ILogger<TranscodingThrottler> _logger;
|
||||||
private readonly IConfigurationManager _config;
|
private readonly IConfigurationManager _config;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private Timer? _timer;
|
private Timer? _timer;
|
||||||
private bool _isPaused;
|
private bool _isPaused;
|
||||||
|
|
||||||
@@ -27,12 +29,14 @@ namespace Jellyfin.Api.Models.PlaybackDtos
|
|||||||
/// <param name="logger">Instance of the <see cref="ILogger{TranscodingThrottler}"/> interface.</param>
|
/// <param name="logger">Instance of the <see cref="ILogger{TranscodingThrottler}"/> interface.</param>
|
||||||
/// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
/// <param name="config">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
||||||
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
|
||||||
public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem)
|
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
||||||
|
public TranscodingThrottler(TranscodingJobDto job, ILogger<TranscodingThrottler> logger, IConfigurationManager config, IFileSystem fileSystem, IMediaEncoder mediaEncoder)
|
||||||
{
|
{
|
||||||
_job = job;
|
_job = job;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
|
_mediaEncoder = mediaEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -55,7 +59,8 @@ namespace Jellyfin.Api.Models.PlaybackDtos
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _job.Process!.StandardInput.WriteLineAsync().ConfigureAwait(false);
|
var resumeKey = _mediaEncoder.IsPkeyPauseSupported ? "u" : Environment.NewLine;
|
||||||
|
await _job.Process!.StandardInput.WriteAsync(resumeKey).ConfigureAwait(false);
|
||||||
_isPaused = false;
|
_isPaused = false;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -125,11 +130,13 @@ namespace Jellyfin.Api.Models.PlaybackDtos
|
|||||||
{
|
{
|
||||||
if (!_isPaused)
|
if (!_isPaused)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Sending pause command to ffmpeg");
|
var pauseKey = _mediaEncoder.IsPkeyPauseSupported ? "p" : "c";
|
||||||
|
|
||||||
|
_logger.LogDebug("Sending pause command [{Key}] to ffmpeg", pauseKey);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _job.Process!.StandardInput.WriteAsync("c").ConfigureAwait(false);
|
await _job.Process!.StandardInput.WriteAsync(pauseKey).ConfigureAwait(false);
|
||||||
_isPaused = true;
|
_isPaused = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Data</PackageId>
|
<PackageId>Jellyfin.Data</PackageId>
|
||||||
<VersionPrefix>10.8.1</VersionPrefix>
|
<VersionPrefix>10.8.8</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -18,8 +18,8 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BlurHashSharp" Version="1.2.0" />
|
<PackageReference Include="BlurHashSharp" Version="1.2.0" />
|
||||||
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
|
<PackageReference Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
|
||||||
<PackageReference Include="SkiaSharp" Version="2.88.1-preview.71" />
|
<PackageReference Include="SkiaSharp" Version="2.88.2" />
|
||||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.1-preview.71" />
|
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.2" />
|
||||||
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
|
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -27,13 +27,13 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.9" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.6">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.6">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.9">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
|||||||
+657
@@ -0,0 +1,657 @@
|
|||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Jellyfin.Server.Implementations;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(JellyfinDb))]
|
||||||
|
[Migration("20221022080052_AddIndexActivityLogsDateCreated")]
|
||||||
|
partial class AddIndexActivityLogsDateCreated
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasDefaultSchema("jellyfin")
|
||||||
|
.HasAnnotation("ProductVersion", "6.0.9");
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("DayOfWeek")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<double>("EndHour")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<double>("StartHour")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("AccessSchedules", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateCreated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("ItemId")
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("LogSeverity")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Overview")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("ShortOverview")
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Type")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DateCreated");
|
||||||
|
|
||||||
|
b.ToTable("ActivityLogs", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("ItemId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "ItemId", "Client", "Key")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("CustomItemDisplayPreferences", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ChromecastVersion")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DashboardTheme")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableNextVideoInfoOverlay")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("IndexBy")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("ItemId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ScrollDirection")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("ShowBackdrop")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("ShowSidebar")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SkipBackwardLength")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SkipForwardLength")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("TvHome")
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "ItemId", "Client")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("DisplayPreferences", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("DisplayPreferencesId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Order")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Type")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DisplayPreferencesId");
|
||||||
|
|
||||||
|
b.ToTable("HomeSection", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Path")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(512)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ImageInfos", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Client")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("IndexBy")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("ItemId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberIndexing")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberSorting")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SortBy")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SortOrder")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("ViewType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
|
b.ToTable("ItemDisplayPreferences", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Kind")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("Permission_Permissions_Guid")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("Value")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "Kind")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[UserId] IS NOT NULL");
|
||||||
|
|
||||||
|
b.ToTable("Permissions", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Kind")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("Preference_Preferences_Guid")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid?>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(65535)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "Kind")
|
||||||
|
.IsUnique()
|
||||||
|
.HasFilter("[UserId] IS NOT NULL");
|
||||||
|
|
||||||
|
b.ToTable("Preferences", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AccessToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateCreated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateLastActivity")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AccessToken")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("ApiKeys", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AccessToken")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AppName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AppVersion")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(32)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateCreated")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateLastActivity")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DeviceId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(256)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DeviceName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(64)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsActive")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<Guid>("UserId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId");
|
||||||
|
|
||||||
|
b.HasIndex("AccessToken", "DateLastActivity");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId", "DateLastActivity");
|
||||||
|
|
||||||
|
b.HasIndex("UserId", "DeviceId");
|
||||||
|
|
||||||
|
b.ToTable("Devices", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("CustomName")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("DeviceId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("DeviceId")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("DeviceOptions", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AudioLanguagePreference")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("AuthenticationProviderId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("DisplayCollectionsView")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("DisplayMissingEpisodes")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("EasyPassword")
|
||||||
|
.HasMaxLength(65535)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableAutoLogin")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableLocalPassword")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableNextEpisodeAutoPlay")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("EnableUserPreferenceAccess")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("HidePlayedInLatest")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<long>("InternalId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("InvalidLoginAttemptCount")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastActivityDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("LastLoginDate")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("LoginAttemptsBeforeLockout")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("MaxActiveSessions")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("MaxParentalAgeRating")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("MustUpdatePassword")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.HasMaxLength(65535)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordResetProviderId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("PlayDefaultAudioTrack")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberAudioSelections")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("RememberSubtitleSelections")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("RemoteClientBitrateLimit")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SubtitleLanguagePreference")
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SubtitleMode")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SyncPlayAccess")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Username")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.UseCollation("NOCASE");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("Username")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("Users", "jellyfin");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("AccessSchedules")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("DisplayPreferences")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.DisplayPreferences", null)
|
||||||
|
.WithMany("HomeSections")
|
||||||
|
.HasForeignKey("DisplayPreferencesId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithOne("ProfileImage")
|
||||||
|
.HasForeignKey("Jellyfin.Data.Entities.ImageInfo", "UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("ItemDisplayPreferences")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("Permissions")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", null)
|
||||||
|
.WithMany("Preferences")
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Jellyfin.Data.Entities.User", "User")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("UserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("User");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("HomeSections");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("AccessSchedules");
|
||||||
|
|
||||||
|
b.Navigation("DisplayPreferences");
|
||||||
|
|
||||||
|
b.Navigation("ItemDisplayPreferences");
|
||||||
|
|
||||||
|
b.Navigation("Permissions");
|
||||||
|
|
||||||
|
b.Navigation("Preferences");
|
||||||
|
|
||||||
|
b.Navigation("ProfileImage");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma warning disable CS1591, SA1601
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
|
{
|
||||||
|
public partial class AddIndexActivityLogsDateCreated : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_ActivityLogs_DateCreated",
|
||||||
|
schema: "jellyfin",
|
||||||
|
table: "ActivityLogs",
|
||||||
|
column: "DateCreated");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_ActivityLogs_DateCreated",
|
||||||
|
schema: "jellyfin",
|
||||||
|
table: "ActivityLogs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
namespace Jellyfin.Server.Implementations.Migrations
|
namespace Jellyfin.Server.Implementations.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(JellyfinDb))]
|
[DbContext(typeof(JellyfinDb))]
|
||||||
@@ -15,7 +17,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasDefaultSchema("jellyfin")
|
.HasDefaultSchema("jellyfin")
|
||||||
.HasAnnotation("ProductVersion", "5.0.7");
|
.HasAnnotation("ProductVersion", "6.0.9");
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
{
|
{
|
||||||
@@ -39,7 +41,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.ToTable("AccessSchedules");
|
b.ToTable("AccessSchedules", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.ActivityLog", b =>
|
||||||
@@ -85,7 +87,9 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("ActivityLogs");
|
b.HasIndex("DateCreated");
|
||||||
|
|
||||||
|
b.ToTable("ActivityLogs", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.CustomItemDisplayPreferences", b =>
|
||||||
@@ -117,7 +121,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
b.HasIndex("UserId", "ItemId", "Client", "Key")
|
b.HasIndex("UserId", "ItemId", "Client", "Key")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("CustomItemDisplayPreferences");
|
b.ToTable("CustomItemDisplayPreferences", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.DisplayPreferences", b =>
|
||||||
@@ -174,7 +178,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
b.HasIndex("UserId", "ItemId", "Client")
|
b.HasIndex("UserId", "ItemId", "Client")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("DisplayPreferences");
|
b.ToTable("DisplayPreferences", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.HomeSection", b =>
|
||||||
@@ -196,7 +200,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
|
|
||||||
b.HasIndex("DisplayPreferencesId");
|
b.HasIndex("DisplayPreferencesId");
|
||||||
|
|
||||||
b.ToTable("HomeSection");
|
b.ToTable("HomeSection", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.ImageInfo", b =>
|
||||||
@@ -221,7 +225,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
b.HasIndex("UserId")
|
b.HasIndex("UserId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("ImageInfos");
|
b.ToTable("ImageInfos", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.ItemDisplayPreferences", b =>
|
||||||
@@ -265,7 +269,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
|
|
||||||
b.HasIndex("UserId");
|
b.HasIndex("UserId");
|
||||||
|
|
||||||
b.ToTable("ItemDisplayPreferences");
|
b.ToTable("ItemDisplayPreferences", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.Permission", b =>
|
||||||
@@ -296,7 +300,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasFilter("[UserId] IS NOT NULL");
|
.HasFilter("[UserId] IS NOT NULL");
|
||||||
|
|
||||||
b.ToTable("Permissions");
|
b.ToTable("Permissions", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.Preference", b =>
|
||||||
@@ -329,7 +333,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
.IsUnique()
|
.IsUnique()
|
||||||
.HasFilter("[UserId] IS NOT NULL");
|
.HasFilter("[UserId] IS NOT NULL");
|
||||||
|
|
||||||
b.ToTable("Preferences");
|
b.ToTable("Preferences", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.Security.ApiKey", b =>
|
||||||
@@ -358,7 +362,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
b.HasIndex("AccessToken")
|
b.HasIndex("AccessToken")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("ApiKeys");
|
b.ToTable("ApiKeys", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.Security.Device", b =>
|
||||||
@@ -416,7 +420,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
|
|
||||||
b.HasIndex("UserId", "DeviceId");
|
b.HasIndex("UserId", "DeviceId");
|
||||||
|
|
||||||
b.ToTable("Devices");
|
b.ToTable("Devices", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.Security.DeviceOptions", b =>
|
||||||
@@ -437,7 +441,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
b.HasIndex("DeviceId")
|
b.HasIndex("DeviceId")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("DeviceOptions");
|
b.ToTable("DeviceOptions", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.User", b =>
|
||||||
@@ -550,7 +554,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
|||||||
b.HasIndex("Username")
|
b.HasIndex("Username")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("Users");
|
b.ToTable("Users", "jellyfin");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using Jellyfin.Data.Entities;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Implementations.ModelConfiguration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// FluentAPI configuration for the ActivityLog entity.
|
||||||
|
/// </summary>
|
||||||
|
public class ActivityLogConfiguration : IEntityTypeConfiguration<ActivityLog>
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Configure(EntityTypeBuilder<ActivityLog> builder)
|
||||||
|
{
|
||||||
|
builder.HasIndex(entity => entity.DateCreated);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -434,11 +434,15 @@ namespace Jellyfin.Server.Extensions
|
|||||||
options.MapType<TranscodeReason>(() =>
|
options.MapType<TranscodeReason>(() =>
|
||||||
new OpenApiSchema
|
new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = "string",
|
Type = "array",
|
||||||
Enum = Enum.GetNames<TranscodeReason>()
|
Items = new OpenApiSchema
|
||||||
.Select(e => new OpenApiString(e))
|
{
|
||||||
.Cast<IOpenApiAny>()
|
Reference = new OpenApiReference
|
||||||
.ToArray()
|
{
|
||||||
|
Id = nameof(TranscodeReason),
|
||||||
|
Type = ReferenceType.Schema,
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
|
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using Jellyfin.Server.Migrations;
|
using Jellyfin.Server.Migrations;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
@@ -8,6 +9,7 @@ using MediaBrowser.Model.ApiClient;
|
|||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using MediaBrowser.Model.SyncPlay;
|
using MediaBrowser.Model.SyncPlay;
|
||||||
|
using Microsoft.OpenApi.Any;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
@@ -56,6 +58,15 @@ namespace Jellyfin.Server.Filters
|
|||||||
|
|
||||||
context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository);
|
context.SchemaGenerator.GenerateSchema(configuration.ConfigurationType, context.SchemaRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.SchemaRepository.AddDefinition(nameof(TranscodeReason), new OpenApiSchema
|
||||||
|
{
|
||||||
|
Type = "string",
|
||||||
|
Enum = Enum.GetNames<TranscodeReason>()
|
||||||
|
.Select(e => new OpenApiString(e))
|
||||||
|
.Cast<IOpenApiAny>()
|
||||||
|
.ToArray()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,8 @@
|
|||||||
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
<PackageReference Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.6" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.9" />
|
||||||
<PackageReference Include="prometheus-net" Version="6.0.0" />
|
<PackageReference Include="prometheus-net" Version="6.0.0" />
|
||||||
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
|
<PackageReference Include="prometheus-net.AspNetCore" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ namespace Jellyfin.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appHost.Dispose();
|
await appHost.DisposeAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_restartOnShutdown)
|
if (_restartOnShutdown)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Common</PackageId>
|
<PackageId>Jellyfin.Common</PackageId>
|
||||||
<VersionPrefix>10.8.1</VersionPrefix>
|
<VersionPrefix>10.8.8</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -169,8 +169,8 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||||||
|
|
||||||
var childUpdateType = ItemUpdateType.None;
|
var childUpdateType = ItemUpdateType.None;
|
||||||
|
|
||||||
// Refresh songs
|
// Refresh songs only and not m3u files in album folder
|
||||||
foreach (var item in items)
|
foreach (var item in items.OfType<Audio>())
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
|||||||
@@ -1863,7 +1863,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
data.PlaybackPositionTicks = 0;
|
data.PlaybackPositionTicks = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
data.LastPlayedDate = datePlayed ?? data.LastPlayedDate ?? DateTime.UtcNow;
|
data.LastPlayedDate = datePlayed ?? DateTime.UtcNow;
|
||||||
data.Played = true;
|
data.Played = true;
|
||||||
|
|
||||||
UserDataManager.SaveUserData(user.Id, this, data, UserDataSaveReason.TogglePlayed, CancellationToken.None);
|
UserDataManager.SaveUserData(user.Id, this, data, UserDataSaveReason.TogglePlayed, CancellationToken.None);
|
||||||
|
|||||||
@@ -892,29 +892,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
private static BaseItem[] SortItemsByRequest(InternalItemsQuery query, IReadOnlyList<BaseItem> items)
|
private static BaseItem[] SortItemsByRequest(InternalItemsQuery query, IReadOnlyList<BaseItem> items)
|
||||||
{
|
{
|
||||||
var ids = query.ItemIds;
|
return items.OrderBy(i => Array.IndexOf(query.ItemIds, i.Id)).ToArray();
|
||||||
int size = items.Count;
|
|
||||||
|
|
||||||
// ids can potentially contain non-unique guids, but query result cannot,
|
|
||||||
// so we include only first occurrence of each guid
|
|
||||||
var positions = new Dictionary<Guid, int>(size);
|
|
||||||
int index = 0;
|
|
||||||
for (int i = 0; i < ids.Length; i++)
|
|
||||||
{
|
|
||||||
if (positions.TryAdd(ids[i], index))
|
|
||||||
{
|
|
||||||
index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var newItems = new BaseItem[size];
|
|
||||||
for (int i = 0; i < size; i++)
|
|
||||||
{
|
|
||||||
var item = items[i];
|
|
||||||
newItems[positions[item.Id]] = item;
|
|
||||||
}
|
|
||||||
|
|
||||||
return newItems;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
|
public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
|
||||||
|
|||||||
@@ -205,6 +205,16 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
public int? MinIndexNumber { get; set; }
|
public int? MinIndexNumber { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum ParentIndexNumber and IndexNumber.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// It produces this where clause:
|
||||||
|
/// <para>(ParentIndexNumber = X and IndexNumber >= Y) or ParentIndexNumber > X.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public (int ParentIndexNumber, int IndexNumber)? MinParentAndIndexNumber { get; set; }
|
||||||
|
|
||||||
public int? AiredDuringSeason { get; set; }
|
public int? AiredDuringSeason { get; set; }
|
||||||
|
|
||||||
public double? MinCriticRating { get; set; }
|
public double? MinCriticRating { get; set; }
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||||||
DtoOptions = options
|
DtoOptions = options
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!user.DisplayMissingEpisodes)
|
if (user == null || !user.DisplayMissingEpisodes)
|
||||||
{
|
{
|
||||||
query.IsMissing = false;
|
query.IsMissing = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -570,5 +570,13 @@ namespace MediaBrowser.Controller.Library
|
|||||||
Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason);
|
Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason);
|
||||||
|
|
||||||
BaseItem GetParentItem(Guid? parentId, Guid? userId);
|
BaseItem GetParentItem(Guid? parentId, Guid? userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue a library scan.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This exists so plugins can trigger a library scan.
|
||||||
|
/// </remarks>
|
||||||
|
void QueueLibraryScan();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Controller</PackageId>
|
<PackageId>Jellyfin.Controller</PackageId>
|
||||||
<VersionPrefix>10.8.1</VersionPrefix>
|
<VersionPrefix>10.8.8</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly ISubtitleEncoder _subtitleEncoder;
|
private readonly ISubtitleEncoder _subtitleEncoder;
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
|
private readonly Version _minKernelVersioni915Hang = new Version(5, 18);
|
||||||
|
|
||||||
private static readonly string[] _videoProfilesH264 = new[]
|
private static readonly string[] _videoProfilesH264 = new[]
|
||||||
{
|
{
|
||||||
@@ -930,6 +931,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
|
arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disable auto inserted SW scaler for HW decoders in case of changed resolution.
|
||||||
|
var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
|
||||||
|
if (!isSwDecoder)
|
||||||
|
{
|
||||||
|
arg.Append(" -autoscale 0");
|
||||||
|
}
|
||||||
|
|
||||||
return arg.ToString();
|
return arg.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1144,16 +1152,15 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
if (state.SubtitleStream.IsExternal)
|
if (state.SubtitleStream.IsExternal)
|
||||||
{
|
{
|
||||||
var subtitlePath = state.SubtitleStream.Path;
|
|
||||||
var charsetParam = string.Empty;
|
var charsetParam = string.Empty;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
|
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
|
||||||
{
|
{
|
||||||
var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
|
var charenc = _subtitleEncoder.GetSubtitleFileCharacterSet(
|
||||||
subtitlePath,
|
state.SubtitleStream,
|
||||||
state.SubtitleStream.Language,
|
state.SubtitleStream.Language,
|
||||||
state.MediaSource.Protocol,
|
state.MediaSource,
|
||||||
CancellationToken.None).GetAwaiter().GetResult();
|
CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(charenc))
|
if (!string.IsNullOrEmpty(charenc))
|
||||||
{
|
{
|
||||||
@@ -1165,7 +1172,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
return string.Format(
|
return string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"subtitles=f='{0}'{1}{2}{3}{4}{5}",
|
"subtitles=f='{0}'{1}{2}{3}{4}{5}",
|
||||||
_mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
|
_mediaEncoder.EscapeSubtitleFilterPath(state.SubtitleStream.Path),
|
||||||
charsetParam,
|
charsetParam,
|
||||||
alphaParam,
|
alphaParam,
|
||||||
sub2videoParam,
|
sub2videoParam,
|
||||||
@@ -1302,6 +1309,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
// which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
|
// which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
|
||||||
var intelLowPowerHwEncoding = false;
|
var intelLowPowerHwEncoding = false;
|
||||||
|
|
||||||
|
// Workaround for linux 5.18+ i915 hang at cost of performance.
|
||||||
|
// https://github.com/intel/media-driver/issues/1456
|
||||||
|
var enableWaFori915Hang = false;
|
||||||
|
|
||||||
if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
|
var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
|
||||||
@@ -1317,6 +1328,20 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
if (OperatingSystem.IsLinux() && Environment.OSVersion.Version >= _minKernelVersioni915Hang)
|
||||||
|
{
|
||||||
|
var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
|
||||||
|
var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
|
||||||
|
&& IsVaapiSupported(state)
|
||||||
|
&& IsOpenclFullSupported()
|
||||||
|
&& !IsVaapiVppTonemapAvailable(state, encodingOptions)
|
||||||
|
&& IsHwTonemapAvailable(state, encodingOptions);
|
||||||
|
|
||||||
|
enableWaFori915Hang = isIntelDecoder && doOclTonemap;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
|
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
|
||||||
@@ -1325,6 +1350,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
|
intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
enableWaFori915Hang = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intelLowPowerHwEncoding)
|
if (intelLowPowerHwEncoding)
|
||||||
@@ -1332,6 +1361,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
param += " -low_power 1";
|
param += " -low_power 1";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enableWaFori915Hang)
|
||||||
|
{
|
||||||
|
param += " -async_depth 1";
|
||||||
|
}
|
||||||
|
|
||||||
var isVc1 = string.Equals(state.VideoStream?.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
|
var isVc1 = string.Equals(state.VideoStream?.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
|
||||||
var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
|
var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -1381,7 +1415,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
param += " -preset 7";
|
param += " -preset 7";
|
||||||
}
|
}
|
||||||
|
|
||||||
param += " -look_ahead 0";
|
// Only h264_qsv has look_ahead option
|
||||||
|
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
param += " -look_ahead 0";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
|
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
|
||||||
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc)
|
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc)
|
||||||
@@ -1419,7 +1457,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
param += " -preset p4";
|
param += " -preset p1";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1713,6 +1751,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
// Can't stream copy if we're burning in subtitles
|
// Can't stream copy if we're burning in subtitles
|
||||||
if (request.SubtitleStreamIndex.HasValue
|
if (request.SubtitleStreamIndex.HasValue
|
||||||
|
&& request.SubtitleStreamIndex.Value >= 0
|
||||||
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
|
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -1760,7 +1799,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
|
|
||||||
var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
|
var requestedRangeTypes = state.GetRequestedRangeTypes(videoStream.Codec);
|
||||||
if (requestedProfiles.Length > 0)
|
if (requestedRangeTypes.Length > 0)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(videoStream.VideoRangeType))
|
if (string.IsNullOrEmpty(videoStream.VideoRangeType))
|
||||||
{
|
{
|
||||||
@@ -2026,6 +2065,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
|
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
|
|| string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
@@ -3367,6 +3408,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
// map from d3d11va to qsv.
|
// map from d3d11va to qsv.
|
||||||
mainFilters.Add("hwmap=derive_device=qsv");
|
mainFilters.Add("hwmap=derive_device=qsv");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Insert a qsv scaler to sync the decoder surface,
|
||||||
|
// msdk will passthrough this internally.
|
||||||
|
mainFilters.Add("hwmap=derive_device=qsv,scale_qsv");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hw deint
|
// hw deint
|
||||||
@@ -4503,7 +4550,9 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
if (isD3d11Supported && isCodecAvailable)
|
if (isD3d11Supported && isCodecAvailable)
|
||||||
{
|
{
|
||||||
return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
|
// set -threads 3 to intel d3d11va decoder explicitly. Lower threads may result in dead lock.
|
||||||
|
// on newer devices such as Xe, the larger the init_pool_size, the longer the initialization time for opencl to derive from d3d11.
|
||||||
|
return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -4937,14 +4986,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
// The default value of -probesize is more than enough, so leave it as is.
|
// The default value of -probesize is more than enough, so leave it as is.
|
||||||
var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
|
var ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
|
if (state.MediaSource.AnalyzeDurationMs > 0)
|
||||||
{
|
|
||||||
analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
|
|
||||||
}
|
|
||||||
else if (state.MediaSource.AnalyzeDurationMs.HasValue)
|
|
||||||
{
|
{
|
||||||
analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToString(CultureInfo.InvariantCulture);
|
analyzeDurationArgument = "-analyzeduration " + (state.MediaSource.AnalyzeDurationMs.Value * 1000).ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
|
||||||
|
{
|
||||||
|
analyzeDurationArgument = "-analyzeduration " + ffmpegAnalyzeDuration;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(analyzeDurationArgument))
|
if (!string.IsNullOrEmpty(analyzeDurationArgument))
|
||||||
{
|
{
|
||||||
@@ -4967,7 +5016,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
if (state.InputProtocol == MediaProtocol.Rtsp)
|
if (state.InputProtocol == MediaProtocol.Rtsp)
|
||||||
{
|
{
|
||||||
inputModifier += " -rtsp_transport tcp -rtsp_transport udp -rtsp_flags prefer_tcp";
|
inputModifier += " -rtsp_transport tcp+udp -rtsp_flags prefer_tcp";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(state.InputAudioSync))
|
if (!string.IsNullOrEmpty(state.InputAudioSync))
|
||||||
@@ -5496,7 +5545,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
|
if (string.Equals(currentMediaStream.Path, streamToFind.Path, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
/// <returns>The version of encoder.</returns>
|
/// <returns>The version of encoder.</returns>
|
||||||
Version EncoderVersion { get; }
|
Version EncoderVersion { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether p key pausing is supported.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if p key pausing is supported, <c>false</c> otherwise.</value>
|
||||||
|
bool IsPkeyPauseSupported { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver).
|
/// Gets a value indicating whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ using System.IO;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.Dto;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.MediaEncoding
|
namespace MediaBrowser.Controller.MediaEncoding
|
||||||
{
|
{
|
||||||
@@ -37,11 +38,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the subtitle language encoding parameter.
|
/// Gets the subtitle language encoding parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="subtitleStream">The subtitle stream.</param>
|
||||||
/// <param name="language">The language.</param>
|
/// <param name="language">The language.</param>
|
||||||
/// <param name="protocol">The protocol.</param>
|
/// <param name="mediaSource">The media source.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken);
|
Task<string> GetSubtitleFileCharacterSet(MediaStream subtitleStream, string language, MediaSourceInfo mediaSource, CancellationToken cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Http;
|
|||||||
|
|
||||||
namespace MediaBrowser.Controller.Net
|
namespace MediaBrowser.Controller.Net
|
||||||
{
|
{
|
||||||
public interface IWebSocketConnection
|
public interface IWebSocketConnection : IAsyncDisposable, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when [closed].
|
/// Occurs when [closed].
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
@@ -17,7 +18,7 @@ namespace MediaBrowser.Controller.Session
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class SessionInfo.
|
/// Class SessionInfo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SessionInfo : IDisposable
|
public sealed class SessionInfo : IAsyncDisposable, IDisposable
|
||||||
{
|
{
|
||||||
// 1 second
|
// 1 second
|
||||||
private const long ProgressIncrement = 10000000;
|
private const long ProgressIncrement = 10000000;
|
||||||
@@ -380,10 +381,28 @@ namespace MediaBrowser.Controller.Session
|
|||||||
{
|
{
|
||||||
if (controller is IDisposable disposable)
|
if (controller is IDisposable disposable)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Disposing session controller {0}", disposable.GetType().Name);
|
_logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name);
|
||||||
disposable.Dispose();
|
disposable.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
StopAutomaticProgress();
|
||||||
|
|
||||||
|
var controllers = SessionControllers.ToList();
|
||||||
|
|
||||||
|
foreach (var controller in controllers)
|
||||||
|
{
|
||||||
|
if (controller is IAsyncDisposable disposableAsync)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name);
|
||||||
|
await disposableAsync.DisposeAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
string output;
|
string output;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
output = GetProcessOutput(_encoderPath, "-version", false);
|
output = GetProcessOutput(_encoderPath, "-version", false, null);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -234,7 +234,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
string output;
|
string output;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
output = GetProcessOutput(_encoderPath, "-version", false);
|
output = GetProcessOutput(_encoderPath, "-version", false, null);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -341,7 +341,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true);
|
var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true, null);
|
||||||
return output.Contains(driverName, StringComparison.Ordinal);
|
return output.Contains(driverName, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -356,7 +356,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
string? output = null;
|
string? output = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
output = GetProcessOutput(_encoderPath, "-hwaccels", false);
|
output = GetProcessOutput(_encoderPath, "-hwaccels", false, null);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -384,7 +384,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
string output;
|
string output;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false);
|
output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false, null);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -402,13 +402,34 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CheckSupportedRuntimeKey(string keyDesc)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(keyDesc))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
string output;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
output = GetProcessOutput(_encoderPath, "-hide_banner -f lavfi -i nullsrc=s=1x1:d=500 -f null -", true, "?");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error checking supported runtime key");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.Contains(keyDesc, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
private IEnumerable<string> GetCodecs(Codec codec)
|
private IEnumerable<string> GetCodecs(Codec codec)
|
||||||
{
|
{
|
||||||
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
|
string codecstr = codec == Codec.Encoder ? "encoders" : "decoders";
|
||||||
string output;
|
string output;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
output = GetProcessOutput(_encoderPath, "-" + codecstr, false);
|
output = GetProcessOutput(_encoderPath, "-" + codecstr, false, null);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -439,7 +460,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
string output;
|
string output;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
output = GetProcessOutput(_encoderPath, "-filters", false);
|
output = GetProcessOutput(_encoderPath, "-filters", false, null);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -477,7 +498,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetProcessOutput(string path, string arguments, bool readStdErr)
|
private string GetProcessOutput(string path, string arguments, bool readStdErr, string? testKey)
|
||||||
{
|
{
|
||||||
using (var process = new Process()
|
using (var process = new Process()
|
||||||
{
|
{
|
||||||
@@ -487,6 +508,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
ErrorDialog = false,
|
ErrorDialog = false,
|
||||||
|
RedirectStandardInput = !string.IsNullOrEmpty(testKey),
|
||||||
RedirectStandardOutput = true,
|
RedirectStandardOutput = true,
|
||||||
RedirectStandardError = true
|
RedirectStandardError = true
|
||||||
}
|
}
|
||||||
@@ -496,6 +518,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
process.Start();
|
process.Start();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(testKey))
|
||||||
|
{
|
||||||
|
process.StandardInput.Write(testKey);
|
||||||
|
}
|
||||||
|
|
||||||
return readStdErr ? process.StandardError.ReadToEnd() : process.StandardOutput.ReadToEnd();
|
return readStdErr ? process.StandardError.ReadToEnd() : process.StandardOutput.ReadToEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
private List<string> _filters = new List<string>();
|
private List<string> _filters = new List<string>();
|
||||||
private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
|
private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
|
||||||
|
|
||||||
|
private bool _isPkeyPauseSupported = false;
|
||||||
|
|
||||||
private bool _isVaapiDeviceAmd = false;
|
private bool _isVaapiDeviceAmd = false;
|
||||||
private bool _isVaapiDeviceInteliHD = false;
|
private bool _isVaapiDeviceInteliHD = false;
|
||||||
private bool _isVaapiDeviceInteli965 = false;
|
private bool _isVaapiDeviceInteli965 = false;
|
||||||
@@ -100,6 +102,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
public Version EncoderVersion => _ffmpegVersion;
|
public Version EncoderVersion => _ffmpegVersion;
|
||||||
|
|
||||||
|
public bool IsPkeyPauseSupported => _isPkeyPauseSupported;
|
||||||
|
|
||||||
public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd;
|
public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd;
|
||||||
|
|
||||||
public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD;
|
public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD;
|
||||||
@@ -154,6 +158,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
|
|
||||||
_threads = EncodingHelper.GetNumberOfThreads(null, options, null);
|
_threads = EncodingHelper.GetNumberOfThreads(null, options, null);
|
||||||
|
|
||||||
|
_isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding");
|
||||||
|
|
||||||
// Check the Vaapi device vendor
|
// Check the Vaapi device vendor
|
||||||
if (OperatingSystem.IsLinux()
|
if (OperatingSystem.IsLinux()
|
||||||
&& SupportsHwaccel("vaapi")
|
&& SupportsHwaccel("vaapi")
|
||||||
@@ -376,15 +382,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
string analyzeDuration = string.Empty;
|
string analyzeDuration = string.Empty;
|
||||||
string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
|
string ffmpegAnalyzeDuration = _config.GetFFmpegAnalyzeDuration() ?? string.Empty;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
|
if (request.MediaSource.AnalyzeDurationMs > 0)
|
||||||
{
|
|
||||||
analyzeDuration = "-analyzeduration " + ffmpegAnalyzeDuration;
|
|
||||||
}
|
|
||||||
else if (request.MediaSource.AnalyzeDurationMs > 0)
|
|
||||||
{
|
{
|
||||||
analyzeDuration = "-analyzeduration " +
|
analyzeDuration = "-analyzeduration " +
|
||||||
(request.MediaSource.AnalyzeDurationMs * 1000).ToString();
|
(request.MediaSource.AnalyzeDurationMs * 1000).ToString();
|
||||||
}
|
}
|
||||||
|
else if (!string.IsNullOrEmpty(ffmpegAnalyzeDuration))
|
||||||
|
{
|
||||||
|
analyzeDuration = "-analyzeduration " + ffmpegAnalyzeDuration;
|
||||||
|
}
|
||||||
|
|
||||||
var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File;
|
var forceEnableLogging = request.MediaSource.Protocol != MediaProtocol.File;
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<PackageReference Include="libse" Version="3.6.5" />
|
<PackageReference Include="libse" Version="3.6.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0" />
|
||||||
<PackageReference Include="UTF.Unknown" Version="2.5.0" />
|
<PackageReference Include="UTF.Unknown" Version="2.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
|
|||||||
@@ -863,8 +863,13 @@ namespace MediaBrowser.MediaEncoding.Probing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (string.Equals(streamInfo.CodecType, "data", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
stream.Type = MediaStreamType.Data;
|
||||||
|
}
|
||||||
else
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
// Convert
|
// Convert
|
||||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt");
|
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, ".srt");
|
||||||
|
|
||||||
await ConvertTextSubtitleToSrt(subtitleStream.Path, subtitleStream.Language, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
|
await ConvertTextSubtitleToSrt(subtitleStream, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return new SubtitleInfo(outputPath, MediaProtocol.File, "srt", true);
|
return new SubtitleInfo(outputPath, MediaProtocol.File, "srt", true);
|
||||||
}
|
}
|
||||||
@@ -351,13 +351,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts the text subtitle to SRT.
|
/// Converts the text subtitle to SRT.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="inputPath">The input path.</param>
|
/// <param name="subtitleStream">The subtitle stream.</param>
|
||||||
/// <param name="language">The language.</param>
|
|
||||||
/// <param name="mediaSource">The input mediaSource.</param>
|
/// <param name="mediaSource">The input mediaSource.</param>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
private async Task ConvertTextSubtitleToSrt(string inputPath, string language, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken)
|
private async Task ConvertTextSubtitleToSrt(MediaStream subtitleStream, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var semaphore = GetLock(outputPath);
|
var semaphore = GetLock(outputPath);
|
||||||
|
|
||||||
@@ -367,7 +366,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
{
|
{
|
||||||
if (!File.Exists(outputPath))
|
if (!File.Exists(outputPath))
|
||||||
{
|
{
|
||||||
await ConvertTextSubtitleToSrtInternal(inputPath, language, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
|
await ConvertTextSubtitleToSrtInternal(subtitleStream, mediaSource, outputPath, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -379,8 +378,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts the text subtitle to SRT internal.
|
/// Converts the text subtitle to SRT internal.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="inputPath">The input path.</param>
|
/// <param name="subtitleStream">The subtitle stream.</param>
|
||||||
/// <param name="language">The language.</param>
|
|
||||||
/// <param name="mediaSource">The input mediaSource.</param>
|
/// <param name="mediaSource">The input mediaSource.</param>
|
||||||
/// <param name="outputPath">The output path.</param>
|
/// <param name="outputPath">The output path.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
@@ -388,8 +386,9 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
/// <exception cref="ArgumentNullException">
|
/// <exception cref="ArgumentNullException">
|
||||||
/// The <c>inputPath</c> or <c>outputPath</c> is <c>null</c>.
|
/// The <c>inputPath</c> or <c>outputPath</c> is <c>null</c>.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
private async Task ConvertTextSubtitleToSrtInternal(string inputPath, string language, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken)
|
private async Task ConvertTextSubtitleToSrtInternal(MediaStream subtitleStream, MediaSourceInfo mediaSource, string outputPath, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var inputPath = subtitleStream.Path;
|
||||||
if (string.IsNullOrEmpty(inputPath))
|
if (string.IsNullOrEmpty(inputPath))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(inputPath));
|
throw new ArgumentNullException(nameof(inputPath));
|
||||||
@@ -402,7 +401,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)));
|
Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath)));
|
||||||
|
|
||||||
var encodingParam = await GetSubtitleFileCharacterSet(inputPath, language, mediaSource.Protocol, cancellationToken).ConfigureAwait(false);
|
var encodingParam = await GetSubtitleFileCharacterSet(subtitleStream, subtitleStream.Language, mediaSource, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// FFmpeg automatically convert character encoding when it is UTF-16
|
// FFmpeg automatically convert character encoding when it is UTF-16
|
||||||
// If we specify character encoding, it rejects with "do not specify a character encoding" and "Unable to recode subtitle event"
|
// If we specify character encoding, it rejects with "do not specify a character encoding" and "Unable to recode subtitle event"
|
||||||
@@ -420,18 +419,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
int exitCode;
|
int exitCode;
|
||||||
|
|
||||||
using (var process = new Process
|
using (var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
StartInfo = new ProcessStartInfo
|
CreateNoWindow = true,
|
||||||
{
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true,
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
UseShellExecute = false,
|
Arguments = string.Format(CultureInfo.InvariantCulture, "{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
Arguments = string.Format(CultureInfo.InvariantCulture, "{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
ErrorDialog = false
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
},
|
||||||
ErrorDialog = false
|
EnableRaisingEvents = true
|
||||||
},
|
})
|
||||||
EnableRaisingEvents = true
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
|
|
||||||
@@ -571,7 +570,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
|
|
||||||
var processArgs = string.Format(
|
var processArgs = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"-i {0} -map 0:{1} -an -vn -c:s {2} \"{3}\"",
|
"-i {0} -copyts -map 0:{1} -an -vn -c:s {2} \"{3}\"",
|
||||||
inputPath,
|
inputPath,
|
||||||
subtitleStreamIndex,
|
subtitleStreamIndex,
|
||||||
outputCodec,
|
outputCodec,
|
||||||
@@ -580,18 +579,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
int exitCode;
|
int exitCode;
|
||||||
|
|
||||||
using (var process = new Process
|
using (var process = new Process
|
||||||
|
{
|
||||||
|
StartInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
StartInfo = new ProcessStartInfo
|
CreateNoWindow = true,
|
||||||
{
|
UseShellExecute = false,
|
||||||
CreateNoWindow = true,
|
FileName = _mediaEncoder.EncoderPath,
|
||||||
UseShellExecute = false,
|
Arguments = processArgs,
|
||||||
FileName = _mediaEncoder.EncoderPath,
|
WindowStyle = ProcessWindowStyle.Hidden,
|
||||||
Arguments = processArgs,
|
ErrorDialog = false
|
||||||
WindowStyle = ProcessWindowStyle.Hidden,
|
},
|
||||||
ErrorDialog = false
|
EnableRaisingEvents = true
|
||||||
},
|
})
|
||||||
EnableRaisingEvents = true
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||||
|
|
||||||
@@ -606,7 +605,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
|
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!ranToCompletion)
|
if (!ranToCompletion)
|
||||||
{
|
{
|
||||||
@@ -729,9 +728,19 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<string> GetSubtitleFileCharacterSet(string path, string language, MediaProtocol protocol, CancellationToken cancellationToken)
|
public async Task<string> GetSubtitleFileCharacterSet(MediaStream subtitleStream, string language, MediaSourceInfo mediaSource, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using (var stream = await GetStream(path, protocol, cancellationToken).ConfigureAwait(false))
|
var subtitleCodec = subtitleStream.Codec;
|
||||||
|
var path = subtitleStream.Path;
|
||||||
|
|
||||||
|
if (path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
path = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + subtitleCodec);
|
||||||
|
await ExtractTextSubtitle(mediaSource, subtitleStream, subtitleCodec, path, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var stream = await GetStream(path, mediaSource.Protocol, cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName ?? string.Empty;
|
var charset = CharsetDetector.DetectFromStream(stream).Detected?.EncodingName ?? string.Empty;
|
||||||
|
|
||||||
@@ -754,12 +763,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
switch (protocol)
|
switch (protocol)
|
||||||
{
|
{
|
||||||
case MediaProtocol.Http:
|
case MediaProtocol.Http:
|
||||||
{
|
{
|
||||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
.GetAsync(new Uri(path), cancellationToken)
|
.GetAsync(new Uri(path), cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
return await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
case MediaProtocol.File:
|
case MediaProtocol.File:
|
||||||
return AsyncFile.OpenRead(path);
|
return AsyncFile.OpenRead(path);
|
||||||
|
|||||||
@@ -436,9 +436,9 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
{
|
{
|
||||||
containerSupported = true;
|
containerSupported = true;
|
||||||
|
|
||||||
videoSupported = videoStream != null && profile.SupportsVideoCodec(videoStream.Codec);
|
videoSupported = videoStream == null || profile.SupportsVideoCodec(videoStream.Codec);
|
||||||
|
|
||||||
audioSupported = audioStream != null && profile.SupportsAudioCodec(audioStream.Codec);
|
audioSupported = audioStream == null || profile.SupportsAudioCodec(audioStream.Codec);
|
||||||
|
|
||||||
if (videoSupported && audioSupported)
|
if (videoSupported && audioSupported)
|
||||||
{
|
{
|
||||||
@@ -447,18 +447,17 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<TranscodeReason>();
|
|
||||||
if (!containerSupported)
|
if (!containerSupported)
|
||||||
{
|
{
|
||||||
reasons |= TranscodeReason.ContainerNotSupported;
|
reasons |= TranscodeReason.ContainerNotSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoStream != null && !videoSupported)
|
if (!videoSupported)
|
||||||
{
|
{
|
||||||
reasons |= TranscodeReason.VideoCodecNotSupported;
|
reasons |= TranscodeReason.VideoCodecNotSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (audioStream != null && !audioSupported)
|
if (!audioSupported)
|
||||||
{
|
{
|
||||||
reasons |= TranscodeReason.AudioCodecNotSupported;
|
reasons |= TranscodeReason.AudioCodecNotSupported;
|
||||||
}
|
}
|
||||||
@@ -590,21 +589,19 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect candidate audio streams
|
// Collect candidate audio streams
|
||||||
IEnumerable<MediaStream> candidateAudioStreams = audioStream == null ? Array.Empty<MediaStream>() : new[] { audioStream };
|
ICollection<MediaStream> candidateAudioStreams = audioStream == null ? Array.Empty<MediaStream>() : new[] { audioStream };
|
||||||
if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0)
|
if (!options.AudioStreamIndex.HasValue || options.AudioStreamIndex < 0)
|
||||||
{
|
{
|
||||||
if (audioStream?.IsDefault == true)
|
if (audioStream?.IsDefault == true)
|
||||||
{
|
{
|
||||||
candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.IsDefault);
|
candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.IsDefault).ToArray();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.Language == audioStream?.Language);
|
candidateAudioStreams = item.MediaStreams.Where(stream => stream.Type == MediaStreamType.Audio && stream.Language == audioStream?.Language).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
candidateAudioStreams = candidateAudioStreams.ToArray();
|
|
||||||
|
|
||||||
var videoStream = item.VideoStream;
|
var videoStream = item.VideoStream;
|
||||||
|
|
||||||
var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
|
var directPlayBitrateEligibility = IsBitrateEligibleForDirectPlayback(item, options.GetMaxBitrate(false) ?? 0, options, PlayMethod.DirectPlay);
|
||||||
@@ -1060,7 +1057,7 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
MediaSourceInfo mediaSource,
|
MediaSourceInfo mediaSource,
|
||||||
MediaStream videoStream,
|
MediaStream videoStream,
|
||||||
MediaStream audioStream,
|
MediaStream audioStream,
|
||||||
IEnumerable<MediaStream> candidateAudioStreams,
|
ICollection<MediaStream> candidateAudioStreams,
|
||||||
MediaStream subtitleStream,
|
MediaStream subtitleStream,
|
||||||
bool isEligibleForDirectPlay,
|
bool isEligibleForDirectPlay,
|
||||||
bool isEligibleForDirectStream)
|
bool isEligibleForDirectStream)
|
||||||
@@ -1182,14 +1179,18 @@ namespace MediaBrowser.Model.Dlna
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check audio codec
|
// Check audio codec
|
||||||
var selectedAudioStream = candidateAudioStreams.FirstOrDefault(audioStream => directPlayProfile.SupportsAudioCodec(audioStream.Codec));
|
MediaStream selectedAudioStream = null;
|
||||||
if (selectedAudioStream == null)
|
if (candidateAudioStreams.Any())
|
||||||
{
|
{
|
||||||
directPlayProfileReasons |= TranscodeReason.AudioCodecNotSupported;
|
selectedAudioStream = candidateAudioStreams.FirstOrDefault(audioStream => directPlayProfile.SupportsAudioCodec(audioStream.Codec));
|
||||||
}
|
if (selectedAudioStream == null)
|
||||||
else
|
{
|
||||||
{
|
directPlayProfileReasons |= TranscodeReason.AudioCodecNotSupported;
|
||||||
audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
audioCodecProfileReasons = audioStreamMatches.GetValueOrDefault(selectedAudioStream);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var failureReasons = directPlayProfileReasons | containerProfileReasons | subtitleProfileReasons;
|
var failureReasons = directPlayProfileReasons | containerProfileReasons | subtitleProfileReasons;
|
||||||
|
|||||||
@@ -588,15 +588,26 @@ namespace MediaBrowser.Model.Entities
|
|||||||
|
|
||||||
return Width switch
|
return Width switch
|
||||||
{
|
{
|
||||||
<= 720 when Height <= 480 => IsInterlaced ? "480i" : "480p",
|
// 256x144 (16:9 square pixel format)
|
||||||
// 720x576 (PAL) (768 when rescaled for square pixels)
|
<= 256 when Height <= 144 => IsInterlaced ? "144i" : "144p",
|
||||||
<= 768 when Height <= 576 => IsInterlaced ? "576i" : "576p",
|
// 426x240 (16:9 square pixel format)
|
||||||
// 960x540 (sometimes 544 which is multiple of 16)
|
<= 426 when Height <= 240 => IsInterlaced ? "240i" : "240p",
|
||||||
|
// 640x360 (16:9 square pixel format)
|
||||||
|
<= 640 when Height <= 360 => IsInterlaced ? "360i" : "360p",
|
||||||
|
// 682x384 (16:9 square pixel format)
|
||||||
|
<= 682 when Height <= 384 => IsInterlaced ? "384i" : "384p",
|
||||||
|
// 720x404 (16:9 square pixel format)
|
||||||
|
<= 720 when Height <= 404 => IsInterlaced ? "404i" : "404p",
|
||||||
|
// 854x480 (16:9 square pixel format)
|
||||||
|
<= 854 when Height <= 480 => IsInterlaced ? "480i" : "480p",
|
||||||
|
// 960x544 (16:9 square pixel format)
|
||||||
<= 960 when Height <= 544 => IsInterlaced ? "540i" : "540p",
|
<= 960 when Height <= 544 => IsInterlaced ? "540i" : "540p",
|
||||||
|
// 1024x576 (16:9 square pixel format)
|
||||||
|
<= 1024 when Height <= 576 => IsInterlaced ? "576i" : "576p",
|
||||||
// 1280x720
|
// 1280x720
|
||||||
<= 1280 when Height <= 962 => IsInterlaced ? "720i" : "720p",
|
<= 1280 when Height <= 962 => IsInterlaced ? "720i" : "720p",
|
||||||
// 1920x1080
|
// 2560x1080 (FHD ultra wide 21:9) using 1440px width to accomodate WQHD
|
||||||
<= 1920 when Height <= 1440 => IsInterlaced ? "1080i" : "1080p",
|
<= 2560 when Height <= 1440 => IsInterlaced ? "1080i" : "1080p",
|
||||||
// 4K
|
// 4K
|
||||||
<= 4096 when Height <= 3072 => "4K",
|
<= 4096 when Height <= 3072 => "4K",
|
||||||
// 8K
|
// 8K
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ namespace MediaBrowser.Model.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The embedded image.
|
/// The embedded image.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
EmbeddedImage
|
EmbeddedImage,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The data.
|
||||||
|
/// </summary>
|
||||||
|
Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Model</PackageId>
|
<PackageId>Jellyfin.Model</PackageId>
|
||||||
<VersionPrefix>10.8.1</VersionPrefix>
|
<VersionPrefix>10.8.8</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -34,13 +34,13 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
|
||||||
<PackageReference Include="MimeTypes" Version="2.4.0">
|
<PackageReference Include="MimeTypes" Version="2.4.0">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Text.Json" Version="6.0.5" />
|
<PackageReference Include="System.Text.Json" Version="6.0.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -408,10 +408,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEnglishRequested)
|
item.Overview = result.Plot;
|
||||||
{
|
|
||||||
item.Overview = result.Plot;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Plugin.Instance.Configuration.CastAndCrew)
|
if (!Plugin.Instance.Configuration.CastAndCrew)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
@@ -40,6 +41,7 @@ namespace MediaBrowser.Providers.TV
|
|||||||
{
|
{
|
||||||
await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
|
await base.AfterMetadataRefresh(item, refreshOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
RemoveObsoleteEpisodes(item);
|
||||||
RemoveObsoleteSeasons(item);
|
RemoveObsoleteSeasons(item);
|
||||||
await FillInMissingSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
|
await FillInMissingSeasonsAsync(item, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -121,6 +123,61 @@ namespace MediaBrowser.Providers.TV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RemoveObsoleteEpisodes(Series series)
|
||||||
|
{
|
||||||
|
var episodes = series.GetEpisodes(null, new DtoOptions()).OfType<Episode>().ToList();
|
||||||
|
var numberOfEpisodes = episodes.Count;
|
||||||
|
// TODO: O(n^2), but can it be done faster without overcomplicating it?
|
||||||
|
for (var i = 0; i < numberOfEpisodes; i++)
|
||||||
|
{
|
||||||
|
var currentEpisode = episodes[i];
|
||||||
|
// The outer loop only examines virtual episodes
|
||||||
|
if (!currentEpisode.IsVirtualItem)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Virtual episodes without an episode number are practically orphaned and should be deleted
|
||||||
|
if (!currentEpisode.IndexNumber.HasValue)
|
||||||
|
{
|
||||||
|
DeleteEpisode(currentEpisode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = i + 1; j < numberOfEpisodes; j++)
|
||||||
|
{
|
||||||
|
var comparisonEpisode = episodes[j];
|
||||||
|
// The inner loop is only for "physical" episodes
|
||||||
|
if (comparisonEpisode.IsVirtualItem
|
||||||
|
|| currentEpisode.ParentIndexNumber != comparisonEpisode.ParentIndexNumber
|
||||||
|
|| !comparisonEpisode.ContainsEpisodeNumber(currentEpisode.IndexNumber.Value))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteEpisode(currentEpisode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteEpisode(Episode episode)
|
||||||
|
{
|
||||||
|
Logger.LogInformation(
|
||||||
|
"Removing virtual episode S{SeasonNumber}E{EpisodeNumber} in series {SeriesName}",
|
||||||
|
episode.ParentIndexNumber,
|
||||||
|
episode.IndexNumber,
|
||||||
|
episode.SeriesName);
|
||||||
|
|
||||||
|
LibraryManager.DeleteItem(
|
||||||
|
episode,
|
||||||
|
new DeleteOptions
|
||||||
|
{
|
||||||
|
DeleteFileLocation = true
|
||||||
|
},
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates seasons for all episodes that aren't in a season folder.
|
/// Creates seasons for all episodes that aren't in a season folder.
|
||||||
/// If no season number can be determined, a dummy season will be created.
|
/// If no season number can be determined, a dummy season will be created.
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("10.8.1")]
|
[assembly: AssemblyVersion("10.8.8")]
|
||||||
[assembly: AssemblyFileVersion("10.8.1")]
|
[assembly: AssemblyFileVersion("10.8.8")]
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
# We just wrap `build` so this is really it
|
# We just wrap `build` so this is really it
|
||||||
name: "jellyfin"
|
name: "jellyfin"
|
||||||
version: "10.8.1"
|
version: "10.8.8"
|
||||||
packages:
|
packages:
|
||||||
- debian.amd64
|
- debian.amd64
|
||||||
- debian.arm64
|
- debian.arm64
|
||||||
|
|||||||
Vendored
+42
@@ -1,3 +1,45 @@
|
|||||||
|
jellyfin-server (10.8.8-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.8
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Tue, 29 Nov 2022 13:42:47 -0500
|
||||||
|
|
||||||
|
jellyfin-server (10.8.7-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 10.8.7; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.7
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 31 Oct 2022 23:07:06 -0400
|
||||||
|
|
||||||
|
jellyfin-server (10.8.6-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 10.8.6; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.6
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Fri, 28 Oct 2022 22:41:01 -0400
|
||||||
|
|
||||||
|
jellyfin-server (10.8.5-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 10.8.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.5
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 24 Sep 2022 22:01:56 -0400
|
||||||
|
|
||||||
|
jellyfin-server (10.8.4-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 10.8.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.4
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Sat, 13 Aug 2022 21:51:45 -0400
|
||||||
|
|
||||||
|
jellyfin-server (10.8.3-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 10.8.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.3
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 01 Aug 2022 20:19:48 -0400
|
||||||
|
|
||||||
|
jellyfin-server (10.8.2-1) unstable; urgency=medium
|
||||||
|
|
||||||
|
* New upstream version 10.8.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.2
|
||||||
|
|
||||||
|
-- Jellyfin Packaging Team <packaging@jellyfin.org> Mon, 01 Aug 2022 14:27:27 -0400
|
||||||
|
|
||||||
jellyfin-server (10.8.1-1) unstable; urgency=medium
|
jellyfin-server (10.8.1-1) unstable; urgency=medium
|
||||||
|
|
||||||
* New upstream version 10.8.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.1
|
* New upstream version 10.8.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.1
|
||||||
|
|||||||
Vendored
-4
@@ -30,8 +30,4 @@ Defaults!RESTARTSERVER_INITD !requiretty
|
|||||||
Defaults!STARTSERVER_INITD !requiretty
|
Defaults!STARTSERVER_INITD !requiretty
|
||||||
Defaults!STOPSERVER_INITD !requiretty
|
Defaults!STOPSERVER_INITD !requiretty
|
||||||
|
|
||||||
#Allow the server to mount iso images
|
|
||||||
jellyfin ALL=(ALL) NOPASSWD: /bin/mount
|
|
||||||
jellyfin ALL=(ALL) NOPASSWD: /bin/umount
|
|
||||||
|
|
||||||
Defaults:jellyfin !requiretty
|
Defaults:jellyfin !requiretty
|
||||||
|
|||||||
Vendored
+1
-1
@@ -8,7 +8,7 @@ EnvironmentFile = /etc/default/jellyfin
|
|||||||
User = jellyfin
|
User = jellyfin
|
||||||
Group = jellyfin
|
Group = jellyfin
|
||||||
WorkingDirectory = /var/lib/jellyfin
|
WorkingDirectory = /var/lib/jellyfin
|
||||||
ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} ${JELLYFIN_ADDITIONAL_OPTS}
|
ExecStart = /usr/bin/jellyfin $JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT $JELLYFIN_ADDITIONAL_OPTS
|
||||||
Restart = on-failure
|
Restart = on-failure
|
||||||
TimeoutSec = 15
|
TimeoutSec = 15
|
||||||
SuccessExitStatus=0 143
|
SuccessExitStatus=0 143
|
||||||
|
|||||||
Vendored
+1
-1
@@ -5,7 +5,7 @@ Homepage: https://jellyfin.org
|
|||||||
Standards-Version: 3.9.2
|
Standards-Version: 3.9.2
|
||||||
|
|
||||||
Package: jellyfin
|
Package: jellyfin
|
||||||
Version: 10.8.1
|
Version: 10.8.8
|
||||||
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
|
Maintainer: Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
Depends: jellyfin-server, jellyfin-web
|
Depends: jellyfin-server, jellyfin-web
|
||||||
Description: Provides the Jellyfin Free Software Media System
|
Description: Provides the Jellyfin Free Software Media System
|
||||||
|
|||||||
Vendored
+1
-1
@@ -40,7 +40,7 @@ override_dh_clistrip:
|
|||||||
|
|
||||||
override_dh_auto_build:
|
override_dh_auto_build:
|
||||||
dotnet publish -maxcpucount:1 --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
|
dotnet publish -maxcpucount:1 --configuration $(CONFIG) --output='$(CURDIR)/usr/lib/jellyfin/bin' --self-contained --runtime $(DOTNETRUNTIME) \
|
||||||
"-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
|
-p:DebugSymbols=false -p:DebugType=none Jellyfin.Server
|
||||||
|
|
||||||
override_dh_auto_clean:
|
override_dh_auto_clean:
|
||||||
dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true
|
dotnet clean -maxcpucount:1 --configuration $(CONFIG) Jellyfin.Server || true
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ RUN yum update -yq \
|
|||||||
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
|
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
|
||||||
|
|
||||||
# Install DotNET SDK
|
# Install DotNET SDK
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b/dotnet-sdk-6.0.301-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
|||||||
|
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
|||||||
|
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
|
||||||
|
|||||||
@@ -10,4 +10,4 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
|||||||
|
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="${ARTIFACT_DIR}" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ RUN dnf update -yq \
|
|||||||
&& dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make
|
&& dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget make
|
||||||
|
|
||||||
# Install DotNET SDK
|
# Install DotNET SDK
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b/dotnet-sdk-6.0.301-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ RUN apt-get update -yqq \
|
|||||||
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
|
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
|
||||||
|
|
||||||
# Install dotnet repository
|
# Install dotnet repository
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b/dotnet-sdk-6.0.301-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
|
|||||||
mmv build-essential lsb-release
|
mmv build-essential lsb-release
|
||||||
|
|
||||||
# Install dotnet repository
|
# Install dotnet repository
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b/dotnet-sdk-6.0.301-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
|
|||||||
mmv build-essential lsb-release
|
mmv build-essential lsb-release
|
||||||
|
|
||||||
# Install dotnet repository
|
# Install dotnet repository
|
||||||
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/77d472e5-194c-421e-992d-e4ca1d08e6cc/56c61ac303ddf1b12026151f4f000a2b/dotnet-sdk-6.0.301-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
RUN wget -q https://download.visualstudio.microsoft.com/download/pr/8159607a-e686-4ead-ac99-b4c97290a5fd/ec6070b1b2cc0651ebe57cf1bd411315/dotnet-sdk-6.0.401-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
|
||||||
&& mkdir -p dotnet-sdk \
|
&& mkdir -p dotnet-sdk \
|
||||||
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
|
||||||
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Build archives
|
# Build archives
|
||||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
|
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
|
||||||
tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
|
tar -czf jellyfin-server_${version}_linux-amd64.tar.gz -C dist jellyfin-server_${version}
|
||||||
rm -rf dist/jellyfin-server_${version}
|
rm -rf dist/jellyfin-server_${version}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Build archives
|
# Build archives
|
||||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
|
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-musl-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
|
||||||
tar -czf jellyfin-server_${version}_linux-amd64-musl.tar.gz -C dist jellyfin-server_${version}
|
tar -czf jellyfin-server_${version}_linux-amd64-musl.tar.gz -C dist jellyfin-server_${version}
|
||||||
rm -rf dist/jellyfin-server_${version}
|
rm -rf dist/jellyfin-server_${version}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Build archives
|
# Build archives
|
||||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
|
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
|
||||||
tar -czf jellyfin-server_${version}_linux-arm64.tar.gz -C dist jellyfin-server_${version}
|
tar -czf jellyfin-server_${version}_linux-arm64.tar.gz -C dist jellyfin-server_${version}
|
||||||
rm -rf dist/jellyfin-server_${version}
|
rm -rf dist/jellyfin-server_${version}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Build archives
|
# Build archives
|
||||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
|
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime linux-arm --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
|
||||||
tar -czf jellyfin-server_${version}_linux-armhf.tar.gz -C dist jellyfin-server_${version}
|
tar -czf jellyfin-server_${version}_linux-armhf.tar.gz -C dist jellyfin-server_${version}
|
||||||
rm -rf dist/jellyfin-server_${version}
|
rm -rf dist/jellyfin-server_${version}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Build archives
|
# Build archives
|
||||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
|
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime osx-x64 --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
|
||||||
tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
|
tar -czf jellyfin-server_${version}_macos-amd64.tar.gz -C dist jellyfin-server_${version}
|
||||||
rm -rf dist/jellyfin-server_${version}
|
rm -rf dist/jellyfin-server_${version}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Build archives
|
# Build archives
|
||||||
dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=false"
|
dotnet publish Jellyfin.Server --configuration Release --output dist/jellyfin-server_${version}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=false
|
||||||
tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
|
tar -czf jellyfin-server_${version}_portable.tar.gz -C dist jellyfin-server_${version}
|
||||||
rm -rf dist/jellyfin-server_${version}
|
rm -rf dist/jellyfin-server_${version}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ fi
|
|||||||
output_dir="dist/jellyfin-server_${version}"
|
output_dir="dist/jellyfin-server_${version}"
|
||||||
|
|
||||||
# Build binary
|
# Build binary
|
||||||
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ "-p:DebugSymbols=false;DebugType=none;UseAppHost=true"
|
dotnet publish Jellyfin.Server --configuration Release --self-contained --runtime win-x64 --output ${output_dir}/ -p:DebugSymbols=false -p:DebugType=none -p:UseAppHost=true
|
||||||
|
|
||||||
# Prepare addins
|
# Prepare addins
|
||||||
addin_build_dir="$( mktemp -d )"
|
addin_build_dir="$( mktemp -d )"
|
||||||
|
|||||||
@@ -18,14 +18,6 @@ $ sudo dnf install https://download1.rpmfusion.org/free/fedora/rpmfusion-free-re
|
|||||||
$ sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm
|
$ sudo yum localinstall --nogpgcheck https://download1.rpmfusion.org/free/el/rpmfusion-free-release-7.noarch.rpm
|
||||||
```
|
```
|
||||||
|
|
||||||
## ISO mounting
|
|
||||||
|
|
||||||
To allow Jellyfin to mount/umount ISO files uncomment these two lines in `/etc/sudoers.d/jellyfin-sudoers`
|
|
||||||
```
|
|
||||||
# %jellyfin ALL=(ALL) NOPASSWD: /bin/mount
|
|
||||||
# %jellyfin ALL=(ALL) NOPASSWD: /bin/umount
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building with dotnet
|
## Building with dotnet
|
||||||
|
|
||||||
Jellyfin is build with `--self-contained` so no dotnet required for runtime.
|
Jellyfin is build with `--self-contained` so no dotnet required for runtime.
|
||||||
|
|||||||
@@ -5,3 +5,49 @@
|
|||||||
[Service]
|
[Service]
|
||||||
#User = jellyfin
|
#User = jellyfin
|
||||||
#EnvironmentFile = /etc/sysconfig/jellyfin
|
#EnvironmentFile = /etc/sysconfig/jellyfin
|
||||||
|
|
||||||
|
# Service hardening options
|
||||||
|
# These were added in PR #6953 to solve issue #6952, but some combination of
|
||||||
|
# them causes "restart.sh" functionality to break with the following error:
|
||||||
|
# sudo: effective uid is not 0, is /usr/bin/sudo on a file system with the
|
||||||
|
# 'nosuid' option set or an NFS file system without root privileges?
|
||||||
|
# See issue #7503 for details on the troubleshooting that went into this.
|
||||||
|
# Since these were added for NixOS specifically and are above and beyond
|
||||||
|
# what 99% of systemd units do, they have been moved here as optional
|
||||||
|
# additional flags to set for maximum system security and can be enabled at
|
||||||
|
# the administrator's or package maintainer's discretion.
|
||||||
|
# Uncomment these only if you know what you're doing, and doing so may cause
|
||||||
|
# bugs with in-server Restart and potentially other functionality as well.
|
||||||
|
#NoNewPrivileges=true
|
||||||
|
#SystemCallArchitectures=native
|
||||||
|
#RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||||
|
#RestrictNamespaces=false
|
||||||
|
#RestrictRealtime=true
|
||||||
|
#RestrictSUIDSGID=true
|
||||||
|
#ProtectClock=true
|
||||||
|
#ProtectControlGroups=false
|
||||||
|
#ProtectHostname=true
|
||||||
|
#ProtectKernelLogs=false
|
||||||
|
#ProtectKernelModules=false
|
||||||
|
#ProtectKernelTunables=false
|
||||||
|
#LockPersonality=true
|
||||||
|
#PrivateTmp=false
|
||||||
|
#PrivateDevices=false
|
||||||
|
#PrivateUsers=true
|
||||||
|
#RemoveIPC=true
|
||||||
|
#SystemCallFilter=~@clock
|
||||||
|
#SystemCallFilter=~@aio
|
||||||
|
#SystemCallFilter=~@chown
|
||||||
|
#SystemCallFilter=~@cpu-emulation
|
||||||
|
#SystemCallFilter=~@debug
|
||||||
|
#SystemCallFilter=~@keyring
|
||||||
|
#SystemCallFilter=~@memlock
|
||||||
|
#SystemCallFilter=~@module
|
||||||
|
#SystemCallFilter=~@mount
|
||||||
|
#SystemCallFilter=~@obsolete
|
||||||
|
#SystemCallFilter=~@privileged
|
||||||
|
#SystemCallFilter=~@raw-io
|
||||||
|
#SystemCallFilter=~@reboot
|
||||||
|
#SystemCallFilter=~@setuid
|
||||||
|
#SystemCallFilter=~@swap
|
||||||
|
#SystemCallErrorNumber=EPERM
|
||||||
|
|||||||
+1
-35
@@ -8,44 +8,10 @@ EnvironmentFile = /etc/sysconfig/jellyfin
|
|||||||
User = jellyfin
|
User = jellyfin
|
||||||
Group = jellyfin
|
Group = jellyfin
|
||||||
WorkingDirectory = /var/lib/jellyfin
|
WorkingDirectory = /var/lib/jellyfin
|
||||||
ExecStart = /usr/bin/jellyfin ${JELLYFIN_WEB_OPT} ${JELLYFIN_RESTART_OPT} ${JELLYFIN_FFMPEG_OPT} ${JELLYFIN_SERVICE_OPT} ${JELLYFIN_NOWEBAPP_OPT} ${JELLYFIN_ADDITIONAL_OPTS}
|
ExecStart = /usr/bin/jellyfin $JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT $JELLYFIN_ADDITIONAL_OPTS
|
||||||
Restart = on-failure
|
Restart = on-failure
|
||||||
TimeoutSec = 15
|
TimeoutSec = 15
|
||||||
SuccessExitStatus=0 143
|
SuccessExitStatus=0 143
|
||||||
|
|
||||||
NoNewPrivileges=true
|
|
||||||
SystemCallArchitectures=native
|
|
||||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
|
||||||
RestrictNamespaces=false
|
|
||||||
RestrictRealtime=true
|
|
||||||
RestrictSUIDSGID=true
|
|
||||||
ProtectClock=true
|
|
||||||
ProtectControlGroups=false
|
|
||||||
ProtectHostname=true
|
|
||||||
ProtectKernelLogs=false
|
|
||||||
ProtectKernelModules=false
|
|
||||||
ProtectKernelTunables=false
|
|
||||||
LockPersonality=true
|
|
||||||
PrivateTmp=false
|
|
||||||
PrivateDevices=false
|
|
||||||
PrivateUsers=true
|
|
||||||
RemoveIPC=true
|
|
||||||
SystemCallFilter=~@clock
|
|
||||||
SystemCallFilter=~@aio
|
|
||||||
SystemCallFilter=~@chown
|
|
||||||
SystemCallFilter=~@cpu-emulation
|
|
||||||
SystemCallFilter=~@debug
|
|
||||||
SystemCallFilter=~@keyring
|
|
||||||
SystemCallFilter=~@memlock
|
|
||||||
SystemCallFilter=~@module
|
|
||||||
SystemCallFilter=~@mount
|
|
||||||
SystemCallFilter=~@obsolete
|
|
||||||
SystemCallFilter=~@privileged
|
|
||||||
SystemCallFilter=~@raw-io
|
|
||||||
SystemCallFilter=~@reboot
|
|
||||||
SystemCallFilter=~@setuid
|
|
||||||
SystemCallFilter=~@swap
|
|
||||||
SystemCallErrorNumber=EPERM
|
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy = multi-user.target
|
WantedBy = multi-user.target
|
||||||
|
|||||||
+16
-2
@@ -7,7 +7,7 @@
|
|||||||
%endif
|
%endif
|
||||||
|
|
||||||
Name: jellyfin
|
Name: jellyfin
|
||||||
Version: 10.8.1
|
Version: 10.8.8
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: The Free Software Media System
|
Summary: The Free Software Media System
|
||||||
License: GPLv2
|
License: GPLv2
|
||||||
@@ -68,7 +68,7 @@ export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
|||||||
export PATH=$PATH:/usr/local/bin
|
export PATH=$PATH:/usr/local/bin
|
||||||
# cannot use --output due to https://github.com/dotnet/sdk/issues/22220
|
# cannot use --output due to https://github.com/dotnet/sdk/issues/22220
|
||||||
dotnet publish --configuration Release --self-contained --runtime %{dotnet_runtime} \
|
dotnet publish --configuration Release --self-contained --runtime %{dotnet_runtime} \
|
||||||
"-p:DebugSymbols=false;DebugType=none" Jellyfin.Server
|
-p:DebugSymbols=false -p:DebugType=none Jellyfin.Server
|
||||||
|
|
||||||
|
|
||||||
%install
|
%install
|
||||||
@@ -176,6 +176,20 @@ fi
|
|||||||
%systemd_postun_with_restart jellyfin.service
|
%systemd_postun_with_restart jellyfin.service
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
|
* Tue Nov 29 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.8.8; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.8
|
||||||
|
* Mon Oct 31 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.8.7; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.7
|
||||||
|
* Fri Oct 28 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.8.6; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.6
|
||||||
|
* Sat Sep 24 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.8.5; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.5
|
||||||
|
* Sat Aug 13 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.8.4; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.4
|
||||||
|
* Mon Aug 01 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.8.3; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.3
|
||||||
|
* Mon Aug 01 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
- New upstream version 10.8.2; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.2
|
||||||
* Sun Jun 26 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
* Sun Jun 26 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
- New upstream version 10.8.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.1
|
- New upstream version 10.8.1; release changelog at https://github.com/jellyfin/jellyfin/releases/tag/v10.8.1
|
||||||
* Fri Jun 10 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
* Fri Jun 10 2022 Jellyfin Packaging Team <packaging@jellyfin.org>
|
||||||
|
|||||||
@@ -11,8 +11,4 @@ Defaults!RESTARTSERVER_SYSTEMD !requiretty
|
|||||||
Defaults!STARTSERVER_SYSTEMD !requiretty
|
Defaults!STARTSERVER_SYSTEMD !requiretty
|
||||||
Defaults!STOPSERVER_SYSTEMD !requiretty
|
Defaults!STOPSERVER_SYSTEMD !requiretty
|
||||||
|
|
||||||
# Allow the server to mount iso images
|
|
||||||
jellyfin ALL=(ALL) NOPASSWD: /bin/mount
|
|
||||||
jellyfin ALL=(ALL) NOPASSWD: /bin/umount
|
|
||||||
|
|
||||||
Defaults:jellyfin !requiretty
|
Defaults:jellyfin !requiretty
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Extensions</PackageId>
|
<PackageId>Jellyfin.Extensions</PackageId>
|
||||||
<VersionPrefix>10.8.1</VersionPrefix>
|
<VersionPrefix>10.8.8</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Jellyfin.MediaEncoding.Keyframes.FfProbe;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class FfProbeKeyframeExtractor
|
public static class FfProbeKeyframeExtractor
|
||||||
{
|
{
|
||||||
private const string DefaultArguments = "-v error -skip_frame nokey -show_entries format=duration -show_entries stream=duration -show_entries packet=pts_time,flags -select_streams v -of csv \"{0}\"";
|
private const string DefaultArguments = "-fflags +genpts -v error -skip_frame nokey -show_entries format=duration -show_entries stream=duration -show_entries packet=pts_time,flags -select_streams v -of csv \"{0}\"";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Extracts the keyframes using the ffprobe executable at the specified path.
|
/// Extracts the keyframes using the ffprobe executable at the specified path.
|
||||||
@@ -38,9 +38,28 @@ public static class FfProbeKeyframeExtractor
|
|||||||
EnableRaisingEvents = true
|
EnableRaisingEvents = true
|
||||||
};
|
};
|
||||||
|
|
||||||
process.Start();
|
try
|
||||||
|
{
|
||||||
|
process.Start();
|
||||||
|
|
||||||
return ParseStream(process.StandardOutput);
|
return ParseStream(process.StandardOutput);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!process.HasExited)
|
||||||
|
{
|
||||||
|
process.Kill();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// We do not care if this fails
|
||||||
|
}
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static KeyframeData ParseStream(StreamReader reader)
|
internal static KeyframeData ParseStream(StreamReader reader)
|
||||||
@@ -62,12 +81,17 @@ public static class FfProbeKeyframeExtractor
|
|||||||
var rest = line[(firstComma + 1)..];
|
var rest = line[(firstComma + 1)..];
|
||||||
if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
|
if (lineType.Equals("packet", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (rest.EndsWith(",K_"))
|
// Split time and flags from the packet line. Example line: packet,7169.079000,K_
|
||||||
|
var secondComma = rest.IndexOf(',');
|
||||||
|
var ptsTime = rest[..secondComma];
|
||||||
|
var flags = rest[(secondComma + 1)..];
|
||||||
|
if (flags.StartsWith("K_"))
|
||||||
{
|
{
|
||||||
// Trim the flags from the packet line. Example line: packet,7169.079000,K_
|
if (double.TryParse(ptsTime, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var keyframe))
|
||||||
var keyframe = double.Parse(rest[..^3], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture);
|
{
|
||||||
// Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double.
|
// Have to manually convert to ticks to avoid rounding errors as TimeSpan is only precise down to 1 ms when converting double.
|
||||||
keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
|
keyframes.Add(Convert.ToInt64(keyframe * TimeSpan.TicksPerSecond));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
|
else if (lineType.Equals("stream", StringComparison.OrdinalIgnoreCase))
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.9" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
<PackageReference Include="Moq" Version="4.18.1" />
|
<PackageReference Include="Moq" Version="4.18.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
<PackageReference Include="Moq" Version="4.18.1" />
|
<PackageReference Include="Moq" Version="4.18.2" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
|
||||||
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
<PackageReference Include="Moq" Version="4.18.1" />
|
<PackageReference Include="Moq" Version="4.18.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
|
|||||||
@@ -109,26 +109,37 @@ namespace Jellyfin.Model.Tests.Entities
|
|||||||
[InlineData(null, null, false, null)]
|
[InlineData(null, null, false, null)]
|
||||||
[InlineData(null, 0, false, null)]
|
[InlineData(null, 0, false, null)]
|
||||||
[InlineData(0, null, false, null)]
|
[InlineData(0, null, false, null)]
|
||||||
[InlineData(640, 480, false, "480p")]
|
[InlineData(256, 144, false, "144p")]
|
||||||
[InlineData(640, 480, true, "480i")]
|
[InlineData(256, 144, true, "144i")]
|
||||||
[InlineData(720, 576, false, "576p")]
|
[InlineData(426, 240, false, "240p")]
|
||||||
[InlineData(720, 576, true, "576i")]
|
[InlineData(426, 240, true, "240i")]
|
||||||
|
[InlineData(640, 360, false, "360p")]
|
||||||
|
[InlineData(640, 360, true, "360i")]
|
||||||
|
[InlineData(854, 480, false, "480p")]
|
||||||
|
[InlineData(854, 480, true, "480i")]
|
||||||
[InlineData(960, 540, false, "540p")]
|
[InlineData(960, 540, false, "540p")]
|
||||||
[InlineData(960, 540, true, "540i")]
|
[InlineData(960, 540, true, "540i")]
|
||||||
|
[InlineData(1024, 576, false, "576p")]
|
||||||
|
[InlineData(1024, 576, true, "576i")]
|
||||||
[InlineData(1280, 720, false, "720p")]
|
[InlineData(1280, 720, false, "720p")]
|
||||||
[InlineData(1280, 720, true, "720i")]
|
[InlineData(1280, 720, true, "720i")]
|
||||||
[InlineData(1920, 1080, false, "1080p")]
|
[InlineData(2560, 1080, false, "1080p")]
|
||||||
[InlineData(1920, 1080, true, "1080i")]
|
[InlineData(2560, 1080, true, "1080i")]
|
||||||
[InlineData(4096, 3072, false, "4K")]
|
[InlineData(4096, 3072, false, "4K")]
|
||||||
[InlineData(8192, 6144, false, "8K")]
|
[InlineData(8192, 6144, false, "8K")]
|
||||||
[InlineData(512, 384, false, "480p")]
|
[InlineData(512, 384, false, "384p")]
|
||||||
[InlineData(576, 336, false, "480p")]
|
[InlineData(576, 336, false, "360p")]
|
||||||
[InlineData(624, 352, false, "480p")]
|
[InlineData(576, 336, true, "360i")]
|
||||||
[InlineData(640, 352, false, "480p")]
|
[InlineData(624, 352, false, "360p")]
|
||||||
[InlineData(704, 396, false, "480p")]
|
[InlineData(640, 352, false, "360p")]
|
||||||
[InlineData(720, 404, false, "480p")]
|
[InlineData(640, 480, false, "480p")]
|
||||||
|
[InlineData(704, 396, false, "404p")]
|
||||||
|
[InlineData(720, 404, false, "404p")]
|
||||||
[InlineData(720, 480, false, "480p")]
|
[InlineData(720, 480, false, "480p")]
|
||||||
|
[InlineData(720, 576, false, "576p")]
|
||||||
[InlineData(768, 576, false, "576p")]
|
[InlineData(768, 576, false, "576p")]
|
||||||
|
[InlineData(960, 544, false, "540p")]
|
||||||
|
[InlineData(960, 544, true, "540i")]
|
||||||
[InlineData(960, 720, false, "720p")]
|
[InlineData(960, 720, false, "720p")]
|
||||||
[InlineData(1280, 528, false, "720p")]
|
[InlineData(1280, 528, false, "720p")]
|
||||||
[InlineData(1280, 532, false, "720p")]
|
[InlineData(1280, 532, false, "720p")]
|
||||||
@@ -140,6 +151,11 @@ namespace Jellyfin.Model.Tests.Entities
|
|||||||
[InlineData(1280, 696, false, "720p")]
|
[InlineData(1280, 696, false, "720p")]
|
||||||
[InlineData(1280, 716, false, "720p")]
|
[InlineData(1280, 716, false, "720p")]
|
||||||
[InlineData(1280, 718, false, "720p")]
|
[InlineData(1280, 718, false, "720p")]
|
||||||
|
[InlineData(1920, 1080, false, "1080p")]
|
||||||
|
[InlineData(1440, 1070, false, "1080p")]
|
||||||
|
[InlineData(1440, 1072, false, "1080p")]
|
||||||
|
[InlineData(1440, 1080, false, "1080p")]
|
||||||
|
[InlineData(1440, 1440, false, "1080p")]
|
||||||
[InlineData(1912, 792, false, "1080p")]
|
[InlineData(1912, 792, false, "1080p")]
|
||||||
[InlineData(1916, 1076, false, "1080p")]
|
[InlineData(1916, 1076, false, "1080p")]
|
||||||
[InlineData(1918, 1080, false, "1080p")]
|
[InlineData(1918, 1080, false, "1080p")]
|
||||||
@@ -153,14 +169,16 @@ namespace Jellyfin.Model.Tests.Entities
|
|||||||
[InlineData(1920, 960, false, "1080p")]
|
[InlineData(1920, 960, false, "1080p")]
|
||||||
[InlineData(1920, 1024, false, "1080p")]
|
[InlineData(1920, 1024, false, "1080p")]
|
||||||
[InlineData(1920, 1040, false, "1080p")]
|
[InlineData(1920, 1040, false, "1080p")]
|
||||||
|
[InlineData(1920, 1070, false, "1080p")]
|
||||||
[InlineData(1920, 1072, false, "1080p")]
|
[InlineData(1920, 1072, false, "1080p")]
|
||||||
[InlineData(1440, 1072, false, "1080p")]
|
[InlineData(1920, 1440, false, "1080p")]
|
||||||
[InlineData(1440, 1080, false, "1080p")]
|
|
||||||
[InlineData(3840, 1600, false, "4K")]
|
[InlineData(3840, 1600, false, "4K")]
|
||||||
[InlineData(3840, 1606, false, "4K")]
|
[InlineData(3840, 1606, false, "4K")]
|
||||||
[InlineData(3840, 1608, false, "4K")]
|
[InlineData(3840, 1608, false, "4K")]
|
||||||
[InlineData(3840, 2160, false, "4K")]
|
[InlineData(3840, 2160, false, "4K")]
|
||||||
|
[InlineData(4090, 3070, false, "4K")]
|
||||||
[InlineData(7680, 4320, false, "8K")]
|
[InlineData(7680, 4320, false, "8K")]
|
||||||
|
[InlineData(8190, 6140, false, "8K")]
|
||||||
public void GetResolutionText_Valid(int? width, int? height, bool interlaced, string expected)
|
public void GetResolutionText_Valid(int? width, int? height, bool interlaced, string expected)
|
||||||
{
|
{
|
||||||
var mediaStream = new MediaStream()
|
var mediaStream = new MediaStream()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
|
||||||
<PackageReference Include="Moq" Version="4.18.1" />
|
<PackageReference Include="Moq" Version="4.18.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user