Merge branch 'master' into bhowe34/fix-replace-missing-metadata-for-music

This commit is contained in:
Brian Howe 2024-02-27 21:07:30 -06:00
commit 54eb81395e
341 changed files with 3132 additions and 3677 deletions

View File

@ -259,7 +259,7 @@ jobs:
publishFeedCredentials: 'NugetOrg' publishFeedCredentials: 'NugetOrg'
allowPackageConflicts: true # This ignores an error if the version already exists allowPackageConflicts: true # This ignores an error if the version already exists
- task: NuGetAuthenticate@0 - task: NuGetAuthenticate@1
displayName: 'Authenticate to unstable Nuget feed' displayName: 'Authenticate to unstable Nuget feed'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master') condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')

View File

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

View File

@ -0,0 +1,28 @@
{
"name": "Development Jellyfin Server",
"image":"mcr.microsoft.com/devcontainers/dotnet:8.0-jammy",
// restores nuget packages, installs the dotnet workloads and installs the dev https certificate
"postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust",
// reads the extensions list and installs them
"postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
"features": {
"ghcr.io/devcontainers/features/dotnet:2": {
"version": "none",
"dotnetRuntimeVersions": "8.0",
"aspNetCoreRuntimeVersions": "8.0"
},
"ghcr.io/devcontainers-contrib/features/apt-packages:1": {
"preserve_apt_list": false,
"packages": ["libfontconfig1"]
},
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"dockerDashComposeVersion": "v2"
},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}
},
"hostRequirements": {
"memory": "8gb",
"cpus": 4
}
}

View File

@ -27,11 +27,11 @@ jobs:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 uses: github/codeql-action/init@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 uses: github/codeql-action/autobuild@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@407ffafae6a767df3e0230c3df91b6443ae8df75 # v2.22.8 uses: github/codeql-action/analyze@0b21cf2492b6b02c465a3e5d7c473717ad7721ba # v3.23.1

View File

@ -25,7 +25,7 @@ jobs:
- name: Generate openapi.json - name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json - name: Upload openapi.json
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
with: with:
name: openapi-head name: openapi-head
retention-days: 14 retention-days: 14
@ -59,7 +59,7 @@ jobs:
- name: Generate openapi.json - name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json - name: Upload openapi.json
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
with: with:
name: openapi-base name: openapi-base
retention-days: 14 retention-days: 14
@ -78,12 +78,12 @@ jobs:
- openapi-base - openapi-base
steps: steps:
- name: Download openapi-head - name: Download openapi-head
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
with: with:
name: openapi-head name: openapi-head
path: openapi-head path: openapi-head
- name: Download openapi-base - name: Download openapi-base
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
with: with:
name: openapi-base name: openapi-base
path: openapi-base path: openapi-base

View File

@ -19,9 +19,9 @@ jobs:
runs-on: "${{ matrix.os }}" runs-on: "${{ matrix.os }}"
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4 - uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0
with: with:
dotnet-version: ${{ env.SDK_VERSION }} dotnet-version: ${{ env.SDK_VERSION }}
@ -34,7 +34,7 @@ jobs:
--verbosity minimal --verbosity minimal
- name: Merge code coverage results - name: Merge code coverage results
uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5 uses: danielpalme/ReportGenerator-GitHub-Action@4d510cbed8a05af5aefea46c7fd6e05b95844c89 # 5.2.0
with: with:
reports: "**/coverage.cobertura.xml" reports: "**/coverage.cobertura.xml"
targetdir: "merged/" targetdir: "merged/"
@ -42,9 +42,3 @@ jobs:
# TODO - which action / tool to use to publish code coverage results? # TODO - which action / tool to use to publish code coverage results?
# - name: Publish code coverage results # - name: Publish code coverage results
- name: Publish OpenAPI Artifact
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3
with:
name: "OpenAPI Spec"
path: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net*/openapi.json"

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }} if: ${{ contains(github.repository, 'jellyfin/') }}
steps: steps:
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with: with:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
ascending: true ascending: true

View File

@ -15,7 +15,7 @@ jobs:
if: ${{ github.repository == 'jellyfin/jellyfin' }} if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps: steps:
- name: Remove from 'Current Release' project - name: Remove from 'Current Release' project
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3 uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport') if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true continue-on-error: true
with: with:
@ -24,7 +24,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Release Next' project - name: Add to 'Release Next' project
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3 uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened' if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
continue-on-error: true continue-on-error: true
with: with:
@ -33,7 +33,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Current Release' project - name: Add to 'Current Release' project
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3 uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport') if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
continue-on-error: true continue-on-error: true
with: with:
@ -47,7 +47,7 @@ jobs:
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)" run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
- name: Move issue to needs triage - name: Move issue to needs triage
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3 uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1 if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
continue-on-error: true continue-on-error: true
with: with:
@ -56,7 +56,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add issue to triage project - name: Add issue to triage project
uses: alex-page/github-project-automation-plus@7ffb872c64bd809d23563a130a0a97d01dfa8f43 # v0.8.3 uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
if: github.event.issue.pull_request == '' && github.event.action == 'opened' if: github.event.issue.pull_request == '' && github.event.action == 'opened'
continue-on-error: true continue-on-error: true
with: with:

View File

@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }} if: ${{ contains(github.repository, 'jellyfin/') }}
steps: steps:
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
with: with:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
ascending: true ascending: true

View File

@ -1,13 +1,11 @@
{ {
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [ "recommendations": [
"ms-dotnettools.csharp", "ms-dotnettools.csharp",
"editorconfig.editorconfig" "editorconfig.editorconfig",
"GitHub.vscode-github-actions",
"ms-dotnettools.vscode-dotnet-runtime",
"ms-dotnettools.csdevkit"
], ],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [ "unwantedRecommendations": [
] ]

View File

@ -81,6 +81,7 @@
- [Maxr1998](https://github.com/Maxr1998) - [Maxr1998](https://github.com/Maxr1998)
- [mcarlton00](https://github.com/mcarlton00) - [mcarlton00](https://github.com/mcarlton00)
- [mitchfizz05](https://github.com/mitchfizz05) - [mitchfizz05](https://github.com/mitchfizz05)
- [mohd-akram](https://github.com/mohd-akram)
- [MrTimscampi](https://github.com/MrTimscampi) - [MrTimscampi](https://github.com/MrTimscampi)
- [n8225](https://github.com/n8225) - [n8225](https://github.com/n8225)
- [Nalsai](https://github.com/Nalsai) - [Nalsai](https://github.com/Nalsai)
@ -171,9 +172,10 @@
- [tallbl0nde](https://github.com/tallbl0nde) - [tallbl0nde](https://github.com/tallbl0nde)
- [sleepycatcoding](https://github.com/sleepycatcoding) - [sleepycatcoding](https://github.com/sleepycatcoding)
- [scampower3](https://github.com/scampower3) - [scampower3](https://github.com/scampower3)
- [Chris-Codes-It] (https://github.com/Chris-Codes-It) - [Chris-Codes-It](https://github.com/Chris-Codes-It)
- [Pithaya](https://github.com/Pithaya) - [Pithaya](https://github.com/Pithaya)
- [Çağrı Sakaoğlu](https://github.com/ilovepilav) - [Çağrı Sakaoğlu](https://github.com/ilovepilav)
- [Gauvino](https://github.com/Gauvino)
# Emby Contributors # Emby Contributors

View File

@ -12,52 +12,52 @@
<PackageVersion Include="BlurHashSharp" Version="1.3.0" /> <PackageVersion Include="BlurHashSharp" Version="1.3.0" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.0" /> <PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="Diacritics" Version="3.3.18" /> <PackageVersion Include="Diacritics" Version="3.3.27" />
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" /> <PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" /> <PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.0.0" /> <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.2.0" />
<PackageVersion Include="FsCheck.Xunit" Version="2.16.6" /> <PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0" /> <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.1" />
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.4" /> <PackageVersion Include="IDisposableAnalyzers" Version="4.0.4" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" /> <PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="3.6.13" /> <PackageVersion Include="libse" Version="3.6.13" />
<PackageVersion Include="LrcParser" Version="2023.524.0" /> <PackageVersion Include="LrcParser" Version="2023.524.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.1" /> <PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.0" /> <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" /> <PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.0" /> <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.0" /> <PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.0" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="MimeTypes" Version="2.4.0" /> <PackageVersion Include="MimeTypes" Version="2.4.0" />
<PackageVersion Include="Mono.Nat" Version="3.0.4" /> <PackageVersion Include="Mono.Nat" Version="3.0.4" />
<PackageVersion Include="Moq" Version="4.18.4" /> <PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="NEbml" Version="0.11.0" /> <PackageVersion Include="NEbml" Version="0.11.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" /> <PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="PlaylistsNET" Version="1.4.0" /> <PackageVersion Include="PlaylistsNET" Version="1.4.1" />
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.0" /> <PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" /> <PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="prometheus-net" Version="8.2.0" /> <PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.0" /> <PackageVersion Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" /> <PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" /> <PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" />
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" /> <PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
@ -66,25 +66,25 @@
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" /> <PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" /> <PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
<PackageVersion Include="SharpFuzz" Version="2.1.1" /> <PackageVersion Include="SharpFuzz" Version="2.1.1" />
<PackageVersion Include="SkiaSharp" Version="2.88.5" /> <PackageVersion Include="SkiaSharp" Version="2.88.7" />
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.5" /> <PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.7" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.5" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" /> <PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.507" /> <PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Svg.Skia" Version="1.0.0.2" /> <PackageVersion Include="Svg.Skia" Version="1.0.0.10" />
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" /> <PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" /> <PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageVersion Include="System.Globalization" Version="4.3.0" /> <PackageVersion Include="System.Globalization" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" /> <PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" /> <PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.0" /> <PackageVersion Include="System.Text.Json" Version="8.0.1" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" /> <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" /> <PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="TMDbLib" Version="2.1.0" /> <PackageVersion Include="TMDbLib" Version="2.1.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" /> <PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="Xunit.Priority" Version="1.1.6" /> <PackageVersion Include="Xunit.Priority" Version="1.1.6" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.3" /> <PackageVersion Include="xunit.runner.visualstudio" Version="2.5.6" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" /> <PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageVersion Include="xunit" Version="2.6.1" /> <PackageVersion Include="xunit" Version="2.6.6" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -8,72 +8,68 @@ FROM node:20-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& apk del curl \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& npm ci --no-audit --unsafe-perm \ && npm ci --no-audit --unsafe-perm \
&& npm run build:production \ && npm run build:production \
&& mv dist /dist && mv dist /dist
FROM debian:stable-slim as app FROM debian:bookworm-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622 # http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) # https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_VISIBLE_DEVICES="all"
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
# https://github.com/intel/compute-runtime/releases ENV JELLYFIN_DATA_DIR=/config
ARG GMMLIB_VERSION=22.0.2 ENV JELLYFIN_CACHE_DIR=/cache
ARG IGC_VERSION=1.0.10395
ARG NEO_VERSION=22.08.22549 # https://github.com/intel/compute-runtime/releases
ARG LEVEL_ZERO_VERSION=1.3.22549 ARG GMMLIB_VERSION=22.3.11.ci17757293
ARG IGC_VERSION=1.0.15136.22
ARG NEO_VERSION=23.39.27427.23
ARG LEVEL_ZERO_VERSION=1.3.27427.23
# Install dependencies:
# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
# curl: healthcheck
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget curl \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \ && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \ && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
&& apt-get update \ && apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \ && apt-get install --no-install-recommends --no-install-suggests -y mesa-va-drivers jellyfin-ffmpeg6 openssl locales \
mesa-va-drivers \
jellyfin-ffmpeg5 \
openssl \
locales \
# Intel VAAPI Tone mapping dependencies: # Intel VAAPI Tone mapping dependencies:
# Prefer NEO to Beignet since the latter one doesn't support Comet Lake or newer for now. # Prefer NEO to Beignet since the latter one doesn't support Comet Lake or newer for now.
# Do not use the intel-opencl-icd package from repo since they will not build with RELEASE_WITH_REGKEYS enabled. # Do not use the intel-opencl-icd package from repo since they will not build with RELEASE_WITH_REGKEYS enabled.
&& mkdir intel-compute-runtime \ && mkdir intel-compute-runtime \
&& cd intel-compute-runtime \ && cd intel-compute-runtime \
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \ && curl -LO https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \ -LO https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \ -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \ -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \ -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/libigdgmm12_${GMMLIB_VERSION}_amd64.deb \
&& dpkg -i *.deb \ && dpkg -i *.deb \
&& cd .. \ && cd .. \
&& rm -rf intel-compute-runtime \ && rm -rf intel-compute-runtime \
&& apt-get remove gnupg wget -y \ && apt-get remove gnupg -y \
&& apt-get clean autoclean -y \ && apt-get clean autoclean -y \
&& apt-get autoremove -y \ && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \ && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
&& chmod 777 /cache /config /media \ && chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV LC_ALL=en_US.UTF-8
ENV LC_ALL en_US.UTF-8 ENV LANG=en_US.UTF-8
ENV LANG en_US.UTF-8 ENV LANGUAGE=en_US:en
ENV LANGUAGE en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo WORKDIR /repo
COPY . . 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
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p: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
@ -83,11 +79,9 @@ COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
ENTRYPOINT ["./jellyfin/jellyfin", \ ENTRYPOINT [ "./jellyfin/jellyfin", \
"--datadir", "/config", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
"--cachedir", "/cache", \
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1 CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1

View File

@ -4,64 +4,58 @@
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register # https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=8.0 ARG DOTNET_VERSION=8.0
FROM node:20-alpine as web-builder FROM node:20-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& apk del curl \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& npm ci --no-audit --unsafe-perm \ && npm ci --no-audit --unsafe-perm \
&& npm run build:production \ && npm run build:production \
&& mv dist /dist && mv dist /dist
FROM multiarch/qemu-user-static:x86_64-arm as qemu FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM arm32v7/debian:stable-slim as app FROM arm32v7/debian:bookworm-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622 # http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) # https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_VISIBLE_DEVICES="all"
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
ENV JELLYFIN_DATA_DIR=/config
ENV JELLYFIN_CACHE_DIR=/cache
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
# curl: setup & healthcheck
RUN apt-get update \ RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \ && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
curl -ks https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \ && curl -fsSL https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | gpg --dearmor -o /etc/apt/trusted.gpg.d/ubuntu-jellyfin.gpg \
echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \ && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \ && apt-get update \
apt-get update && \ && apt-get install --no-install-recommends --no-install-suggests -y \
apt-get install --no-install-recommends --no-install-suggests -y \ jellyfin-ffmpeg6 libssl-dev libfontconfig1 \
jellyfin-ffmpeg \ libfreetype6 vainfo libva2 locales \
libssl-dev \
libfontconfig1 \
libfreetype6 \
vainfo \
libva2 \
locales \
&& apt-get remove gnupg -y \ && apt-get remove gnupg -y \
&& apt-get clean autoclean -y \ && apt-get clean autoclean -y \
&& apt-get autoremove -y \ && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \ && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
&& chmod 777 /cache /config /media \ && chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV LC_ALL=en_US.UTF-8
ENV LC_ALL en_US.UTF-8 ENV LANG=en_US.UTF-8
ENV LANG en_US.UTF-8 ENV LANGUAGE=en_US:en
ENV LANGUAGE en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo WORKDIR /repo
COPY . . COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm -p:DebugSymbols=false -p: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
@ -72,11 +66,9 @@ COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
ENTRYPOINT ["./jellyfin/jellyfin", \ ENTRYPOINT [ "/jellyfin/jellyfin", \
"--datadir", "/config", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
"--cachedir", "/cache", \
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1 CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1

View File

@ -4,58 +4,58 @@
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register # https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=8.0 ARG DOTNET_VERSION=8.0
FROM node:20-alpine as web-builder FROM node:20-alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master ARG JELLYFIN_WEB_VERSION=master
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \ RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \ && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& apk del curl \
&& cd jellyfin-web-* \ && cd jellyfin-web-* \
&& npm ci --no-audit --unsafe-perm \ && npm ci --no-audit --unsafe-perm \
&& npm run build:production \ && npm run build:production \
&& mv dist /dist && mv dist /dist
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM arm64v8/debian:stable-slim as app FROM arm64v8/debian:bookworm-slim as app
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable # https://askubuntu.com/questions/972516/debian-frontend-environment-variable
ARG DEBIAN_FRONTEND="noninteractive" ARG DEBIAN_FRONTEND="noninteractive"
# http://stackoverflow.com/questions/48162574/ddg#49462622 # http://stackoverflow.com/questions/48162574/ddg#49462622
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support) # https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_VISIBLE_DEVICES="all"
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility" ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
ENV JELLYFIN_DATA_DIR=/config
ENV JELLYFIN_CACHE_DIR=/cache
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
# curl: healcheck RUN apt-get update \
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
ffmpeg \ && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
libssl-dev \ && curl -fsSL https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | gpg --dearmor -o /etc/apt/trusted.gpg.d/ubuntu-jellyfin.gpg \
ca-certificates \ && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
libfontconfig1 \ && apt-get update \
libfreetype6 \ && apt-get install --no-install-recommends --no-install-suggests -y \
libomxil-bellagio0 \ jellyfin-ffmpeg6 locales libssl-dev libfontconfig1 \
libomxil-bellagio-bin \ libfreetype6 libomxil-bellagio0 libomxil-bellagio-bin \
locales \ && apt-get remove gnupg -y \
curl \
&& apt-get clean autoclean -y \ && apt-get clean autoclean -y \
&& apt-get autoremove -y \ && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \ && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
&& chmod 777 /cache /config /media \ && chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1 ENV LC_ALL=en_US.UTF-8
ENV LC_ALL en_US.UTF-8 ENV LANG=en_US.UTF-8
ENV LANG en_US.UTF-8 ENV LANGUAGE=en_US:en
ENV LANGUAGE en_US:en
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo WORKDIR /repo
COPY . . COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p: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
@ -66,11 +66,9 @@ COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web COPY --from=web-builder /dist /jellyfin/jellyfin-web
EXPOSE 8096 EXPOSE 8096
VOLUME /cache /config VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
ENTRYPOINT ["./jellyfin/jellyfin", \ ENTRYPOINT [ "/jellyfin/jellyfin", \
"--datadir", "/config", \ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
"--cachedir", "/cache", \
"--ffmpeg", "/usr/bin/ffmpeg"]
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \ HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1 CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1

View File

@ -15,7 +15,6 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Photos; using Emby.Photos;
using Emby.Server.Implementations.Channels;
using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Collections;
using Emby.Server.Implementations.Configuration; using Emby.Server.Implementations.Configuration;
using Emby.Server.Implementations.Cryptography; using Emby.Server.Implementations.Cryptography;
@ -25,7 +24,6 @@ using Emby.Server.Implementations.Dto;
using Emby.Server.Implementations.HttpServer.Security; using Emby.Server.Implementations.HttpServer.Security;
using Emby.Server.Implementations.IO; using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Library; using Emby.Server.Implementations.Library;
using Emby.Server.Implementations.LiveTv;
using Emby.Server.Implementations.Localization; using Emby.Server.Implementations.Localization;
using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Plugins; using Emby.Server.Implementations.Plugins;
@ -76,6 +74,7 @@ using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo; using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.MediaEncoding.Subtitles; using MediaBrowser.MediaEncoding.Subtitles;
using MediaBrowser.MediaEncoding.Transcoding;
using MediaBrowser.Model.Cryptography; using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -503,8 +502,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton(_xmlSerializer); serviceCollection.AddSingleton(_xmlSerializer);
serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>(); serviceCollection.AddSingleton<ICryptoProvider, CryptographyProvider>();
serviceCollection.AddSingleton<ISocketFactory, SocketFactory>(); serviceCollection.AddSingleton<ISocketFactory, SocketFactory>();
@ -556,8 +553,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>)); serviceCollection.AddTransient(provider => new Lazy<ILiveTvManager>(provider.GetRequiredService<ILiveTvManager>));
serviceCollection.AddSingleton<IDtoService, DtoService>(); serviceCollection.AddSingleton<IDtoService, DtoService>();
serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
serviceCollection.AddSingleton<ISessionManager, SessionManager>(); serviceCollection.AddSingleton<ISessionManager, SessionManager>();
serviceCollection.AddSingleton<ICollectionManager, CollectionManager>(); serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
@ -566,9 +561,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>(); serviceCollection.AddSingleton<ISyncPlayManager, SyncPlayManager>();
serviceCollection.AddSingleton<LiveTvDtoService>();
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>(); serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
serviceCollection.AddSingleton<IChapterManager, ChapterManager>(); serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
@ -583,7 +575,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>(); serviceCollection.AddSingleton<IAttachmentExtractor, MediaBrowser.MediaEncoding.Attachments.AttachmentExtractor>();
serviceCollection.AddSingleton<TranscodingJobHelper>(); serviceCollection.AddSingleton<ITranscodeManager, TranscodeManager>();
serviceCollection.AddScoped<MediaInfoHelper>(); serviceCollection.AddScoped<MediaInfoHelper>();
serviceCollection.AddScoped<AudioHelper>(); serviceCollection.AddScoped<AudioHelper>();
serviceCollection.AddScoped<DynamicHlsHelper>(); serviceCollection.AddScoped<DynamicHlsHelper>();
@ -703,7 +695,7 @@ namespace Emby.Server.Implementations
GetExports<IMetadataSaver>(), GetExports<IMetadataSaver>(),
GetExports<IExternalId>()); GetExports<IExternalId>());
Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>()); Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<IListingsProvider>());
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>()); Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
} }

View File

@ -104,6 +104,13 @@ namespace Emby.Server.Implementations.Data
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal, out var dateTimeResult)) if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.AdjustToUniversal, out var dateTimeResult))
{ {
// If the resulting DateTimeKind is Unspecified it is actually Utc.
// This is required downstream for the Json serializer.
if (dateTimeResult.Kind == DateTimeKind.Unspecified)
{
dateTimeResult = DateTime.SpecifyKind(dateTimeResult, DateTimeKind.Utc);
}
result = dateTimeResult; result = dateTimeResult;
return true; return true;
} }

View File

@ -699,7 +699,7 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBindNull("@EndDate"); saveItemStatement.TryBindNull("@EndDate");
} }
saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(default) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture)); saveItemStatement.TryBind("@ChannelId", item.ChannelId.IsEmpty() ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture));
if (item is IHasProgramAttributes hasProgramAttributes) if (item is IHasProgramAttributes hasProgramAttributes)
{ {
@ -729,7 +729,7 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBind("@ProductionYear", item.ProductionYear); saveItemStatement.TryBind("@ProductionYear", item.ProductionYear);
var parentId = item.ParentId; var parentId = item.ParentId;
if (parentId.Equals(default)) if (parentId.IsEmpty())
{ {
saveItemStatement.TryBindNull("@ParentId"); saveItemStatement.TryBindNull("@ParentId");
} }
@ -925,7 +925,7 @@ namespace Emby.Server.Implementations.Data
{ {
saveItemStatement.TryBind("@SeasonName", episode.SeasonName); saveItemStatement.TryBind("@SeasonName", episode.SeasonName);
var nullableSeasonId = episode.SeasonId.Equals(default) ? (Guid?)null : episode.SeasonId; var nullableSeasonId = episode.SeasonId.IsEmpty() ? (Guid?)null : episode.SeasonId;
saveItemStatement.TryBind("@SeasonId", nullableSeasonId); saveItemStatement.TryBind("@SeasonId", nullableSeasonId);
} }
@ -937,7 +937,7 @@ namespace Emby.Server.Implementations.Data
if (item is IHasSeries hasSeries) if (item is IHasSeries hasSeries)
{ {
var nullableSeriesId = hasSeries.SeriesId.Equals(default) ? (Guid?)null : hasSeries.SeriesId; var nullableSeriesId = hasSeries.SeriesId.IsEmpty() ? (Guid?)null : hasSeries.SeriesId;
saveItemStatement.TryBind("@SeriesId", nullableSeriesId); saveItemStatement.TryBind("@SeriesId", nullableSeriesId);
saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey); saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey);
@ -1010,7 +1010,7 @@ namespace Emby.Server.Implementations.Data
} }
Guid ownerId = item.OwnerId; Guid ownerId = item.OwnerId;
if (ownerId.Equals(default)) if (ownerId.IsEmpty())
{ {
saveItemStatement.TryBindNull("@OwnerId"); saveItemStatement.TryBindNull("@OwnerId");
} }
@ -1266,7 +1266,7 @@ namespace Emby.Server.Implementations.Data
/// <exception cref="ArgumentException"><paramr name="id"/> is <seealso cref="Guid.Empty"/>.</exception> /// <exception cref="ArgumentException"><paramr name="id"/> is <seealso cref="Guid.Empty"/>.</exception>
public BaseItem RetrieveItem(Guid id) public BaseItem RetrieveItem(Guid id)
{ {
if (id.Equals(default)) if (id.IsEmpty())
{ {
throw new ArgumentException("Guid can't be empty", nameof(id)); throw new ArgumentException("Guid can't be empty", nameof(id));
} }
@ -1970,7 +1970,7 @@ namespace Emby.Server.Implementations.Data
{ {
CheckDisposed(); CheckDisposed();
if (id.Equals(default)) if (id.IsEmpty())
{ {
throw new ArgumentNullException(nameof(id)); throw new ArgumentNullException(nameof(id));
} }
@ -3230,7 +3230,7 @@ namespace Emby.Server.Implementations.Data
whereClauses.Add($"ChannelId in ({inClause})"); whereClauses.Add($"ChannelId in ({inClause})");
} }
if (!query.ParentId.Equals(default)) if (!query.ParentId.IsEmpty())
{ {
whereClauses.Add("ParentId=@ParentId"); whereClauses.Add("ParentId=@ParentId");
statement?.TryBind("@ParentId", query.ParentId); statement?.TryBind("@ParentId", query.ParentId);
@ -4452,7 +4452,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
public void DeleteItem(Guid id) public void DeleteItem(Guid id)
{ {
if (id.Equals(default)) if (id.IsEmpty())
{ {
throw new ArgumentNullException(nameof(id)); throw new ArgumentNullException(nameof(id));
} }
@ -4583,13 +4583,13 @@ AND Type = @InternalPersonType)");
statement?.TryBind("@UserId", query.User.InternalId); statement?.TryBind("@UserId", query.User.InternalId);
} }
if (!query.ItemId.Equals(default)) if (!query.ItemId.IsEmpty())
{ {
whereClauses.Add("ItemId=@ItemId"); whereClauses.Add("ItemId=@ItemId");
statement?.TryBind("@ItemId", query.ItemId); statement?.TryBind("@ItemId", query.ItemId);
} }
if (!query.AppearsInItemId.Equals(default)) if (!query.AppearsInItemId.IsEmpty())
{ {
whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)"); whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)");
statement?.TryBind("@AppearsInItemId", query.AppearsInItemId); statement?.TryBind("@AppearsInItemId", query.AppearsInItemId);
@ -4640,7 +4640,7 @@ AND Type = @InternalPersonType)");
private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement) private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement)
{ {
if (itemId.Equals(default)) if (itemId.IsEmpty())
{ {
throw new ArgumentNullException(nameof(itemId)); throw new ArgumentNullException(nameof(itemId));
} }
@ -5156,7 +5156,7 @@ AND Type = @InternalPersonType)");
private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db) private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db)
{ {
if (itemId.Equals(default)) if (itemId.IsEmpty())
{ {
throw new ArgumentNullException(nameof(itemId)); throw new ArgumentNullException(nameof(itemId));
} }
@ -5228,7 +5228,7 @@ AND Type = @InternalPersonType)");
public void UpdatePeople(Guid itemId, List<PersonInfo> people) public void UpdatePeople(Guid itemId, List<PersonInfo> people)
{ {
if (itemId.Equals(default)) if (itemId.IsEmpty())
{ {
throw new ArgumentNullException(nameof(itemId)); throw new ArgumentNullException(nameof(itemId));
} }
@ -5378,7 +5378,7 @@ AND Type = @InternalPersonType)");
{ {
CheckDisposed(); CheckDisposed();
if (id.Equals(default)) if (id.IsEmpty())
{ {
throw new ArgumentNullException(nameof(id)); throw new ArgumentNullException(nameof(id));
} }
@ -5758,7 +5758,7 @@ AND Type = @InternalPersonType)");
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
CheckDisposed(); CheckDisposed();
if (id.Equals(default)) if (id.IsEmpty())
{ {
throw new ArgumentException("Guid can't be empty.", nameof(id)); throw new ArgumentException("Guid can't be empty.", nameof(id));
} }

View File

@ -418,15 +418,6 @@ namespace Emby.Server.Implementations.Dto
{ {
dto.PlayAccess = item.GetPlayAccess(user); dto.PlayAccess = item.GetPlayAccess(user);
} }
if (options.ContainsField(ItemFields.BasicSyncInfo))
{
var userCanSync = user is not null && user.HasPermission(PermissionKind.EnableContentDownloading);
if (userCanSync && item.SupportsExternalTransfer)
{
dto.SupportsSync = true;
}
}
} }
private static int GetChildCount(Folder folder, User user) private static int GetChildCount(Folder folder, User user)

View File

@ -22,7 +22,6 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DiscUtils.Udf" /> <PackageReference Include="DiscUtils.Udf" />
<PackageReference Include="Jellyfin.XmlTv" />
<PackageReference Include="Microsoft.Data.Sqlite" /> <PackageReference Include="Microsoft.Data.Sqlite" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" />

View File

@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -241,7 +242,7 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint
{ {
var userIds = _sessionManager.Sessions var userIds = _sessionManager.Sessions
.Select(i => i.UserId) .Select(i => i.UserId)
.Where(i => !i.Equals(default)) .Where(i => !i.IsEmpty())
.Distinct() .Distinct()
.ToArray(); .ToArray();

View File

@ -32,26 +32,26 @@ namespace Emby.Server.Implementations.Images
switch (viewType) switch (viewType)
{ {
case CollectionType.Movies: case CollectionType.movies:
includeItemTypes = new[] { BaseItemKind.Movie }; includeItemTypes = new[] { BaseItemKind.Movie };
break; break;
case CollectionType.TvShows: case CollectionType.tvshows:
includeItemTypes = new[] { BaseItemKind.Series }; includeItemTypes = new[] { BaseItemKind.Series };
break; break;
case CollectionType.Music: case CollectionType.music:
includeItemTypes = new[] { BaseItemKind.MusicAlbum }; includeItemTypes = new[] { BaseItemKind.MusicAlbum };
break; break;
case CollectionType.MusicVideos: case CollectionType.musicvideos:
includeItemTypes = new[] { BaseItemKind.MusicVideo }; includeItemTypes = new[] { BaseItemKind.MusicVideo };
break; break;
case CollectionType.Books: case CollectionType.books:
includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook }; includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
break; break;
case CollectionType.BoxSets: case CollectionType.boxsets:
includeItemTypes = new[] { BaseItemKind.BoxSet }; includeItemTypes = new[] { BaseItemKind.BoxSet };
break; break;
case CollectionType.HomeVideos: case CollectionType.homevideos:
case CollectionType.Photos: case CollectionType.photos:
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo }; includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo };
break; break;
default: default:
@ -59,7 +59,7 @@ namespace Emby.Server.Implementations.Images
break; break;
} }
var recursive = viewType != CollectionType.Playlists; var recursive = viewType != CollectionType.playlists;
return view.GetItemList(new InternalItemsQuery return view.GetItemList(new InternalItemsQuery
{ {

View File

@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Images
var view = (UserView)item; var view = (UserView)item;
var isUsingCollectionStrip = IsUsingCollectionStrip(view); var isUsingCollectionStrip = IsUsingCollectionStrip(view);
var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.BoxSets && view.ViewType != CollectionType.Playlists; var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.boxsets && view.ViewType != CollectionType.playlists;
var result = view.GetItemList(new InternalItemsQuery var result = view.GetItemList(new InternalItemsQuery
{ {
@ -114,9 +114,9 @@ namespace Emby.Server.Implementations.Images
{ {
CollectionType[] collectionStripViewTypes = CollectionType[] collectionStripViewTypes =
{ {
CollectionType.Movies, CollectionType.movies,
CollectionType.TvShows, CollectionType.tvshows,
CollectionType.Playlists CollectionType.playlists
}; };
return view?.ViewType is not null && collectionStripViewTypes.Contains(view.ViewType.Value); return view?.ViewType is not null && collectionStripViewTypes.Contains(view.ViewType.Value);

View File

@ -732,7 +732,7 @@ namespace Emby.Server.Implementations.Library
Path = path Path = path
}; };
if (folder.Id.Equals(default)) if (folder.Id.IsEmpty())
{ {
if (string.IsNullOrEmpty(folder.Path)) if (string.IsNullOrEmpty(folder.Path))
{ {
@ -1219,7 +1219,7 @@ namespace Emby.Server.Implementations.Library
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception> /// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
public BaseItem GetItemById(Guid id) public BaseItem GetItemById(Guid id)
{ {
if (id.Equals(default)) if (id.IsEmpty())
{ {
throw new ArgumentException("Guid can't be empty", nameof(id)); throw new ArgumentException("Guid can't be empty", nameof(id));
} }
@ -1241,7 +1241,7 @@ namespace Emby.Server.Implementations.Library
public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent) public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
{ {
if (query.Recursive && !query.ParentId.Equals(default)) if (query.Recursive && !query.ParentId.IsEmpty())
{ {
var parent = GetItemById(query.ParentId); var parent = GetItemById(query.ParentId);
if (parent is not null) if (parent is not null)
@ -1272,7 +1272,7 @@ namespace Emby.Server.Implementations.Library
public int GetCount(InternalItemsQuery query) public int GetCount(InternalItemsQuery query)
{ {
if (query.Recursive && !query.ParentId.Equals(default)) if (query.Recursive && !query.ParentId.IsEmpty())
{ {
var parent = GetItemById(query.ParentId); var parent = GetItemById(query.ParentId);
if (parent is not null) if (parent is not null)
@ -1430,7 +1430,7 @@ namespace Emby.Server.Implementations.Library
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query) public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
{ {
if (query.Recursive && !query.ParentId.Equals(default)) if (query.Recursive && !query.ParentId.IsEmpty())
{ {
var parent = GetItemById(query.ParentId); var parent = GetItemById(query.ParentId);
if (parent is not null) if (parent is not null)
@ -1486,7 +1486,7 @@ namespace Emby.Server.Implementations.Library
private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true) private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true)
{ {
if (query.AncestorIds.Length == 0 && if (query.AncestorIds.Length == 0 &&
query.ParentId.Equals(default) && query.ParentId.IsEmpty() &&
query.ChannelIds.Count == 0 && query.ChannelIds.Count == 0 &&
query.TopParentIds.Length == 0 && query.TopParentIds.Length == 0 &&
string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) && string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) &&
@ -1514,13 +1514,13 @@ namespace Emby.Server.Implementations.Library
{ {
if (item is UserView view) if (item is UserView view)
{ {
if (view.ViewType == CollectionType.LiveTv) if (view.ViewType == CollectionType.livetv)
{ {
return new[] { view.Id }; return new[] { view.Id };
} }
// Translate view into folders // Translate view into folders
if (!view.DisplayParentId.Equals(default)) if (!view.DisplayParentId.IsEmpty())
{ {
var displayParent = GetItemById(view.DisplayParentId); var displayParent = GetItemById(view.DisplayParentId);
if (displayParent is not null) if (displayParent is not null)
@ -1531,7 +1531,7 @@ namespace Emby.Server.Implementations.Library
return Array.Empty<Guid>(); return Array.Empty<Guid>();
} }
if (!view.ParentId.Equals(default)) if (!view.ParentId.IsEmpty())
{ {
var displayParent = GetItemById(view.ParentId); var displayParent = GetItemById(view.ParentId);
if (displayParent is not null) if (displayParent is not null)
@ -1543,7 +1543,7 @@ namespace Emby.Server.Implementations.Library
} }
// Handle grouping // Handle grouping
if (user is not null && view.ViewType != CollectionType.Unknown && UserView.IsEligibleForGrouping(view.ViewType) if (user is not null && view.ViewType != CollectionType.unknown && UserView.IsEligibleForGrouping(view.ViewType)
&& user.GetPreference(PreferenceKind.GroupedFolders).Length > 0) && user.GetPreference(PreferenceKind.GroupedFolders).Length > 0)
{ {
return GetUserRootFolder() return GetUserRootFolder()
@ -2137,7 +2137,7 @@ namespace Emby.Server.Implementations.Library
return null; return null;
} }
while (!item.ParentId.Equals(default)) while (!item.ParentId.IsEmpty())
{ {
var parent = item.GetParent(); var parent = item.GetParent();
if (parent is null || parent is AggregateFolder) if (parent is null || parent is AggregateFolder)
@ -2215,7 +2215,7 @@ namespace Emby.Server.Implementations.Library
CollectionType? viewType, CollectionType? viewType,
string sortName) string sortName)
{ {
var parentIdString = parentId.Equals(default) var parentIdString = parentId.IsEmpty()
? null ? null
: parentId.ToString("N", CultureInfo.InvariantCulture); : parentId.ToString("N", CultureInfo.InvariantCulture);
var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty); var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty);
@ -2251,7 +2251,7 @@ namespace Emby.Server.Implementations.Library
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
if (!refresh && !item.DisplayParentId.Equals(default)) if (!refresh && !item.DisplayParentId.IsEmpty())
{ {
var displayParent = GetItemById(item.DisplayParentId); var displayParent = GetItemById(item.DisplayParentId);
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
@ -2315,7 +2315,7 @@ namespace Emby.Server.Implementations.Library
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
if (!refresh && !item.DisplayParentId.Equals(default)) if (!refresh && !item.DisplayParentId.IsEmpty())
{ {
var displayParent = GetItemById(item.DisplayParentId); var displayParent = GetItemById(item.DisplayParentId);
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
@ -2345,7 +2345,7 @@ namespace Emby.Server.Implementations.Library
{ {
ArgumentException.ThrowIfNullOrEmpty(name); ArgumentException.ThrowIfNullOrEmpty(name);
var parentIdString = parentId.Equals(default) var parentIdString = parentId.IsEmpty()
? null ? null
: parentId.ToString("N", CultureInfo.InvariantCulture); : parentId.ToString("N", CultureInfo.InvariantCulture);
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty); var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty);
@ -2391,7 +2391,7 @@ namespace Emby.Server.Implementations.Library
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval; var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
if (!refresh && !item.DisplayParentId.Equals(default)) if (!refresh && !item.DisplayParentId.IsEmpty())
{ {
var displayParent = GetItemById(item.DisplayParentId); var displayParent = GetItemById(item.DisplayParentId);
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed; refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
@ -2419,7 +2419,7 @@ namespace Emby.Server.Implementations.Library
return GetItemById(parentId.Value); return GetItemById(parentId.Value);
} }
if (userId.HasValue && !userId.Equals(default)) if (!userId.IsNullOrEmpty())
{ {
return GetUserRootFolder(); return GetUserRootFolder();
} }

View File

@ -11,14 +11,16 @@ using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using EasyCaching.Core.Configurations;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -37,6 +39,7 @@ namespace Emby.Server.Implementations.Library
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message. // Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const char LiveStreamIdDelimeter = '_'; private const char LiveStreamIdDelimeter = '_';
private readonly IServerApplicationHost _appHost;
private readonly IItemRepository _itemRepo; private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@ -55,6 +58,7 @@ namespace Emby.Server.Implementations.Library
private IMediaSourceProvider[] _providers; private IMediaSourceProvider[] _providers;
public MediaSourceManager( public MediaSourceManager(
IServerApplicationHost appHost,
IItemRepository itemRepo, IItemRepository itemRepo,
IApplicationPaths applicationPaths, IApplicationPaths applicationPaths,
ILocalizationManager localizationManager, ILocalizationManager localizationManager,
@ -66,6 +70,7 @@ namespace Emby.Server.Implementations.Library
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IDirectoryService directoryService) IDirectoryService directoryService)
{ {
_appHost = appHost;
_itemRepo = itemRepo; _itemRepo = itemRepo;
_userManager = userManager; _userManager = userManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
@ -520,10 +525,10 @@ namespace Emby.Server.Implementations.Library
_logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource); _logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions); var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
if (!request.UserId.Equals(default)) if (!request.UserId.IsEmpty())
{ {
var user = _userManager.GetUserById(request.UserId); var user = _userManager.GetUserById(request.UserId);
var item = request.ItemId.Equals(default) var item = request.ItemId.IsEmpty()
? null ? null
: _libraryManager.GetItemById(request.ItemId); : _libraryManager.GetItemById(request.ItemId);
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
@ -799,6 +804,35 @@ namespace Emby.Server.Implementations.Library
return result.Item1; return result.Item1;
} }
public async Task<List<MediaSourceInfo>> GetRecordingStreamMediaSources(ActiveRecordingInfo info, CancellationToken cancellationToken)
{
var stream = new MediaSourceInfo
{
EncoderPath = _appHost.GetApiUrlForLocalAccess() + "/LiveTv/LiveRecordings/" + info.Id + "/stream",
EncoderProtocol = MediaProtocol.Http,
Path = info.Path,
Protocol = MediaProtocol.File,
Id = info.Id,
SupportsDirectPlay = false,
SupportsDirectStream = true,
SupportsTranscoding = true,
IsInfiniteStream = true,
RequiresOpening = false,
RequiresClosing = false,
BufferMs = 0,
IgnoreDts = true,
IgnoreIndex = true
};
await new LiveStreamHelper(_mediaEncoder, _logger, _appPaths)
.AddMediaInfoWithProbe(stream, false, false, cancellationToken).ConfigureAwait(false);
return new List<MediaSourceInfo>
{
stream
};
}
public async Task CloseLiveStream(string id) public async Task CloseLiveStream(string id)
{ {
ArgumentException.ThrowIfNullOrEmpty(id); ArgumentException.ThrowIfNullOrEmpty(id);

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library
{ {
return Guid.Empty; return Guid.Empty;
} }
}).Where(i => !i.Equals(default)).ToArray(); }).Where(i => !i.IsEmpty()).ToArray();
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions); return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
} }

View File

@ -61,7 +61,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
List<FileSystemMetadata> files, List<FileSystemMetadata> files,
CollectionType? collectionType) CollectionType? collectionType)
{ {
if (collectionType == CollectionType.Books) if (collectionType == CollectionType.books)
{ {
return ResolveMultipleAudio(parent, files, true); return ResolveMultipleAudio(parent, files, true);
} }
@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();
var isBooksCollectionType = collectionType == CollectionType.Books; var isBooksCollectionType = collectionType == CollectionType.books;
if (args.IsDirectory) if (args.IsDirectory)
{ {
@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
MediaBrowser.Controller.Entities.Audio.Audio item = null; MediaBrowser.Controller.Entities.Audio.Audio item = null;
var isMusicCollectionType = collectionType == CollectionType.Music; var isMusicCollectionType = collectionType == CollectionType.music;
// Use regular audio type for mixed libraries, owned items and music // Use regular audio type for mixed libraries, owned items and music
if (isMixedCollectionType || if (isMixedCollectionType ||

View File

@ -55,7 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
protected override MusicAlbum Resolve(ItemResolveArgs args) protected override MusicAlbum Resolve(ItemResolveArgs args)
{ {
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();
var isMusicMediaFolder = collectionType == CollectionType.Music; var isMusicMediaFolder = collectionType == CollectionType.music;
// If there's a collection type and it's not music, don't allow it. // If there's a collection type and it's not music, don't allow it.
if (!isMusicMediaFolder) if (!isMusicMediaFolder)

View File

@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();
var isMusicMediaFolder = collectionType == CollectionType.Music; var isMusicMediaFolder = collectionType == CollectionType.music;
// If there's a collection type and it's not music, it can't be a music artist // If there's a collection type and it's not music, it can't be a music artist
if (!isMusicMediaFolder) if (!isMusicMediaFolder)

View File

@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();
// Only process items that are in a collection folder containing books // Only process items that are in a collection folder containing books
if (collectionType != CollectionType.Books) if (collectionType != CollectionType.books)
{ {
return null; return null;
} }

View File

@ -31,11 +31,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
private static readonly CollectionType[] _validCollectionTypes = new[] private static readonly CollectionType[] _validCollectionTypes = new[]
{ {
CollectionType.Movies, CollectionType.movies,
CollectionType.HomeVideos, CollectionType.homevideos,
CollectionType.MusicVideos, CollectionType.musicvideos,
CollectionType.TvShows, CollectionType.tvshows,
CollectionType.Photos CollectionType.photos
}; };
/// <summary> /// <summary>
@ -100,12 +100,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
Video movie = null; Video movie = null;
var files = args.GetActualFileSystemChildren().ToList(); var files = args.GetActualFileSystemChildren().ToList();
if (collectionType == CollectionType.MusicVideos) if (collectionType == CollectionType.musicvideos)
{ {
movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false);
} }
if (collectionType == CollectionType.HomeVideos) if (collectionType == CollectionType.homevideos)
{ {
movie = FindMovie<Video>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false); movie = FindMovie<Video>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false);
} }
@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true); movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true);
} }
if (collectionType == CollectionType.Movies) if (collectionType == CollectionType.movies)
{ {
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true); movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true);
} }
@ -147,17 +147,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
Video item = null; Video item = null;
if (collectionType == CollectionType.MusicVideos) if (collectionType == CollectionType.musicvideos)
{ {
item = ResolveVideo<MusicVideo>(args, false); item = ResolveVideo<MusicVideo>(args, false);
} }
// To find a movie file, the collection type must be movies or boxsets // To find a movie file, the collection type must be movies or boxsets
else if (collectionType == CollectionType.Movies) else if (collectionType == CollectionType.movies)
{ {
item = ResolveVideo<Movie>(args, true); item = ResolveVideo<Movie>(args, true);
} }
else if (collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos) else if (collectionType == CollectionType.homevideos || collectionType == CollectionType.photos)
{ {
item = ResolveVideo<Video>(args, false); item = ResolveVideo<Video>(args, false);
} }
@ -195,12 +195,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null; return null;
} }
if (collectionType is CollectionType.MusicVideos) if (collectionType is CollectionType.musicvideos)
{ {
return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false); return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false);
} }
if (collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos) if (collectionType == CollectionType.homevideos || collectionType == CollectionType.photos)
{ {
return ResolveVideos<Video>(parent, files, false, collectionType, false); return ResolveVideos<Video>(parent, files, false, collectionType, false);
} }
@ -221,12 +221,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return ResolveVideos<Movie>(parent, files, false, collectionType, true); return ResolveVideos<Movie>(parent, files, false, collectionType, true);
} }
if (collectionType == CollectionType.Movies) if (collectionType == CollectionType.movies)
{ {
return ResolveVideos<Movie>(parent, files, true, collectionType, true); return ResolveVideos<Movie>(parent, files, true, collectionType, true);
} }
if (collectionType == CollectionType.TvShows) if (collectionType == CollectionType.tvshows)
{ {
return ResolveVideos<Episode>(parent, files, false, collectionType, true); return ResolveVideos<Episode>(parent, files, false, collectionType, true);
} }
@ -403,7 +403,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
var multiDiscFolders = new List<FileSystemMetadata>(); var multiDiscFolders = new List<FileSystemMetadata>();
var libraryOptions = args.LibraryOptions; var libraryOptions = args.LibraryOptions;
var supportPhotos = collectionType == CollectionType.HomeVideos && libraryOptions.EnablePhotos; var supportPhotos = collectionType == CollectionType.homevideos && libraryOptions.EnablePhotos;
var photos = new List<FileSystemMetadata>(); var photos = new List<FileSystemMetadata>();
// Search for a folder rip // Search for a folder rip
@ -459,7 +459,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ?? var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
new MultiItemResolverResult(); new MultiItemResolverResult();
var isPhotosCollection = collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos; var isPhotosCollection = collectionType == CollectionType.homevideos || collectionType == CollectionType.photos;
if (!isPhotosCollection && result.Items.Count == 1) if (!isPhotosCollection && result.Items.Count == 1)
{ {
var videoPath = result.Items[0].Path; var videoPath = result.Items[0].Path;

View File

@ -46,8 +46,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Must be an image file within a photo collection // Must be an image file within a photo collection
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();
if (collectionType == CollectionType.Photos if (collectionType == CollectionType.photos
|| (collectionType == CollectionType.HomeVideos && args.LibraryOptions.EnablePhotos)) || (collectionType == CollectionType.homevideos && args.LibraryOptions.EnablePhotos))
{ {
if (HasPhotos(args)) if (HasPhotos(args))
{ {

View File

@ -61,8 +61,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
// Must be an image file within a photo collection // Must be an image file within a photo collection
var collectionType = args.CollectionType; var collectionType = args.CollectionType;
if (collectionType == CollectionType.Photos if (collectionType == CollectionType.photos
|| (collectionType == CollectionType.HomeVideos && args.LibraryOptions.EnablePhotos)) || (collectionType == CollectionType.homevideos && args.LibraryOptions.EnablePhotos))
{ {
if (IsImageFile(args.Path, _imageProcessor)) if (IsImageFile(args.Path, _imageProcessor))
{ {

View File

@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
private CollectionType?[] _musicPlaylistCollectionTypes = private CollectionType?[] _musicPlaylistCollectionTypes =
{ {
null, null,
CollectionType.Music CollectionType.music
}; };
/// <inheritdoc/> /// <inheritdoc/>

View File

@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
// If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something // If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
// Also handle flat tv folders // Also handle flat tv folders
if (season is not null if (season is not null
|| args.GetCollectionType() == CollectionType.TvShows || args.GetCollectionType() == CollectionType.tvshows
|| args.HasParent<Series>()) || args.HasParent<Series>())
{ {
var episode = ResolveVideo<Episode>(args, false); var episode = ResolveVideo<Episode>(args, false);

View File

@ -60,11 +60,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
var seriesInfo = Naming.TV.SeriesResolver.Resolve(_namingOptions, args.Path); var seriesInfo = Naming.TV.SeriesResolver.Resolve(_namingOptions, args.Path);
var collectionType = args.GetCollectionType(); var collectionType = args.GetCollectionType();
if (collectionType == CollectionType.TvShows) if (collectionType == CollectionType.tvshows)
{ {
// TODO refactor into separate class or something, this is copied from LibraryManager.GetConfiguredContentType // TODO refactor into separate class or something, this is copied from LibraryManager.GetConfiguredContentType
var configuredContentType = args.GetConfiguredContentType(); var configuredContentType = args.GetConfiguredContentType();
if (configuredContentType != CollectionType.TvShows) if (configuredContentType != CollectionType.tvshows)
{ {
return new Series return new Series
{ {

View File

@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query) public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
{ {
User user = null; User user = null;
if (!query.UserId.Equals(default)) if (!query.UserId.IsEmpty())
{ {
user = _userManager.GetUserById(query.UserId); user = _userManager.GetUserById(query.UserId);
} }
@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.Library
if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist) if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist)
{ {
if (!searchQuery.ParentId.Equals(default)) if (!searchQuery.ParentId.IsEmpty())
{ {
searchQuery.AncestorIds = new[] { searchQuery.ParentId }; searchQuery.AncestorIds = new[] { searchQuery.ParentId };
searchQuery.ParentId = Guid.Empty; searchQuery.ParentId = Guid.Empty;

View File

@ -81,6 +81,53 @@ namespace Emby.Server.Implementations.Library
}); });
} }
public void SaveUserData(User user, BaseItem item, UpdateUserItemDataDto userDataDto, UserDataSaveReason reason)
{
ArgumentNullException.ThrowIfNull(user);
ArgumentNullException.ThrowIfNull(item);
ArgumentNullException.ThrowIfNull(reason);
ArgumentNullException.ThrowIfNull(userDataDto);
var userData = GetUserData(user, item);
if (userDataDto.PlaybackPositionTicks.HasValue)
{
userData.PlaybackPositionTicks = userDataDto.PlaybackPositionTicks.Value;
}
if (userDataDto.PlayCount.HasValue)
{
userData.PlayCount = userDataDto.PlayCount.Value;
}
if (userDataDto.IsFavorite.HasValue)
{
userData.IsFavorite = userDataDto.IsFavorite.Value;
}
if (userDataDto.Likes.HasValue)
{
userData.Likes = userDataDto.Likes.Value;
}
if (userDataDto.Played.HasValue)
{
userData.Played = userDataDto.Played.Value;
}
if (userDataDto.LastPlayedDate.HasValue)
{
userData.LastPlayedDate = userDataDto.LastPlayedDate.Value;
}
if (userDataDto.Rating.HasValue)
{
userData.Rating = userDataDto.Rating.Value;
}
SaveUserData(user, item, userData, reason, CancellationToken.None);
}
/// <summary> /// <summary>
/// Save the provided user data for the given user. Batch operation. Does not fire any events or update the cache. /// Save the provided user data for the given user. Batch operation. Does not fire any events or update the cache.
/// </summary> /// </summary>

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.Library
var folderViewType = collectionFolder?.CollectionType; var folderViewType = collectionFolder?.CollectionType;
// Playlist library requires special handling because the folder only references user playlists // Playlist library requires special handling because the folder only references user playlists
if (folderViewType == CollectionType.Playlists) if (folderViewType == CollectionType.playlists)
{ {
var items = folder.GetItemList(new InternalItemsQuery(user) var items = folder.GetItemList(new InternalItemsQuery(user)
{ {
@ -99,14 +100,14 @@ namespace Emby.Server.Implementations.Library
} }
} }
foreach (var viewType in new[] { CollectionType.Movies, CollectionType.TvShows }) foreach (var viewType in new[] { CollectionType.movies, CollectionType.tvshows })
{ {
var parents = groupedFolders.Where(i => i.CollectionType == viewType || i.CollectionType is null) var parents = groupedFolders.Where(i => i.CollectionType == viewType || i.CollectionType is null)
.ToList(); .ToList();
if (parents.Count > 0) if (parents.Count > 0)
{ {
var localizationKey = viewType == CollectionType.TvShows var localizationKey = viewType == CollectionType.tvshows
? "TvShows" ? "TvShows"
: "Movies"; : "Movies";
@ -117,7 +118,7 @@ namespace Emby.Server.Implementations.Library
if (_config.Configuration.EnableFolderView) if (_config.Configuration.EnableFolderView)
{ {
var name = _localizationManager.GetLocalizedString("Folders"); var name = _localizationManager.GetLocalizedString("Folders");
list.Add(_libraryManager.GetNamedView(name, CollectionType.Folders, string.Empty)); list.Add(_libraryManager.GetNamedView(name, CollectionType.folders, string.Empty));
} }
if (query.IncludeExternalContent) if (query.IncludeExternalContent)
@ -151,7 +152,7 @@ namespace Emby.Server.Implementations.Library
var index = Array.IndexOf(orders, i.Id); var index = Array.IndexOf(orders, i.Id);
if (index == -1 if (index == -1
&& i is UserView view && i is UserView view
&& !view.DisplayParentId.Equals(default)) && !view.DisplayParentId.IsEmpty())
{ {
index = Array.IndexOf(orders, view.DisplayParentId); index = Array.IndexOf(orders, view.DisplayParentId);
} }
@ -253,7 +254,7 @@ namespace Emby.Server.Implementations.Library
var parents = new List<BaseItem>(); var parents = new List<BaseItem>();
if (!parentId.Equals(default)) if (!parentId.IsEmpty())
{ {
var parentItem = _libraryManager.GetItemById(parentId); var parentItem = _libraryManager.GetItemById(parentId);
if (parentItem is Channel) if (parentItem is Channel)
@ -279,7 +280,7 @@ namespace Emby.Server.Implementations.Library
var isPlayed = request.IsPlayed; var isPlayed = request.IsPlayed;
if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.Music)) if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.music))
{ {
isPlayed = null; isPlayed = null;
} }
@ -305,11 +306,11 @@ namespace Emby.Server.Implementations.Library
var hasCollectionType = parents.OfType<UserView>().ToArray(); var hasCollectionType = parents.OfType<UserView>().ToArray();
if (hasCollectionType.Length > 0) if (hasCollectionType.Length > 0)
{ {
if (hasCollectionType.All(i => i.CollectionType == CollectionType.Movies)) if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies))
{ {
includeItemTypes = new[] { BaseItemKind.Movie }; includeItemTypes = new[] { BaseItemKind.Movie };
} }
else if (hasCollectionType.All(i => i.CollectionType == CollectionType.TvShows)) else if (hasCollectionType.All(i => i.CollectionType == CollectionType.tvshows))
{ {
includeItemTypes = new[] { BaseItemKind.Episode }; includeItemTypes = new[] { BaseItemKind.Episode };
} }
@ -324,18 +325,18 @@ namespace Emby.Server.Implementations.Library
{ {
switch (parent.CollectionType) switch (parent.CollectionType)
{ {
case CollectionType.Books: case CollectionType.books:
mediaTypes.Add(MediaType.Book); mediaTypes.Add(MediaType.Book);
mediaTypes.Add(MediaType.Audio); mediaTypes.Add(MediaType.Audio);
break; break;
case CollectionType.Music: case CollectionType.music:
mediaTypes.Add(MediaType.Audio); mediaTypes.Add(MediaType.Audio);
break; break;
case CollectionType.Photos: case CollectionType.photos:
mediaTypes.Add(MediaType.Photo); mediaTypes.Add(MediaType.Photo);
mediaTypes.Add(MediaType.Video); mediaTypes.Add(MediaType.Video);
break; break;
case CollectionType.HomeVideos: case CollectionType.homevideos:
mediaTypes.Add(MediaType.Photo); mediaTypes.Add(MediaType.Photo);
mediaTypes.Add(MediaType.Video); mediaTypes.Add(MediaType.Video);
break; break;

View File

@ -1,25 +0,0 @@
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.LiveTv;
namespace Emby.Server.Implementations.LiveTv
{
/// <summary>
/// <see cref="IConfigurationFactory" /> implementation for <see cref="LiveTvOptions" />.
/// </summary>
public class LiveTvConfigurationFactory : IConfigurationFactory
{
/// <inheritdoc />
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new ConfigurationStore[]
{
new ConfigurationStore
{
ConfigurationType = typeof(LiveTvOptions),
Key = "livetv"
}
};
}
}
}

View File

@ -124,5 +124,7 @@
"TaskKeyframeExtractorDescription": "Extreu fotogrames clau dels fitxers de vídeo per crear llistes de reproducció HLS més precises. Aquesta tasca pot durar molt de temps.", "TaskKeyframeExtractorDescription": "Extreu fotogrames clau dels fitxers de vídeo per crear llistes de reproducció HLS més precises. Aquesta tasca pot durar molt de temps.",
"TaskKeyframeExtractor": "Extractor de fotogrames clau", "TaskKeyframeExtractor": "Extractor de fotogrames clau",
"External": "Extern", "External": "Extern",
"HearingImpaired": "Discapacitat auditiva" "HearingImpaired": "Discapacitat auditiva",
"TaskRefreshTrickplayImages": "Generar miniatures de línia de temps",
"TaskRefreshTrickplayImagesDescription": "Crear miniatures de línia de temps per vídeos en les biblioteques habilitades."
} }

View File

@ -83,8 +83,8 @@
"UserDeletedWithName": "Uživatel {0} byl smazán", "UserDeletedWithName": "Uživatel {0} byl smazán",
"UserDownloadingItemWithValues": "{0} stahuje {1}", "UserDownloadingItemWithValues": "{0} stahuje {1}",
"UserLockedOutWithName": "Uživatel {0} byl odemčen", "UserLockedOutWithName": "Uživatel {0} byl odemčen",
"UserOfflineFromDevice": "{0} se odpojil od {1}", "UserOfflineFromDevice": "{0} se odpojil ze zařízení {1}",
"UserOnlineFromDevice": "{0} se připojil z {1}", "UserOnlineFromDevice": "{0} se připojil ze zařízení {1}",
"UserPasswordChangedWithName": "Provedena změna hesla pro uživatele {0}", "UserPasswordChangedWithName": "Provedena změna hesla pro uživatele {0}",
"UserPolicyUpdatedWithName": "Zásady uživatele pro {0} byly aktualizovány", "UserPolicyUpdatedWithName": "Zásady uživatele pro {0} byly aktualizovány",
"UserStartedPlayingItemWithValues": "{0} spustil přehrávání {1}", "UserStartedPlayingItemWithValues": "{0} spustil přehrávání {1}",

View File

@ -121,8 +121,8 @@
"Default": "Standard", "Default": "Standard",
"TaskOptimizeDatabaseDescription": "Komprimiert die Datenbank und trimmt den freien Speicherplatz. Die Ausführung dieser Aufgabe nach dem Scannen der Bibliothek oder nach anderen Änderungen, die Datenbankänderungen implizieren, kann die Leistung verbessern.", "TaskOptimizeDatabaseDescription": "Komprimiert die Datenbank und trimmt den freien Speicherplatz. Die Ausführung dieser Aufgabe nach dem Scannen der Bibliothek oder nach anderen Änderungen, die Datenbankänderungen implizieren, kann die Leistung verbessern.",
"TaskOptimizeDatabase": "Datenbank optimieren", "TaskOptimizeDatabase": "Datenbank optimieren",
"TaskKeyframeExtractorDescription": "Extrahiere Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Dieser Vorgang kann sehr lange dauern.", "TaskKeyframeExtractorDescription": "Extrahiert Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Dieser Vorgang kann sehr lange dauern.",
"TaskKeyframeExtractor": "Keyframe Extraktor", "TaskKeyframeExtractor": "Keyframe-Extraktor",
"External": "Extern", "External": "Extern",
"HearingImpaired": "Hörgeschädigt", "HearingImpaired": "Hörgeschädigt",
"TaskRefreshTrickplayImages": "Trickplay-Bilder generieren", "TaskRefreshTrickplayImages": "Trickplay-Bilder generieren",

View File

@ -123,5 +123,7 @@
"External": "Externo", "External": "Externo",
"TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reproducción HLS más precisas. Esta tarea puede durar mucho tiempo.", "TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reproducción HLS más precisas. Esta tarea puede durar mucho tiempo.",
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave", "TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
"HearingImpaired": "Discapacidad auditiva" "HearingImpaired": "Discapacidad auditiva",
"TaskRefreshTrickplayImagesDescription": "Crea previsualizaciones para la barra de reproducción en las bibliotecas habilitadas.",
"TaskRefreshTrickplayImages": "Generar imágenes de la barra de reproducción"
} }

View File

@ -124,5 +124,7 @@
"TaskKeyframeExtractorDescription": "فریم های کلیدی را از فایل های ویدئویی استخراج می کند تا لیست های پخش HLS دقیق تری ایجاد کند. این کار ممکن است برای مدت طولانی اجرا شود.", "TaskKeyframeExtractorDescription": "فریم های کلیدی را از فایل های ویدئویی استخراج می کند تا لیست های پخش HLS دقیق تری ایجاد کند. این کار ممکن است برای مدت طولانی اجرا شود.",
"TaskKeyframeExtractor": "استخراج کننده فریم کلیدی", "TaskKeyframeExtractor": "استخراج کننده فریم کلیدی",
"External": "خارجی", "External": "خارجی",
"HearingImpaired": "مشکل شنوایی" "HearingImpaired": "مشکل شنوایی",
"TaskRefreshTrickplayImages": "تولید تصاویر Trickplay",
"TaskRefreshTrickplayImagesDescription": "تولید پیش‌نمایش های trickplay برای ویدیو های فعال شده در کتابخانه."
} }

View File

@ -124,5 +124,6 @@
"TaskKeyframeExtractor": "Tagabunot ng Keyframe", "TaskKeyframeExtractor": "Tagabunot ng Keyframe",
"TaskKeyframeExtractorDescription": "Nagbubunot ng keyframe mula sa mga bidyo upang makabuo ng mas tumpak na HLS playlist. Maaaring matagal ito gawin.", "TaskKeyframeExtractorDescription": "Nagbubunot ng keyframe mula sa mga bidyo upang makabuo ng mas tumpak na HLS playlist. Maaaring matagal ito gawin.",
"External": "External", "External": "External",
"TaskRefreshTrickplayImages": "Gumawa ng Trickplay na Imahe" "TaskRefreshTrickplayImages": "Gumawa ng Trickplay na Imahe",
"TaskRefreshTrickplayImagesDescription": "Nanggagawa ng mga trickplay prebiyu para sa mga bidyo sa pinaganang mga aklatan."
} }

View File

@ -0,0 +1,39 @@
{
"TasksLibraryCategory": "Գրադարան",
"TasksApplicationCategory": "Հավելված",
"TaskCleanActivityLog": "Մաքրել ակտիվության մատյանը",
"Application": "Հավելված",
"AuthenticationSucceededWithUserName": "{0} հաջողությամբ վավերականացվել են",
"Books": "Գրքեր",
"CameraImageUploadedFrom": "Նոր լուսանկար է վերբեռնվել {0}-ի կողմից",
"Channels": "Ալիքներ",
"DeviceOfflineWithName": "{0}ը անջատվեց",
"External": "Արտաքին",
"FailedLoginAttemptWithUserName": "Ձախողված մուտքի փործ {0}-ի կողմից",
"Folders": "Պանակներ",
"HeaderContinueWatching": "Շարունակել դիտումը",
"Inherit": "Ժառանգել",
"ItemAddedWithName": "{0}ը ավացված է գրադարանի մեջ",
"ItemRemovedWithName": "{0}ը հեռացված է գրադարանից",
"LabelIpAddressValue": "IP հասցե` {0}",
"Movies": "Ֆիլմեր",
"Music": "Երաժշտություն",
"NameSeasonNumber": "Սեզոն {0}",
"Photos": "Լուսանկարներ",
"PluginInstalledWithName": "{0}ն տեղադրված է",
"Songs": "Երգեր",
"System": "Համակարգ",
"TvShows": "Հեռուստասերիալներ",
"User": "Օգտատեր",
"VersionNumber": "Տարբերակ {0}",
"TasksMaintenanceCategory": "Սպասարկում",
"TasksChannelsCategory": "Ինտերնետային ալիքներ",
"TaskRefreshPeople": "Թարմացնել մարդկանց",
"TaskRefreshChannels": "Թարմացնել ալիքները",
"TaskDownloadMissingSubtitles": "Ներբեռնել պակասող ենթագրերը",
"Albums": "Ալբոմներ",
"AppDeviceValues": "Հավելված` {0}, Սարք `{1}",
"ChapterNameValue": "Գլուխ {0}",
"Collections": "Հավաքածուներ",
"DeviceOnlineWithName": "{0}-ն միացված է"
}

View File

@ -4,9 +4,9 @@
"HeaderFavoriteAlbums": "რჩეული ალბომები", "HeaderFavoriteAlbums": "რჩეული ალბომები",
"TasksApplicationCategory": "აპლიკაცია", "TasksApplicationCategory": "აპლიკაცია",
"Albums": "ალბომები", "Albums": "ალბომები",
"AppDeviceValues": "აპი: {0}, მოწყობილობა: {1}", "AppDeviceValues": "აპკაცია: {0}, მოწყობილობა: {1}",
"Application": "აპლიკაცია", "Application": "აპლიკაცია",
"Artists": "შემსრულებლები", "Artists": "არტისტი",
"AuthenticationSucceededWithUserName": "{0} -ის ავთენტიკაცია წარმატებულია", "AuthenticationSucceededWithUserName": "{0} -ის ავთენტიკაცია წარმატებულია",
"Books": "წიგნები", "Books": "წიგნები",
"Forced": "ძალით", "Forced": "ძალით",
@ -123,5 +123,7 @@
"TaskUpdatePluginsDescription": "ავტომატურად განახლებადად მონიშნული დამატებების განახლებების გადმოწერა და დაყენება.", "TaskUpdatePluginsDescription": "ავტომატურად განახლებადად მონიშნული დამატებების განახლებების გადმოწერა და დაყენება.",
"TaskCleanTranscodeDescription": "ერთ დღეზე უფრო ძველი ტრანსკოდირების ფაილების წაშლა.", "TaskCleanTranscodeDescription": "ერთ დღეზე უფრო ძველი ტრანსკოდირების ფაილების წაშლა.",
"TaskDownloadMissingSubtitlesDescription": "მეტამონაცემებზე დაყრდნობით ინტერნეტში ნაკლული სუბტიტრების ძებნა.", "TaskDownloadMissingSubtitlesDescription": "მეტამონაცემებზე დაყრდნობით ინტერნეტში ნაკლული სუბტიტრების ძებნა.",
"TaskOptimizeDatabaseDescription": "ბაზს შეკუშვა და ადგილის გათავისუფლება. ამ ამოცანის ბიბლიოთეკის სკანირების ან ნებისმიერი ცვლილების, რომელიც ბაზაში რამეს აკეთებს, გაშვებას შეუძლია ბაზის წარმადობა გაზარდოს." "TaskOptimizeDatabaseDescription": "ბაზს შეკუშვა და ადგილის გათავისუფლება. ამ ამოცანის ბიბლიოთეკის სკანირების ან ნებისმიერი ცვლილების, რომელიც ბაზაში რამეს აკეთებს, გაშვებას შეუძლია ბაზის წარმადობა გაზარდოს.",
"TaskRefreshTrickplayImagesDescription": "ქმნის trickplay წინასწარ ხედებს ვიდეოებისთვის ჩართულ ბიბლიოთეკებში.",
"TaskRefreshTrickplayImages": "Trickplay სურათების გენერირება"
} }

View File

@ -123,5 +123,7 @@
"External": "Ārējais", "External": "Ārējais",
"HearingImpaired": "Ar dzirdes traucējumiem", "HearingImpaired": "Ar dzirdes traucējumiem",
"TaskKeyframeExtractor": "Atslēgkadru ekstraktors", "TaskKeyframeExtractor": "Atslēgkadru ekstraktors",
"TaskKeyframeExtractorDescription": "Ekstraktē atslēgkadrus no video failiem lai izveidotu precīzākus HLS atskaņošanas sarakstus. Šis process var būt ilgs." "TaskKeyframeExtractorDescription": "Ekstraktē atslēgkadrus no video failiem lai izveidotu precīzākus HLS atskaņošanas sarakstus. Šis process var būt ilgs.",
"TaskRefreshTrickplayImages": "Ģenerēt partīšanas attēlus",
"TaskRefreshTrickplayImagesDescription": "Izveido priekšskatījumus videoklipu pārtīšanai iespējotajās bibliotēkās."
} }

View File

@ -5,7 +5,7 @@
"Artists": "Artister", "Artists": "Artister",
"AuthenticationSucceededWithUserName": "{0} har logget inn", "AuthenticationSucceededWithUserName": "{0} har logget inn",
"Books": "Bøker", "Books": "Bøker",
"CameraImageUploadedFrom": "Et nytt kamerabilde er lastet opp fra {0}", "CameraImageUploadedFrom": "Et nytt kamerabilde har blitt lastet opp fra {0}",
"Channels": "Kanaler", "Channels": "Kanaler",
"ChapterNameValue": "Kapittel {0}", "ChapterNameValue": "Kapittel {0}",
"Collections": "Samlinger", "Collections": "Samlinger",
@ -32,10 +32,10 @@
"LabelIpAddressValue": "IP-adresse: {0}", "LabelIpAddressValue": "IP-adresse: {0}",
"LabelRunningTimeValue": "Spilletid {0}", "LabelRunningTimeValue": "Spilletid {0}",
"Latest": "Siste", "Latest": "Siste",
"MessageApplicationUpdated": "Jellyfin-tjeneren har blitt oppdatert", "MessageApplicationUpdated": "Jellyfin-serveren har blitt oppdatert",
"MessageApplicationUpdatedTo": "Jellyfin-tjeneren ble oppdatert til {0}", "MessageApplicationUpdatedTo": "Jellyfin-serveren ble oppdatert til {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Tjenerkonfigurasjonsseksjon {0} har blitt oppdatert", "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurasjonsseksjon {0} har blitt oppdatert",
"MessageServerConfigurationUpdated": "Tjenerkonfigurasjon er oppdatert", "MessageServerConfigurationUpdated": "Serverkonfigurasjon har blitt oppdatert",
"MixedContent": "Blandet innhold", "MixedContent": "Blandet innhold",
"Movies": "Filmer", "Movies": "Filmer",
"Music": "Musikk", "Music": "Musikk",
@ -43,19 +43,19 @@
"NameInstallFailed": "Installasjonen av {0} mislyktes", "NameInstallFailed": "Installasjonen av {0} mislyktes",
"NameSeasonNumber": "Sesong {0}", "NameSeasonNumber": "Sesong {0}",
"NameSeasonUnknown": "Ukjent sesong", "NameSeasonUnknown": "Ukjent sesong",
"NewVersionIsAvailable": "En ny versjon av Jellyfin-tjeneren er tilgjengelig for nedlasting.", "NewVersionIsAvailable": "En ny versjon av Jellyfin Server er tilgjengelig for nedlasting.",
"NotificationOptionApplicationUpdateAvailable": "En programvareoppdatering er tilgjengelig", "NotificationOptionApplicationUpdateAvailable": "En programvareoppdatering er tilgjengelig",
"NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering installert", "NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering installert",
"NotificationOptionAudioPlayback": "Lydavspilling startet", "NotificationOptionAudioPlayback": "Lydavspilling startet",
"NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppet", "NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppet",
"NotificationOptionCameraImageUploaded": "Kamerabilde lastet opp", "NotificationOptionCameraImageUploaded": "Kamerabilde lastet opp",
"NotificationOptionInstallationFailed": "Installasjonen feilet", "NotificationOptionInstallationFailed": "Installasjonsfeil",
"NotificationOptionNewLibraryContent": "Nytt innhold lagt til", "NotificationOptionNewLibraryContent": "Nytt innhold lagt til",
"NotificationOptionPluginError": "Programvareutvidelsesfeil", "NotificationOptionPluginError": "Programvareutvidelsesfeil",
"NotificationOptionPluginInstalled": "Programvareutvidelse installert", "NotificationOptionPluginInstalled": "Programvareutvidelse installert",
"NotificationOptionPluginUninstalled": "Programvareutvidelse avinstallert", "NotificationOptionPluginUninstalled": "Programvareutvidelse avinstallert",
"NotificationOptionPluginUpdateInstalled": "Programvareutvidelsesoppdatering installert", "NotificationOptionPluginUpdateInstalled": "Programvareutvidelsesoppdatering installert",
"NotificationOptionServerRestartRequired": "Tjeneromstart er nødvendig", "NotificationOptionServerRestartRequired": "Serveromstart er nødvendig",
"NotificationOptionTaskFailed": "Feil under utføring av planlagt oppgave", "NotificationOptionTaskFailed": "Feil under utføring av planlagt oppgave",
"NotificationOptionUserLockedOut": "Bruker er utestengt", "NotificationOptionUserLockedOut": "Bruker er utestengt",
"NotificationOptionVideoPlayback": "Videoavspilling startet", "NotificationOptionVideoPlayback": "Videoavspilling startet",
@ -70,9 +70,9 @@
"ScheduledTaskFailedWithName": "{0} mislykkes", "ScheduledTaskFailedWithName": "{0} mislykkes",
"ScheduledTaskStartedWithName": "{0} startet", "ScheduledTaskStartedWithName": "{0} startet",
"ServerNameNeedsToBeRestarted": "{0} må startes på nytt", "ServerNameNeedsToBeRestarted": "{0} må startes på nytt",
"Shows": "Program", "Shows": "Serier",
"Songs": "Sanger", "Songs": "Sanger",
"StartupEmbyServerIsLoading": "Jellyfin-tjener laster. Prøv igjen snart.", "StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.",
"SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}", "SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}",
"SubtitleDownloadFailureFromForItem": "Kunne ikke laste ned undertekster fra {0} for {1}", "SubtitleDownloadFailureFromForItem": "Kunne ikke laste ned undertekster fra {0} for {1}",
"Sync": "Synkroniser", "Sync": "Synkroniser",
@ -124,5 +124,7 @@
"TaskKeyframeExtractorDescription": "Trekker ut nøkkelbilder fra videofiler for å skape mere nøyaktige HLS-spillelister. Denne oppgaven kan ta lang tid.", "TaskKeyframeExtractorDescription": "Trekker ut nøkkelbilder fra videofiler for å skape mere nøyaktige HLS-spillelister. Denne oppgaven kan ta lang tid.",
"TaskKeyframeExtractor": "Nøkkelbilde-uttrekker", "TaskKeyframeExtractor": "Nøkkelbilde-uttrekker",
"External": "Ekstern", "External": "Ekstern",
"HearingImpaired": "Hørselshemmet" "HearingImpaired": "Hørselshemmet",
"TaskRefreshTrickplayImages": "Generer Trickplay bilder",
"TaskRefreshTrickplayImagesDescription": "Oppretter trickplay-forhåndsvisninger for videoer i aktiverte biblioteker."
} }

View File

@ -117,5 +117,6 @@
"TaskCleanActivityLog": "Slett aktivitetslogg", "TaskCleanActivityLog": "Slett aktivitetslogg",
"Undefined": "Udefinert", "Undefined": "Udefinert",
"Forced": "Tvungen", "Forced": "Tvungen",
"Default": "Standard" "Default": "Standard",
"External": "Ekstern"
} }

View File

@ -43,7 +43,7 @@
"NameInstallFailed": "{0} installationen misslyckades", "NameInstallFailed": "{0} installationen misslyckades",
"NameSeasonNumber": "Säsong {0}", "NameSeasonNumber": "Säsong {0}",
"NameSeasonUnknown": "Okänd säsong", "NameSeasonUnknown": "Okänd säsong",
"NewVersionIsAvailable": "En ny version av Jellyfin Server är tillgänglig att hämta.", "NewVersionIsAvailable": "En ny version av Jellyfin Server är tillgänglig för nedladdning.",
"NotificationOptionApplicationUpdateAvailable": "Ny programversion tillgänglig", "NotificationOptionApplicationUpdateAvailable": "Ny programversion tillgänglig",
"NotificationOptionApplicationUpdateInstalled": "Programuppdatering installerad", "NotificationOptionApplicationUpdateInstalled": "Programuppdatering installerad",
"NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats", "NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats",
@ -74,7 +74,7 @@
"Songs": "Låtar", "Songs": "Låtar",
"StartupEmbyServerIsLoading": "Jellyfin Server arbetar. Pröva igen snart.", "StartupEmbyServerIsLoading": "Jellyfin Server arbetar. Pröva igen snart.",
"SubtitleDownloadFailureForItem": "Nerladdning av undertexter för {0} misslyckades", "SubtitleDownloadFailureForItem": "Nerladdning av undertexter för {0} misslyckades",
"SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} för {1}", "SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} till {1}",
"Sync": "Synk", "Sync": "Synk",
"System": "System", "System": "System",
"TvShows": "TV-serier", "TvShows": "TV-serier",

View File

@ -38,5 +38,18 @@
"HeaderFavoriteSongs": "ఇష్టమైన పాటలు", "HeaderFavoriteSongs": "ఇష్టమైన పాటలు",
"HeaderLiveTV": "ప్రత్యక్ష TV", "HeaderLiveTV": "ప్రత్యక్ష TV",
"HeaderNextUp": "తదుపరి", "HeaderNextUp": "తదుపరి",
"HeaderRecordingGroups": "రికార్డింగ్ గుంపులు" "HeaderRecordingGroups": "రికార్డింగ్ గుంపులు",
"MessageApplicationUpdated": "జెల్లీఫిన్ సర్వర్ అప్‌డేట్ చేయడం పూర్తి అయ్యింది",
"MessageApplicationUpdatedTo": "జెల్లీఫిన్ సర్వర్ {0} వెర్షన్ కి అప్‌డేట్ చెయ్యబడింది",
"MessageServerConfigurationUpdated": "సర్వర్ కన్ఫిగరేషన్ అప్డేట్ చేయబడింది",
"NewVersionIsAvailable": "జెల్లీఫిన్ సర్వర్ యొక్క కొత్త వెర్షన్ డౌన్‌లోడ్ చేసుకోవడానికి అందుబాటులో ఉంది.",
"NotificationOptionApplicationUpdateInstalled": "అప్లికేషన్ అప్‌డేట్ ఇన్‌స్టాల్ చేయబడింది",
"ItemAddedWithName": "{0} లైబ్రరీకి జోడించబడింది",
"ItemRemovedWithName": "లైబ్రరీ నుండి {0} తీసివేయబడింది",
"LabelIpAddressValue": "ఐపీ చిరునామా: {0}",
"LabelRunningTimeValue": "నడుస్తున్న సమయం: {0}",
"Latest": "తాజా",
"NameInstallFailed": "{0} ఇన్‌స్టాలేషన్ విఫలమైంది",
"NameSeasonUnknown": "భాగం తెలియదు",
"NotificationOptionApplicationUpdateAvailable": "అప్లికేషన్ అప్‌డేట్ అందుబాటులో ఉంది"
} }

View File

@ -89,7 +89,7 @@
"UserPolicyUpdatedWithName": "{0} için kullanıcı politikası güncellendi", "UserPolicyUpdatedWithName": "{0} için kullanıcı politikası güncellendi",
"UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor", "UserStartedPlayingItemWithValues": "{0}, {2} cihazında {1} izliyor",
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi", "UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
"ValueHasBeenAddedToLibrary": "Medya kütüphanenize {0} eklendi", "ValueHasBeenAddedToLibrary": "{0} medya kütüphanenize eklendi",
"ValueSpecialEpisodeName": "Özel - {0}", "ValueSpecialEpisodeName": "Özel - {0}",
"VersionNumber": "Sürüm {0}", "VersionNumber": "Sürüm {0}",
"TaskCleanCache": "Önbellek Dizinini Temizle", "TaskCleanCache": "Önbellek Dizinini Temizle",
@ -111,7 +111,7 @@
"TaskCleanLogs": "Günlük Dizinini Temizle", "TaskCleanLogs": "Günlük Dizinini Temizle",
"TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve ortam bilgilerini yeniler.", "TaskRefreshLibraryDescription": "Medya kütüphanenize eklenen yeni dosyaları arar ve ortam bilgilerini yeniler.",
"TaskRefreshLibrary": "Medya Kütüphanesini Tara", "TaskRefreshLibrary": "Medya Kütüphanesini Tara",
"TaskRefreshChapterImagesDescription": "Sahnelere ayrılmış videolar için küçük resimler oluştur.", "TaskRefreshChapterImagesDescription": "Bölümlere ayrılmış videolar için küçük resimler oluştur.",
"TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar", "TaskRefreshChapterImages": "Bölüm Resimlerini Çıkar",
"TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.", "TaskCleanCacheDescription": "Sistem tarafından artık ihtiyaç duyulmayan önbellek dosyalarını siler.",
"TaskCleanActivityLog": "Etkinlik Günlüğünü Temizle", "TaskCleanActivityLog": "Etkinlik Günlüğünü Temizle",

View File

@ -18,16 +18,16 @@
"HeaderContinueWatching": "Продовжити перегляд", "HeaderContinueWatching": "Продовжити перегляд",
"HeaderAlbumArtists": "Виконавці альбому", "HeaderAlbumArtists": "Виконавці альбому",
"Genres": "Жанри", "Genres": "Жанри",
"Folders": "Каталоги", "Folders": "Теки",
"Favorites": "Обрані", "Favorites": "Обрані",
"DeviceOnlineWithName": "Пристрій {0} підключився", "DeviceOnlineWithName": "Пристрій {0} підключився",
"DeviceOfflineWithName": "Пристрій {0} відключився", "DeviceOfflineWithName": "Пристрій {0} відключився",
"Collections": "Добірки", "Collections": "Колекції",
"ChapterNameValue": "Розділ {0}", "ChapterNameValue": "Сцена {0}",
"Channels": "Канали", "Channels": "Канали",
"CameraImageUploadedFrom": "Нова фотографія завантажена з {0}", "CameraImageUploadedFrom": "Нову фотографію завантажено з {0}",
"Books": "Книги", "Books": "Книги",
"AuthenticationSucceededWithUserName": "{0} успішно автентифіковано", "AuthenticationSucceededWithUserName": "{0} успішно авторизовано",
"Artists": "Виконавці", "Artists": "Виконавці",
"Application": "Додаток", "Application": "Додаток",
"AppDeviceValues": "Додаток: {0}, Пристрій: {1}", "AppDeviceValues": "Додаток: {0}, Пристрій: {1}",
@ -83,7 +83,7 @@
"SubtitleDownloadFailureFromForItem": "Не вдалося завантажити субтитри з {0} для {1}", "SubtitleDownloadFailureFromForItem": "Не вдалося завантажити субтитри з {0} для {1}",
"StartupEmbyServerIsLoading": "Jellyfin Server завантажується. Будь ласка, спробуйте трішки пізніше.", "StartupEmbyServerIsLoading": "Jellyfin Server завантажується. Будь ласка, спробуйте трішки пізніше.",
"Songs": "Пісні", "Songs": "Пісні",
"Shows": "Шоу", "Shows": "Телепередачі",
"ServerNameNeedsToBeRestarted": "{0} потрібно перезапустити", "ServerNameNeedsToBeRestarted": "{0} потрібно перезапустити",
"ScheduledTaskStartedWithName": "{0} розпочато", "ScheduledTaskStartedWithName": "{0} розпочато",
"ScheduledTaskFailedWithName": "{0} незавершено, збій", "ScheduledTaskFailedWithName": "{0} незавершено, збій",

View File

@ -0,0 +1,3 @@
{
"Books": "کتابیں"
}

View File

@ -1,15 +1,15 @@
{ {
"Albums": "專輯", "Albums": "專輯",
"AppDeviceValues": "程式: {0}, 設備: {1}", "AppDeviceValues": "程式{0},設備:{1}",
"Application": "應用程式", "Application": "應用程式",
"Artists": "藝人", "Artists": "藝人",
"AuthenticationSucceededWithUserName": "{0} 授權成功", "AuthenticationSucceededWithUserName": "成功授權 {0}",
"Books": "書籍", "Books": "書籍",
"CameraImageUploadedFrom": "{0} 成功上傳一張新照片", "CameraImageUploadedFrom": "{0} 成功上傳一張新照片",
"Channels": "頻道", "Channels": "頻道",
"ChapterNameValue": "第 {0} 章", "ChapterNameValue": "第 {0} 章",
"Collections": "系列", "Collections": "系列",
"DeviceOfflineWithName": "{0} 已連接", "DeviceOfflineWithName": "{0} 已斷連接",
"DeviceOnlineWithName": "{0} 已連接", "DeviceOnlineWithName": "{0} 已連接",
"FailedLoginAttemptWithUserName": "{0} 登入失敗", "FailedLoginAttemptWithUserName": "{0} 登入失敗",
"Favorites": "我的最愛", "Favorites": "我的最愛",
@ -27,15 +27,15 @@
"HeaderRecordingGroups": "錄製組", "HeaderRecordingGroups": "錄製組",
"HomeVideos": "家庭影片", "HomeVideos": "家庭影片",
"Inherit": "繼承", "Inherit": "繼承",
"ItemAddedWithName": "{0} 已被加至媒體庫", "ItemAddedWithName": "{0} 已被至媒體庫",
"ItemRemovedWithName": "{0} 已從媒體庫移除", "ItemRemovedWithName": "{0} 已從媒體庫移除",
"LabelIpAddressValue": "IP 地址: {0}", "LabelIpAddressValue": "IP 地址{0}",
"LabelRunningTimeValue": "運行時間: {0}", "LabelRunningTimeValue": "運作時間:{0}",
"Latest": "最新", "Latest": "最新",
"MessageApplicationUpdated": "Jellyfin 已被更新", "MessageApplicationUpdated": "Jellyfin 已被更新",
"MessageApplicationUpdatedTo": "Jellyfin 已被更新至 {0}", "MessageApplicationUpdatedTo": "Jellyfin 已被更新至 {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 已被更新", "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 已被更新",
"MessageServerConfigurationUpdated": "伺服器設定已經被更新", "MessageServerConfigurationUpdated": "已更新伺服器設定",
"MixedContent": "混合內容", "MixedContent": "混合內容",
"Movies": "電影", "Movies": "電影",
"Music": "音樂", "Music": "音樂",
@ -43,23 +43,23 @@
"NameInstallFailed": "{0} 安裝失敗", "NameInstallFailed": "{0} 安裝失敗",
"NameSeasonNumber": "第 {0} 季", "NameSeasonNumber": "第 {0} 季",
"NameSeasonUnknown": "未知的季度", "NameSeasonUnknown": "未知的季度",
"NewVersionIsAvailable": "有新版本的 Jellyfin 可供下載。", "NewVersionIsAvailable": "有新版本的 Jellyfin 可供下載。",
"NotificationOptionApplicationUpdateAvailable": "有可用的更新", "NotificationOptionApplicationUpdateAvailable": "有可用的更新",
"NotificationOptionApplicationUpdateInstalled": "應用程式已被更新", "NotificationOptionApplicationUpdateInstalled": "完成更新應用程式",
"NotificationOptionAudioPlayback": "開始播放音訊", "NotificationOptionAudioPlayback": "播放音訊",
"NotificationOptionAudioPlaybackStopped": "停止播放音訊", "NotificationOptionAudioPlaybackStopped": "停止播放音訊",
"NotificationOptionCameraImageUploaded": "相片已被上傳", "NotificationOptionCameraImageUploaded": "相片上傳",
"NotificationOptionInstallationFailed": "安裝失敗", "NotificationOptionInstallationFailed": "安裝失敗",
"NotificationOptionNewLibraryContent": "已添加新内容", "NotificationOptionNewLibraryContent": "新增媒體",
"NotificationOptionPluginError": "插件出現錯誤", "NotificationOptionPluginError": "插件錯誤",
"NotificationOptionPluginInstalled": "插件已被安裝", "NotificationOptionPluginInstalled": "安裝插件",
"NotificationOptionPluginUninstalled": "插件已被移除", "NotificationOptionPluginUninstalled": "解除安裝插件",
"NotificationOptionPluginUpdateInstalled": "插件已被更新", "NotificationOptionPluginUpdateInstalled": "完成更新插件",
"NotificationOptionServerRestartRequired": "伺服器需要重啟", "NotificationOptionServerRestartRequired": "伺服器需要重啟",
"NotificationOptionTaskFailed": "排程任務執行失敗", "NotificationOptionTaskFailed": "排程工作執行失敗",
"NotificationOptionUserLockedOut": "用戶已被鎖定", "NotificationOptionUserLockedOut": "封鎖用戶",
"NotificationOptionVideoPlayback": "開始播放影片", "NotificationOptionVideoPlayback": "播放影片",
"NotificationOptionVideoPlaybackStopped": "停止播放影片", "NotificationOptionVideoPlaybackStopped": "停止播放影片",
"Photos": "相片", "Photos": "相片",
"Playlists": "播放清單", "Playlists": "播放清單",
"Plugin": "插件", "Plugin": "插件",
@ -68,7 +68,7 @@
"PluginUpdatedWithName": "已更新 {0}", "PluginUpdatedWithName": "已更新 {0}",
"ProviderValue": "提供者:{0}", "ProviderValue": "提供者:{0}",
"ScheduledTaskFailedWithName": "{0} 執行失敗", "ScheduledTaskFailedWithName": "{0} 執行失敗",
"ScheduledTaskStartedWithName": "{0} 開始執行", "ScheduledTaskStartedWithName": "開始執行 {0}",
"ServerNameNeedsToBeRestarted": "{0} 需要重啟", "ServerNameNeedsToBeRestarted": "{0} 需要重啟",
"Shows": "節目", "Shows": "節目",
"Songs": "歌曲", "Songs": "歌曲",
@ -79,50 +79,52 @@
"System": "系統", "System": "系統",
"TvShows": "電視節目", "TvShows": "電視節目",
"User": "用戶", "User": "用戶",
"UserCreatedWithName": "用戶 {0} 已被建立", "UserCreatedWithName": "建立新用戶 {0}",
"UserDeletedWithName": "用戶 {0} 已被移除", "UserDeletedWithName": "用戶 {0} 已被移除",
"UserDownloadingItemWithValues": "{0} 正在下載 {1}", "UserDownloadingItemWithValues": "{0} 正在下載 {1}",
"UserLockedOutWithName": "使用者 {0} 已被鎖定", "UserLockedOutWithName": "用戶 {0} 已被封鎖",
"UserOfflineFromDevice": "{0} 從 {1} 斷開連接", "UserOfflineFromDevice": "{0} 終止了 {1} 的連接",
"UserOnlineFromDevice": "{0} 從 {1} 連線", "UserOnlineFromDevice": "{0} 從 {1} 連線",
"UserPasswordChangedWithName": "{0} 的密碼已被改", "UserPasswordChangedWithName": "{0} 的密碼已被改",
"UserPolicyUpdatedWithName": "使用者協議已更新為 {0}", "UserPolicyUpdatedWithName": "使用條款已更新為 {0}",
"UserStartedPlayingItemWithValues": "{0} 在 {2} 上播放 {1}", "UserStartedPlayingItemWithValues": "{0} 在 {2} 上播放 {1}",
"UserStoppedPlayingItemWithValues": "{0} 停止在 {2} 上播放 {1}", "UserStoppedPlayingItemWithValues": "{0} 停止在 {2} 上播放 {1}",
"ValueHasBeenAddedToLibrary": "已添加 {0} 到你的媒體庫", "ValueHasBeenAddedToLibrary": "{0} 已被加入至你的媒體庫",
"ValueSpecialEpisodeName": "特典 - {0}", "ValueSpecialEpisodeName": "特典 - {0}",
"VersionNumber": "版本 {0}", "VersionNumber": "版本 {0}",
"TaskDownloadMissingSubtitles": "下載少的字幕", "TaskDownloadMissingSubtitles": "下載缺字幕",
"TaskUpdatePlugins": "更新插件", "TaskUpdatePlugins": "更新插件",
"TasksApplicationCategory": "應用程式", "TasksApplicationCategory": "應用程式",
"TaskRefreshLibraryDescription": "掃描媒體庫以加入新增檔案及重新載入 metadata。", "TaskRefreshLibraryDescription": "掃描媒體庫以加入新增的檔案及重新載入元數據。",
"TasksMaintenanceCategory": "維護", "TasksMaintenanceCategory": "維護",
"TaskDownloadMissingSubtitlesDescription": "根據元數據中的設定,在互聯網上搜索缺少的字幕。", "TaskDownloadMissingSubtitlesDescription": "根據元數據中的設定,在網上搜尋欠缺的字幕。",
"TaskRefreshChannelsDescription": "重新載入網絡頻道的資訊。", "TaskRefreshChannelsDescription": "重新載入網絡頻道的資訊。",
"TaskRefreshChannels": "重新載入頻道", "TaskRefreshChannels": "重新載入頻道",
"TaskCleanTranscodeDescription": "刪除超過一天的轉碼文件。", "TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。",
"TaskCleanTranscode": "清理轉碼目錄", "TaskCleanTranscode": "清理轉碼檔資料夾",
"TaskUpdatePluginsDescription": "下載並更新能夠被自動更新的插件。", "TaskUpdatePluginsDescription": "下載並更新能夠被自動更新的插件。",
"TaskRefreshPeopleDescription": "更新媒體中演員和導演的元數據。", "TaskRefreshPeopleDescription": "更新你的媒體中有關的演員和導演的元數據。",
"TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔。", "TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔。",
"TaskCleanLogs": "清理紀錄檔目錄", "TaskCleanLogs": "清理紀錄檔資料夾",
"TaskRefreshLibrary": "掃描媒體庫", "TaskRefreshLibrary": "掃描媒體庫",
"TaskRefreshChapterImagesDescription": "為帶有章節的影片建立縮圖。", "TaskRefreshChapterImagesDescription": "為帶有章節的影片建立縮圖。",
"TaskRefreshChapterImages": "提取章節圖像", "TaskRefreshChapterImages": "提取章節圖像",
"TaskCleanCacheDescription": "刪除系統不再需要的緩存文件。", "TaskCleanCacheDescription": "刪除系統不再需要的緩存文件。",
"TaskCleanCache": "清理緩存目錄", "TaskCleanCache": "清理緩存資料夾",
"TasksChannelsCategory": "網絡頻道", "TasksChannelsCategory": "網絡頻道",
"TasksLibraryCategory": "庫", "TasksLibraryCategory": "媒體庫",
"TaskRefreshPeople": "重新載入人物", "TaskRefreshPeople": "重新載入人物",
"TaskCleanActivityLog": "清理活動記錄", "TaskCleanActivityLog": "清理活動記錄",
"Undefined": "未定義", "Undefined": "未定義",
"Forced": "強制", "Forced": "強制",
"Default": "預設", "Default": "預設",
"TaskOptimizeDatabaseDescription": "壓縮數據庫並截斷可用空間。在掃描媒體庫或執行其他數據庫的修改後運行此任務可能會提高性能。", "TaskOptimizeDatabaseDescription": "壓縮數據庫及釋放可用空間。完成任何會修改數據庫的工作(例如掃描媒體庫)後,執行此工作或可提升伺服器速度。",
"TaskOptimizeDatabase": "最佳化數據庫", "TaskOptimizeDatabase": "最佳化數據庫",
"TaskCleanActivityLogDescription": "刪除早於設定時間的活動記錄。", "TaskCleanActivityLogDescription": "刪除早於設定時間的活動記錄。",
"TaskKeyframeExtractorDescription": "提取關鍵幀以建立更準確的 HLS 播放列表。此工作或需要使用較長時間來完成。", "TaskKeyframeExtractorDescription": "提取關鍵影格Keyframe以建立更準確的 HLS playlist。此工作可能需要使用較長時間來完成。",
"TaskKeyframeExtractor": "關鍵提取器", "TaskKeyframeExtractor": "關鍵影格提取器",
"External": "外部", "External": "外部",
"HearingImpaired": "聽力障礙" "HearingImpaired": "聽力障礙",
"TaskRefreshTrickplayImages": "建立 Trickplay 圖像",
"TaskRefreshTrickplayImagesDescription": "為已啟用 Trickplay 的媒體庫內的影片建立 Trickplay 預覽圖。"
} }

View File

@ -696,7 +696,7 @@
"TwoLetterISORegionName": "SI" "TwoLetterISORegionName": "SI"
}, },
{ {
"DisplayName": "Soomaaliya", "DisplayName": "Somalia",
"Name": "SO", "Name": "SO",
"ThreeLetterISORegionName": "SOM", "ThreeLetterISORegionName": "SOM",
"TwoLetterISORegionName": "SO" "TwoLetterISORegionName": "SO"

View File

@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -178,7 +179,7 @@ namespace Emby.Server.Implementations.Playlists
public Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId) public Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId)
{ {
var user = userId.Equals(default) ? null : _userManager.GetUserById(userId); var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false) return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false)
{ {

View File

@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Playlists
public override bool SupportsInheritedParentImages => false; public override bool SupportsInheritedParentImages => false;
[JsonIgnore] [JsonIgnore]
public override CollectionType? CollectionType => Jellyfin.Data.Enums.CollectionType.Playlists; public override CollectionType? CollectionType => Jellyfin.Data.Enums.CollectionType.playlists;
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
{ {

View File

@ -115,9 +115,10 @@ public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask
List<LinkedChild>? itemsToRemove = null; List<LinkedChild>? itemsToRemove = null;
foreach (var linkedChild in folder.LinkedChildren) foreach (var linkedChild in folder.LinkedChildren)
{ {
if (!File.Exists(folder.Path)) var path = linkedChild.Path;
if (!File.Exists(path))
{ {
_logger.LogInformation("Item in {FolderName} cannot be found at {ItemPath}", folder.Name, linkedChild.Path); _logger.LogInformation("Item in {FolderName} cannot be found at {ItemPath}", folder.Name, path);
(itemsToRemove ??= new List<LinkedChild>()).Add(linkedChild); (itemsToRemove ??= new List<LinkedChild>()).Add(linkedChild);
} }
} }

View File

@ -189,7 +189,7 @@ namespace Emby.Server.Implementations.Session
_logger); _logger);
} }
private void OnSessionEnded(SessionInfo info) private async ValueTask OnSessionEnded(SessionInfo info)
{ {
EventHelper.QueueEventIfNotNull( EventHelper.QueueEventIfNotNull(
SessionEnded, SessionEnded,
@ -202,7 +202,7 @@ namespace Emby.Server.Implementations.Session
_eventManager.Publish(new SessionEndedEventArgs(info)); _eventManager.Publish(new SessionEndedEventArgs(info));
info.Dispose(); await info.DisposeAsync().ConfigureAwait(false);
} }
/// <inheritdoc /> /// <inheritdoc />
@ -301,12 +301,12 @@ namespace Emby.Server.Implementations.Session
await _mediaSourceManager.CloseLiveStream(session.PlayState.LiveStreamId).ConfigureAwait(false); await _mediaSourceManager.CloseLiveStream(session.PlayState.LiveStreamId).ConfigureAwait(false);
} }
OnSessionEnded(session); await OnSessionEnded(session).ConfigureAwait(false);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public void ReportSessionEnded(string sessionId) public async ValueTask ReportSessionEnded(string sessionId)
{ {
CheckDisposed(); CheckDisposed();
var session = GetSession(sessionId, false); var session = GetSession(sessionId, false);
@ -317,7 +317,7 @@ namespace Emby.Server.Implementations.Session
_activeConnections.TryRemove(key, out _); _activeConnections.TryRemove(key, out _);
OnSessionEnded(session); await OnSessionEnded(session).ConfigureAwait(false);
} }
} }
@ -337,7 +337,7 @@ namespace Emby.Server.Implementations.Session
info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture);
} }
if (!info.ItemId.Equals(default) && info.Item is null && libraryItem is not null) if (!info.ItemId.IsEmpty() && info.Item is null && libraryItem is not null)
{ {
var current = session.NowPlayingItem; var current = session.NowPlayingItem;
@ -529,7 +529,7 @@ namespace Emby.Server.Implementations.Session
{ {
var users = new List<User>(); var users = new List<User>();
if (session.UserId.Equals(default)) if (session.UserId.IsEmpty())
{ {
return users; return users;
} }
@ -690,7 +690,7 @@ namespace Emby.Server.Implementations.Session
var session = GetSession(info.SessionId); var session = GetSession(info.SessionId);
var libraryItem = info.ItemId.Equals(default) var libraryItem = info.ItemId.IsEmpty()
? null ? null
: GetNowPlayingItem(session, info.ItemId); : GetNowPlayingItem(session, info.ItemId);
@ -784,7 +784,7 @@ namespace Emby.Server.Implementations.Session
var session = GetSession(info.SessionId); var session = GetSession(info.SessionId);
var libraryItem = info.ItemId.Equals(default) var libraryItem = info.ItemId.IsEmpty()
? null ? null
: GetNowPlayingItem(session, info.ItemId); : GetNowPlayingItem(session, info.ItemId);
@ -923,7 +923,7 @@ namespace Emby.Server.Implementations.Session
session.StopAutomaticProgress(); session.StopAutomaticProgress();
var libraryItem = info.ItemId.Equals(default) var libraryItem = info.ItemId.IsEmpty()
? null ? null
: GetNowPlayingItem(session, info.ItemId); : GetNowPlayingItem(session, info.ItemId);
@ -933,7 +933,7 @@ namespace Emby.Server.Implementations.Session
info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture); info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture);
} }
if (!info.ItemId.Equals(default) && info.Item is null && libraryItem is not null) if (!info.ItemId.IsEmpty() && info.Item is null && libraryItem is not null)
{ {
var current = session.NowPlayingItem; var current = session.NowPlayingItem;
@ -1154,7 +1154,7 @@ namespace Emby.Server.Implementations.Session
var session = GetSessionToRemoteControl(sessionId); var session = GetSessionToRemoteControl(sessionId);
var user = session.UserId.Equals(default) ? null : _userManager.GetUserById(session.UserId); var user = session.UserId.IsEmpty() ? null : _userManager.GetUserById(session.UserId);
List<BaseItem> items; List<BaseItem> items;
@ -1223,7 +1223,7 @@ namespace Emby.Server.Implementations.Session
{ {
var controllingSession = GetSession(controllingSessionId); var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession); AssertCanControl(session, controllingSession);
if (!controllingSession.UserId.Equals(default)) if (!controllingSession.UserId.IsEmpty())
{ {
command.ControllingUserId = controllingSession.UserId; command.ControllingUserId = controllingSession.UserId;
} }
@ -1342,7 +1342,7 @@ namespace Emby.Server.Implementations.Session
{ {
var controllingSession = GetSession(controllingSessionId); var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession); AssertCanControl(session, controllingSession);
if (!controllingSession.UserId.Equals(default)) if (!controllingSession.UserId.IsEmpty())
{ {
command.ControllingUserId = controllingSession.UserId.ToString("N", CultureInfo.InvariantCulture); command.ControllingUserId = controllingSession.UserId.ToString("N", CultureInfo.InvariantCulture);
} }
@ -1463,7 +1463,7 @@ namespace Emby.Server.Implementations.Session
ArgumentException.ThrowIfNullOrEmpty(request.AppVersion); ArgumentException.ThrowIfNullOrEmpty(request.AppVersion);
User user = null; User user = null;
if (!request.UserId.Equals(default)) if (!request.UserId.IsEmpty())
{ {
user = _userManager.GetUserById(request.UserId); user = _userManager.GetUserById(request.UserId);
} }
@ -1590,7 +1590,7 @@ namespace Emby.Server.Implementations.Session
{ {
try try
{ {
ReportSessionEnded(session.Id); await ReportSessionEnded(session.Id).ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1670,7 +1670,6 @@ namespace Emby.Server.Implementations.Session
var fields = dtoOptions.Fields.ToList(); var fields = dtoOptions.Fields.ToList();
fields.Remove(ItemFields.BasicSyncInfo);
fields.Remove(ItemFields.CanDelete); fields.Remove(ItemFields.CanDelete);
fields.Remove(ItemFields.CanDownload); fields.Remove(ItemFields.CanDownload);
fields.Remove(ItemFields.ChildCount); fields.Remove(ItemFields.ChildCount);
@ -1767,7 +1766,7 @@ namespace Emby.Server.Implementations.Session
{ {
ArgumentNullException.ThrowIfNull(info); ArgumentNullException.ThrowIfNull(info);
var user = info.UserId.Equals(default) var user = info.UserId.IsEmpty()
? null ? null
: _userManager.GetUserById(info.UserId); : _userManager.GetUserById(info.UserId);

View File

@ -6,6 +6,7 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.SyncPlay; using MediaBrowser.Controller.SyncPlay;
@ -553,7 +554,7 @@ namespace Emby.Server.Implementations.SyncPlay
if (playingItemRemoved) if (playingItemRemoved)
{ {
var itemId = PlayQueue.GetPlayingItemId(); var itemId = PlayQueue.GetPlayingItemId();
if (!itemId.Equals(default)) if (!itemId.IsEmpty())
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById(itemId);
RunTimeTicks = item.RunTimeTicks ?? 0; RunTimeTicks = item.RunTimeTicks ?? 0;

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -41,7 +42,7 @@ namespace Emby.Server.Implementations.TV
} }
string? presentationUniqueKey = null; string? presentationUniqueKey = null;
if (query.SeriesId.HasValue && !query.SeriesId.Value.Equals(default)) if (!query.SeriesId.IsNullOrEmpty())
{ {
if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series) if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series)
{ {
@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.TV
string? presentationUniqueKey = null; string? presentationUniqueKey = null;
int? limit = null; int? limit = null;
if (request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default)) if (!request.SeriesId.IsNullOrEmpty())
{ {
if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series) if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series)
{ {
@ -146,7 +147,7 @@ namespace Emby.Server.Implementations.TV
// If viewing all next up for all series, remove first episodes // If viewing all next up for all series, remove first episodes
// But if that returns empty, keep those first episodes (avoid completely empty view) // But if that returns empty, keep those first episodes (avoid completely empty view)
var alwaysEnableFirstEpisode = request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default); var alwaysEnableFirstEpisode = !request.SeriesId.IsNullOrEmpty();
var anyFound = false; var anyFound = false;
return allNextUp return allNextUp

View File

@ -11,6 +11,7 @@ using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
using Jellyfin.Extensions;
using Jellyfin.Extensions.Json; using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.Updates
availablePackages = availablePackages.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); availablePackages = availablePackages.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
} }
if (!id.Equals(default)) if (!id.IsEmpty())
{ {
availablePackages = availablePackages.Where(x => x.Id.Equals(id)); availablePackages = availablePackages.Where(x => x.Id.Equals(id));
} }

View File

@ -2,6 +2,7 @@
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -41,7 +42,7 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
var isApiKey = context.User.GetIsApiKey(); var isApiKey = context.User.GetIsApiKey();
var userId = context.User.GetUserId(); var userId = context.User.GetUserId();
// This likely only happens during the wizard, so skip the default checks and let any other handlers do it // This likely only happens during the wizard, so skip the default checks and let any other handlers do it
if (!isApiKey && userId.Equals(default)) if (!isApiKey && userId.IsEmpty())
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -1,6 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -46,7 +47,7 @@ namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy
} }
var userId = contextUser.GetUserId(); var userId = contextUser.GetUserId();
if (userId.Equals(default)) if (userId.IsEmpty())
{ {
context.Fail(); context.Fail();
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -126,7 +127,7 @@ public class ArtistsController : BaseJellyfinApiController
User? user = null; User? user = null;
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
if (!userId.Value.Equals(default)) if (!userId.IsNullOrEmpty())
{ {
user = _userManager.GetUserById(userId.Value); user = _userManager.GetUserById(userId.Value);
} }
@ -330,7 +331,7 @@ public class ArtistsController : BaseJellyfinApiController
User? user = null; User? user = null;
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
if (!userId.Value.Equals(default)) if (!userId.IsNullOrEmpty())
{ {
user = _userManager.GetUserById(userId.Value); user = _userManager.GetUserById(userId.Value);
} }
@ -469,7 +470,7 @@ public class ArtistsController : BaseJellyfinApiController
var item = _libraryManager.GetArtist(name, dtoOptions); var item = _libraryManager.GetArtist(name, dtoOptions);
if (!userId.Value.Equals(default)) if (!userId.IsNullOrEmpty())
{ {
var user = _userManager.GetUserById(userId.Value); var user = _userManager.GetUserById(userId.Value);

View File

@ -6,6 +6,7 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -126,7 +127,7 @@ public class ChannelsController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -201,7 +202,7 @@ public class ChannelsController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);

View File

@ -42,16 +42,15 @@ public class DevicesController : BaseJellyfinApiController
/// <summary> /// <summary>
/// Get Devices. /// Get Devices.
/// </summary> /// </summary>
/// <param name="supportsSync">Gets or sets a value indicating whether [supports synchronize].</param>
/// <param name="userId">Gets or sets the user identifier.</param> /// <param name="userId">Gets or sets the user identifier.</param>
/// <response code="200">Devices retrieved.</response> /// <response code="200">Devices retrieved.</response>
/// <returns>An <see cref="OkResult"/> containing the list of devices.</returns> /// <returns>An <see cref="OkResult"/> containing the list of devices.</returns>
[HttpGet] [HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId) public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] Guid? userId)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
return await _deviceManager.GetDevicesForUser(userId, supportsSync).ConfigureAwait(false); return await _deviceManager.GetDevicesForUser(userId).ConfigureAwait(false);
} }
/// <summary> /// <summary>

View File

@ -9,8 +9,8 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@ -19,6 +19,7 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.MediaEncoding.Encoder;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -51,7 +52,7 @@ public class DynamicHlsController : BaseJellyfinApiController
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
private readonly ILogger<DynamicHlsController> _logger; private readonly ILogger<DynamicHlsController> _logger;
private readonly EncodingHelper _encodingHelper; private readonly EncodingHelper _encodingHelper;
private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator; private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator;
@ -67,7 +68,7 @@ public class DynamicHlsController : BaseJellyfinApiController
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param> /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param>
/// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param> /// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
@ -79,7 +80,7 @@ public class DynamicHlsController : BaseJellyfinApiController
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IFileSystem fileSystem, IFileSystem fileSystem,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
ILogger<DynamicHlsController> logger, ILogger<DynamicHlsController> logger,
DynamicHlsHelper dynamicHlsHelper, DynamicHlsHelper dynamicHlsHelper,
EncodingHelper encodingHelper, EncodingHelper encodingHelper,
@ -91,7 +92,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
_logger = logger; _logger = logger;
_dynamicHlsHelper = dynamicHlsHelper; _dynamicHlsHelper = dynamicHlsHelper;
_encodingHelper = encodingHelper; _encodingHelper = encodingHelper;
@ -283,17 +284,17 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
TranscodingJobType, TranscodingJobType,
cancellationToken) cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
TranscodingJobDto? job = null; TranscodingJob? job = null;
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
if (!System.IO.File.Exists(playlistPath)) if (!System.IO.File.Exists(playlistPath))
{ {
var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath); var transcodingLock = _transcodeManager.GetTranscodingLock(playlistPath);
await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false); await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
try try
{ {
@ -302,11 +303,11 @@ public class DynamicHlsController : BaseJellyfinApiController
// If the playlist doesn't already exist, startup ffmpeg // If the playlist doesn't already exist, startup ffmpeg
try try
{ {
job = await _transcodingJobHelper.StartFfMpeg( job = await _transcodeManager.StartFfMpeg(
state, state,
playlistPath, playlistPath,
GetCommandLineArguments(playlistPath, state, true, 0), GetCommandLineArguments(playlistPath, state, true, 0),
Request, Request.HttpContext.User.GetUserId(),
TranscodingJobType, TranscodingJobType,
cancellationTokenSource) cancellationTokenSource)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -331,11 +332,11 @@ public class DynamicHlsController : BaseJellyfinApiController
} }
} }
job ??= _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
if (job is not null) if (job is not null)
{ {
_transcodingJobHelper.OnTranscodeEndRequest(job); _transcodeManager.OnTranscodeEndRequest(job);
} }
var playlistText = HlsHelpers.GetLivePlaylistText(playlistPath, state); var playlistText = HlsHelpers.GetLivePlaylistText(playlistPath, state);
@ -1383,7 +1384,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
TranscodingJobType, TranscodingJobType,
cancellationTokenSource.Token) cancellationTokenSource.Token)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -1421,7 +1422,7 @@ public class DynamicHlsController : BaseJellyfinApiController
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
TranscodingJobType, TranscodingJobType,
cancellationToken) cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -1432,16 +1433,16 @@ public class DynamicHlsController : BaseJellyfinApiController
var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer); var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
TranscodingJobDto? job; TranscodingJob? job;
if (System.IO.File.Exists(segmentPath)) if (System.IO.File.Exists(segmentPath))
{ {
job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
_logger.LogDebug("returning {0} [it exists, try 1]", segmentPath); _logger.LogDebug("returning {0} [it exists, try 1]", segmentPath);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
} }
var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath); var transcodingLock = _transcodeManager.GetTranscodingLock(playlistPath);
await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false); await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
var released = false; var released = false;
var startTranscoding = false; var startTranscoding = false;
@ -1450,7 +1451,7 @@ public class DynamicHlsController : BaseJellyfinApiController
{ {
if (System.IO.File.Exists(segmentPath)) if (System.IO.File.Exists(segmentPath))
{ {
job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
transcodingLock.Release(); transcodingLock.Release();
released = true; released = true;
_logger.LogDebug("returning {0} [it exists, try 2]", segmentPath); _logger.LogDebug("returning {0} [it exists, try 2]", segmentPath);
@ -1488,7 +1489,7 @@ public class DynamicHlsController : BaseJellyfinApiController
// If the playlist doesn't already exist, startup ffmpeg // If the playlist doesn't already exist, startup ffmpeg
try try
{ {
await _transcodingJobHelper.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false) await _transcodeManager.KillTranscodingJobs(streamingRequest.DeviceId, streamingRequest.PlaySessionId, p => false)
.ConfigureAwait(false); .ConfigureAwait(false);
if (currentTranscodingIndex.HasValue) if (currentTranscodingIndex.HasValue)
@ -1499,11 +1500,11 @@ public class DynamicHlsController : BaseJellyfinApiController
streamingRequest.StartTimeTicks = streamingRequest.CurrentRuntimeTicks; streamingRequest.StartTimeTicks = streamingRequest.CurrentRuntimeTicks;
state.WaitForPath = segmentPath; state.WaitForPath = segmentPath;
job = await _transcodingJobHelper.StartFfMpeg( job = await _transcodeManager.StartFfMpeg(
state, state,
playlistPath, playlistPath,
GetCommandLineArguments(playlistPath, state, false, segmentId), GetCommandLineArguments(playlistPath, state, false, segmentId),
Request, Request.HttpContext.User.GetUserId(),
TranscodingJobType, TranscodingJobType,
cancellationTokenSource).ConfigureAwait(false); cancellationTokenSource).ConfigureAwait(false);
} }
@ -1517,7 +1518,7 @@ public class DynamicHlsController : BaseJellyfinApiController
} }
else else
{ {
job = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
if (job?.TranscodingThrottler is not null) if (job?.TranscodingThrottler is not null)
{ {
await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false); await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false);
@ -1534,7 +1535,7 @@ public class DynamicHlsController : BaseJellyfinApiController
} }
_logger.LogDebug("returning {0} [general case]", segmentPath); _logger.LogDebug("returning {0} [general case]", segmentPath);
job ??= _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false);
} }
@ -1922,7 +1923,7 @@ public class DynamicHlsController : BaseJellyfinApiController
string segmentPath, string segmentPath,
string segmentExtension, string segmentExtension,
int segmentIndex, int segmentIndex,
TranscodingJobDto? transcodingJob, TranscodingJob? transcodingJob,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var segmentExists = System.IO.File.Exists(segmentPath); var segmentExists = System.IO.File.Exists(segmentPath);
@ -1991,7 +1992,7 @@ public class DynamicHlsController : BaseJellyfinApiController
return GetSegmentResult(state, segmentPath, transcodingJob); return GetSegmentResult(state, segmentPath, transcodingJob);
} }
private ActionResult GetSegmentResult(StreamState state, string segmentPath, TranscodingJobDto? transcodingJob) private ActionResult GetSegmentResult(StreamState state, string segmentPath, TranscodingJob? transcodingJob)
{ {
var segmentEndingPositionTicks = state.Request.CurrentRuntimeTicks + state.Request.ActualSegmentLengthTicks; var segmentEndingPositionTicks = state.Request.CurrentRuntimeTicks + state.Request.ActualSegmentLengthTicks;
@ -2001,7 +2002,7 @@ public class DynamicHlsController : BaseJellyfinApiController
if (transcodingJob is not null) if (transcodingJob is not null)
{ {
transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks);
_transcodingJobHelper.OnTranscodeEndRequest(transcodingJob); _transcodeManager.OnTranscodeEndRequest(transcodingJob);
} }
return Task.CompletedTask; return Task.CompletedTask;
@ -2012,7 +2013,7 @@ public class DynamicHlsController : BaseJellyfinApiController
private int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) private int? GetCurrentTranscodingIndex(string playlist, string segmentExtension)
{ {
var job = _transcodingJobHelper.GetTranscodingJob(playlist, TranscodingJobType); var job = _transcodeManager.GetTranscodingJob(playlist, TranscodingJobType);
if (job is null || job.HasExited) if (job is null || job.HasExited)
{ {

View File

@ -3,6 +3,7 @@ using System.Linq;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -53,7 +54,7 @@ public class FilterController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -146,7 +147,7 @@ public class FilterController : BaseJellyfinApiController
[FromQuery] bool? recursive) [FromQuery] bool? recursive)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);

View File

@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -95,7 +96,7 @@ public class GenresController : BaseJellyfinApiController
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
User? user = userId.Value.Equals(default) User? user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -131,8 +132,8 @@ public class GenresController : BaseJellyfinApiController
QueryResult<(BaseItem, ItemCounts)> result; QueryResult<(BaseItem, ItemCounts)> result;
if (parentItem is ICollectionFolder parentCollectionFolder if (parentItem is ICollectionFolder parentCollectionFolder
&& (parentCollectionFolder.CollectionType == CollectionType.Music && (parentCollectionFolder.CollectionType == CollectionType.music
|| parentCollectionFolder.CollectionType == CollectionType.MusicVideos)) || parentCollectionFolder.CollectionType == CollectionType.musicvideos))
{ {
result = _libraryManager.GetMusicGenres(query); result = _libraryManager.GetMusicGenres(query);
} }
@ -172,7 +173,7 @@ public class GenresController : BaseJellyfinApiController
item ??= new Genre(); item ??= new Genre();
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);

View File

@ -24,22 +24,22 @@ public class HlsSegmentController : BaseJellyfinApiController
{ {
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HlsSegmentController"/> class. /// Initializes a new instance of the <see cref="HlsSegmentController"/> class.
/// </summary> /// </summary>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param> /// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="transcodingJobHelper">Initialized instance of the <see cref="TranscodingJobHelper"/>.</param> /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
public HlsSegmentController( public HlsSegmentController(
IFileSystem fileSystem, IFileSystem fileSystem,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
TranscodingJobHelper transcodingJobHelper) ITranscodeManager transcodeManager)
{ {
_fileSystem = fileSystem; _fileSystem = fileSystem;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
} }
/// <summary> /// <summary>
@ -112,7 +112,7 @@ public class HlsSegmentController : BaseJellyfinApiController
[FromQuery, Required] string deviceId, [FromQuery, Required] string deviceId,
[FromQuery, Required] string playSessionId) [FromQuery, Required] string playSessionId)
{ {
_transcodingJobHelper.KillTranscodingJobs(deviceId, playSessionId, path => true); _transcodeManager.KillTranscodingJobs(deviceId, playSessionId, _ => true);
return NoContent(); return NoContent();
} }
@ -174,13 +174,13 @@ public class HlsSegmentController : BaseJellyfinApiController
private ActionResult GetFileResult(string path, string playlistPath) private ActionResult GetFileResult(string path, string playlistPath)
{ {
var transcodingJob = _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls); var transcodingJob = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
Response.OnCompleted(() => Response.OnCompleted(() =>
{ {
if (transcodingJob is not null) if (transcodingJob is not null)
{ {
_transcodingJobHelper.OnTranscodeEndRequest(transcodingJob); _transcodeManager.OnTranscodeEndRequest(transcodingJob);
} }
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -76,7 +77,7 @@ public class InstantMixController : BaseJellyfinApiController
{ {
var item = _libraryManager.GetItemById(id); var item = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
@ -113,7 +114,7 @@ public class InstantMixController : BaseJellyfinApiController
{ {
var album = _libraryManager.GetItemById(id); var album = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
@ -150,7 +151,7 @@ public class InstantMixController : BaseJellyfinApiController
{ {
var playlist = (Playlist)_libraryManager.GetItemById(id); var playlist = (Playlist)_libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
@ -186,7 +187,7 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
@ -223,7 +224,7 @@ public class InstantMixController : BaseJellyfinApiController
{ {
var item = _libraryManager.GetItemById(id); var item = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
@ -260,7 +261,7 @@ public class InstantMixController : BaseJellyfinApiController
{ {
var item = _libraryManager.GetItemById(id); var item = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
@ -334,7 +335,7 @@ public class InstantMixController : BaseJellyfinApiController
{ {
var item = _libraryManager.GetItemById(id); var item = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }

View File

@ -171,7 +171,7 @@ public class ItemUpdateController : BaseJellyfinApiController
info.ContentTypeOptions = GetContentTypeOptions(true).ToArray(); info.ContentTypeOptions = GetContentTypeOptions(true).ToArray();
info.ContentType = configuredContentType; info.ContentType = configuredContentType;
if (inheritedContentType is null || inheritedContentType == CollectionType.TvShows) if (inheritedContentType is null || inheritedContentType == CollectionType.tvshows)
{ {
info.ContentTypeOptions = info.ContentTypeOptions info.ContentTypeOptions = info.ContentTypeOptions
.Where(i => string.IsNullOrWhiteSpace(i.Value) .Where(i => string.IsNullOrWhiteSpace(i.Value)

View File

@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -34,6 +35,7 @@ public class ItemsController : BaseJellyfinApiController
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly ILogger<ItemsController> _logger; private readonly ILogger<ItemsController> _logger;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly IUserDataManager _userDataRepository;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ItemsController"/> class. /// Initializes a new instance of the <see cref="ItemsController"/> class.
@ -44,13 +46,15 @@ public class ItemsController : BaseJellyfinApiController
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
/// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param>
public ItemsController( public ItemsController(
IUserManager userManager, IUserManager userManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
ILocalizationManager localization, ILocalizationManager localization,
IDtoService dtoService, IDtoService dtoService,
ILogger<ItemsController> logger, ILogger<ItemsController> logger,
ISessionManager sessionManager) ISessionManager sessionManager,
IUserDataManager userDataRepository)
{ {
_userManager = userManager; _userManager = userManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
@ -58,6 +62,7 @@ public class ItemsController : BaseJellyfinApiController
_dtoService = dtoService; _dtoService = dtoService;
_logger = logger; _logger = logger;
_sessionManager = sessionManager; _sessionManager = sessionManager;
_userDataRepository = userDataRepository;
} }
/// <summary> /// <summary>
@ -241,7 +246,7 @@ public class ItemsController : BaseJellyfinApiController
var isApiKey = User.GetIsApiKey(); var isApiKey = User.GetIsApiKey();
// if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = !isApiKey && !userId.Value.Equals(default) var user = !isApiKey && !userId.IsNullOrEmpty()
? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException() ? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException()
: null; : null;
@ -275,7 +280,7 @@ public class ItemsController : BaseJellyfinApiController
collectionType = hasCollectionType.CollectionType; collectionType = hasCollectionType.CollectionType;
} }
if (collectionType == CollectionType.Playlists) if (collectionType == CollectionType.playlists)
{ {
recursive = true; recursive = true;
includeItemTypes = new[] { BaseItemKind.Playlist }; includeItemTypes = new[] { BaseItemKind.Playlist };
@ -836,7 +841,7 @@ public class ItemsController : BaseJellyfinApiController
var ancestorIds = Array.Empty<Guid>(); var ancestorIds = Array.Empty<Guid>();
var excludeFolderIds = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes); var excludeFolderIds = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes);
if (parentIdGuid.Equals(default) && excludeFolderIds.Length > 0) if (parentIdGuid.IsEmpty() && excludeFolderIds.Length > 0)
{ {
ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true) ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true)
.Where(i => i is Folder) .Where(i => i is Folder)
@ -881,4 +886,64 @@ public class ItemsController : BaseJellyfinApiController
itemsResult.TotalRecordCount, itemsResult.TotalRecordCount,
returnItems); returnItems);
} }
/// <summary>
/// Get Item User Data.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="itemId">The item id.</param>
/// <response code="200">return item user data.</response>
/// <response code="404">Item is not found.</response>
/// <returns>Return <see cref="UserItemDataDto"/>.</returns>
[HttpGet("Users/{userId}/Items/{itemId}/UserData")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<UserItemDataDto> GetItemUserData(
[FromRoute, Required] Guid userId,
[FromRoute, Required] Guid itemId)
{
if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to view this item user data.");
}
var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException();
var item = _libraryManager.GetItemById(itemId);
return (item == null) ? NotFound() : _userDataRepository.GetUserDataDto(item, user);
}
/// <summary>
/// Update Item User Data.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="itemId">The item id.</param>
/// <param name="userDataDto">New user data object.</param>
/// <response code="200">return updated user item data.</response>
/// <response code="404">Item is not found.</response>
/// <returns>Return <see cref="UserItemDataDto"/>.</returns>
[HttpPost("Users/{userId}/Items/{itemId}/UserData")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<UserItemDataDto> UpdateItemUserData(
[FromRoute, Required] Guid userId,
[FromRoute, Required] Guid itemId,
[FromBody, Required] UpdateUserItemDataDto userDataDto)
{
if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update this item user data.");
}
var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException();
var item = _libraryManager.GetItemById(itemId);
if (item == null)
{
return NotFound();
}
_userDataRepository.SaveUserData(user, item, userDataDto, UserDataSaveReason.UpdateUserData);
return _userDataRepository.GetUserDataDto(item, user);
}
} }

View File

@ -146,12 +146,12 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery] bool inheritFromParent = false) [FromQuery] bool inheritFromParent = false)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? (userId.Value.Equals(default) ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder ? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder()) : _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -213,12 +213,12 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery] bool inheritFromParent = false) [FromQuery] bool inheritFromParent = false)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? (userId.Value.Equals(default) ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder ? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder()) : _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -339,7 +339,7 @@ public class LibraryController : BaseJellyfinApiController
{ {
var isApiKey = User.GetIsApiKey(); var isApiKey = User.GetIsApiKey();
var userId = User.GetUserId(); var userId = User.GetUserId();
var user = !isApiKey && !userId.Equals(default) var user = !isApiKey && !userId.IsEmpty()
? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException() ? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException()
: null; : null;
if (!isApiKey && user is null) if (!isApiKey && user is null)
@ -382,7 +382,7 @@ public class LibraryController : BaseJellyfinApiController
{ {
var isApiKey = User.GetIsApiKey(); var isApiKey = User.GetIsApiKey();
var userId = User.GetUserId(); var userId = User.GetUserId();
var user = !isApiKey && !userId.Equals(default) var user = !isApiKey && !userId.IsEmpty()
? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException() ? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException()
: null; : null;
@ -428,7 +428,7 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery] bool? isFavorite) [FromQuery] bool? isFavorite)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -471,7 +471,7 @@ public class LibraryController : BaseJellyfinApiController
var baseItemDtos = new List<BaseItemDto>(); var baseItemDtos = new List<BaseItemDto>();
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -702,8 +702,8 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? (userId.Value.Equals(default) ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder ? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder()) : _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -718,7 +718,7 @@ public class LibraryController : BaseJellyfinApiController
return new QueryResult<BaseItemDto>(); return new QueryResult<BaseItemDto>();
} }
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
@ -927,15 +927,15 @@ public class LibraryController : BaseJellyfinApiController
{ {
return contentType switch return contentType switch
{ {
CollectionType.BoxSets => new[] { "BoxSet" }, CollectionType.boxsets => new[] { "BoxSet" },
CollectionType.Playlists => new[] { "Playlist" }, CollectionType.playlists => new[] { "Playlist" },
CollectionType.Movies => new[] { "Movie" }, CollectionType.movies => new[] { "Movie" },
CollectionType.TvShows => new[] { "Series", "Season", "Episode" }, CollectionType.tvshows => new[] { "Series", "Season", "Episode" },
CollectionType.Books => new[] { "Book" }, CollectionType.books => new[] { "Book" },
CollectionType.Music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" }, CollectionType.music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" },
CollectionType.HomeVideos => new[] { "Video", "Photo" }, CollectionType.homevideos => new[] { "Video", "Photo" },
CollectionType.Photos => new[] { "Video", "Photo" }, CollectionType.photos => new[] { "Video", "Photo" },
CollectionType.MusicVideos => new[] { "MusicVideo" }, CollectionType.musicvideos => new[] { "MusicVideo" },
_ => new[] { "Series", "Season", "Episode", "Movie" } _ => new[] { "Series", "Season", "Episode", "Movie" }
}; };
} }

View File

@ -10,12 +10,12 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LiveTvDtos; using Jellyfin.Api.Models.LiveTvDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@ -24,6 +24,8 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.LiveTv;
@ -41,43 +43,47 @@ namespace Jellyfin.Api.Controllers;
public class LiveTvController : BaseJellyfinApiController public class LiveTvController : BaseJellyfinApiController
{ {
private readonly ILiveTvManager _liveTvManager; private readonly ILiveTvManager _liveTvManager;
private readonly ITunerHostManager _tunerHostManager;
private readonly IUserManager _userManager; private readonly IUserManager _userManager;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IDtoService _dtoService; private readonly IDtoService _dtoService;
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IConfigurationManager _configurationManager; private readonly IConfigurationManager _configurationManager;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LiveTvController"/> class. /// Initializes a new instance of the <see cref="LiveTvController"/> class.
/// </summary> /// </summary>
/// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param> /// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
/// <param name="tunerHostManager">Instance of the <see cref="ITunerHostManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param> /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param> /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param> /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param> /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param> /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
public LiveTvController( public LiveTvController(
ILiveTvManager liveTvManager, ILiveTvManager liveTvManager,
ITunerHostManager tunerHostManager,
IUserManager userManager, IUserManager userManager,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IDtoService dtoService, IDtoService dtoService,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IConfigurationManager configurationManager, IConfigurationManager configurationManager,
TranscodingJobHelper transcodingJobHelper) ITranscodeManager transcodeManager)
{ {
_liveTvManager = liveTvManager; _liveTvManager = liveTvManager;
_tunerHostManager = tunerHostManager;
_userManager = userManager; _userManager = userManager;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_dtoService = dtoService; _dtoService = dtoService;
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_configurationManager = configurationManager; _configurationManager = configurationManager;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
} }
/// <summary> /// <summary>
@ -177,7 +183,7 @@ public class LiveTvController : BaseJellyfinApiController
dtoOptions, dtoOptions,
CancellationToken.None); CancellationToken.None);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -209,10 +215,10 @@ public class LiveTvController : BaseJellyfinApiController
public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = channelId.Equals(default) var item = channelId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(channelId); : _libraryManager.GetItemById(channelId);
@ -382,7 +388,7 @@ public class LiveTvController : BaseJellyfinApiController
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecordingFolders([FromQuery] Guid? userId) public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecordingFolders([FromQuery] Guid? userId)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var folders = await _liveTvManager.GetRecordingFoldersAsync(user).ConfigureAwait(false); var folders = await _liveTvManager.GetRecordingFoldersAsync(user).ConfigureAwait(false);
@ -405,10 +411,10 @@ public class LiveTvController : BaseJellyfinApiController
public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = recordingId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId); var item = recordingId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
var dtoOptions = new DtoOptions() var dtoOptions = new DtoOptions()
.AddClientFields(User); .AddClientFields(User);
@ -562,7 +568,7 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] bool enableTotalRecordCount = true) [FromQuery] bool enableTotalRecordCount = true)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -589,7 +595,7 @@ public class LiveTvController : BaseJellyfinApiController
GenreIds = genreIds GenreIds = genreIds
}; };
if (librarySeriesId.HasValue && !librarySeriesId.Equals(default)) if (!librarySeriesId.IsNullOrEmpty())
{ {
query.IsSeries = true; query.IsSeries = true;
@ -618,7 +624,7 @@ public class LiveTvController : BaseJellyfinApiController
[Authorize(Policy = Policies.LiveTvAccess)] [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body) public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body)
{ {
var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId); var user = body.UserId.IsEmpty() ? null : _userManager.GetUserById(body.UserId);
var query = new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
@ -643,7 +649,7 @@ public class LiveTvController : BaseJellyfinApiController
GenreIds = body.GenreIds GenreIds = body.GenreIds
}; };
if (!body.LibrarySeriesId.Equals(default)) if (!body.LibrarySeriesId.IsEmpty())
{ {
query.IsSeries = true; query.IsSeries = true;
@ -702,7 +708,7 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] bool enableTotalRecordCount = true) [FromQuery] bool enableTotalRecordCount = true)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -741,7 +747,7 @@ public class LiveTvController : BaseJellyfinApiController
[FromQuery] Guid? userId) [FromQuery] Guid? userId)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -949,9 +955,7 @@ public class LiveTvController : BaseJellyfinApiController
[Authorize(Policy = Policies.LiveTvManagement)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo) public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
{ => await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
return await _liveTvManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
}
/// <summary> /// <summary>
/// Deletes a tuner host. /// Deletes a tuner host.
@ -1128,10 +1132,8 @@ public class LiveTvController : BaseJellyfinApiController
[HttpGet("TunerHosts/Types")] [HttpGet("TunerHosts/Types")]
[Authorize(Policy = Policies.LiveTvAccess)] [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes() public IEnumerable<NameIdPair> GetTunerHostTypes()
{ => _tunerHostManager.GetTunerHostTypes();
return _liveTvManager.GetTunerHostTypes();
}
/// <summary> /// <summary>
/// Discover tuners. /// Discover tuners.
@ -1143,10 +1145,8 @@ public class LiveTvController : BaseJellyfinApiController
[HttpGet("Tuners/Discover")] [HttpGet("Tuners/Discover")]
[Authorize(Policy = Policies.LiveTvManagement)] [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false) public IAsyncEnumerable<TunerHostInfo> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
{ => _tunerHostManager.DiscoverTuners(newDevicesOnly);
return await _liveTvManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false);
}
/// <summary> /// <summary>
/// Gets a live tv recording stream. /// Gets a live tv recording stream.
@ -1171,7 +1171,7 @@ public class LiveTvController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var stream = new ProgressiveFileStream(path, null, _transcodingJobHelper); var stream = new ProgressiveFileStream(path, null, _transcodeManager);
return new FileStreamResult(stream, MimeTypes.GetMimeType(path)); return new FileStreamResult(stream, MimeTypes.GetMimeType(path));
} }

View File

@ -7,6 +7,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
@ -69,7 +70,7 @@ public class MoviesController : BaseJellyfinApiController
[FromQuery] int itemLimit = 8) [FromQuery] int itemLimit = 8)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }

View File

@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -95,7 +96,7 @@ public class MusicGenresController : BaseJellyfinApiController
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
User? user = userId.Value.Equals(default) User? user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -164,7 +165,7 @@ public class MusicGenresController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
if (!userId.Value.Equals(default)) if (!userId.IsNullOrEmpty())
{ {
var user = _userManager.GetUserById(userId.Value); var user = _userManager.GetUserById(userId.Value);

View File

@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -83,7 +84,7 @@ public class PersonsController : BaseJellyfinApiController
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = userId.Value.Equals(default) User? user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -129,7 +130,7 @@ public class PersonsController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
if (!userId.Value.Equals(default)) if (!userId.IsNullOrEmpty())
{ {
var user = _userManager.GetUserById(userId.Value); var user = _userManager.GetUserById(userId.Value);
return _dtoService.GetBaseItemDto(item, dtoOptions, user); return _dtoService.GetBaseItemDto(item, dtoOptions, user);

View File

@ -9,6 +9,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.PlaylistDtos; using Jellyfin.Api.Models.PlaylistDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Playlists;
@ -188,7 +189,7 @@ public class PlaylistsController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var user = userId.Equals(default) var user = userId.IsEmpty()
? null ? null
: _userManager.GetUserById(userId); : _userManager.GetUserById(userId);

View File

@ -8,6 +8,7 @@ using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;
@ -30,7 +31,7 @@ public class PlaystateController : BaseJellyfinApiController
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ISessionManager _sessionManager; private readonly ISessionManager _sessionManager;
private readonly ILogger<PlaystateController> _logger; private readonly ILogger<PlaystateController> _logger;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PlaystateController"/> class. /// Initializes a new instance of the <see cref="PlaystateController"/> class.
@ -40,14 +41,14 @@ public class PlaystateController : BaseJellyfinApiController
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param> /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param> /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
/// <param name="transcodingJobHelper">Th <see cref="TranscodingJobHelper"/> singleton.</param> /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
public PlaystateController( public PlaystateController(
IUserManager userManager, IUserManager userManager,
IUserDataManager userDataRepository, IUserDataManager userDataRepository,
ILibraryManager libraryManager, ILibraryManager libraryManager,
ISessionManager sessionManager, ISessionManager sessionManager,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
TranscodingJobHelper transcodingJobHelper) ITranscodeManager transcodeManager)
{ {
_userManager = userManager; _userManager = userManager;
_userDataRepository = userDataRepository; _userDataRepository = userDataRepository;
@ -55,7 +56,7 @@ public class PlaystateController : BaseJellyfinApiController
_sessionManager = sessionManager; _sessionManager = sessionManager;
_logger = loggerFactory.CreateLogger<PlaystateController>(); _logger = loggerFactory.CreateLogger<PlaystateController>();
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
} }
/// <summary> /// <summary>
@ -188,7 +189,7 @@ public class PlaystateController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId) public ActionResult PingPlaybackSession([FromQuery, Required] string playSessionId)
{ {
_transcodingJobHelper.PingTranscodingJob(playSessionId, null); _transcodeManager.PingTranscodingJob(playSessionId, null);
return NoContent(); return NoContent();
} }
@ -205,7 +206,7 @@ public class PlaystateController : BaseJellyfinApiController
_logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId))
{ {
await _transcodingJobHelper.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); await _transcodeManager.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
} }
playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
@ -354,7 +355,7 @@ public class PlaystateController : BaseJellyfinApiController
_logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty); _logger.LogDebug("ReportPlaybackStopped PlaySessionId: {0}", playbackStopInfo.PlaySessionId ?? string.Empty);
if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId)) if (!string.IsNullOrWhiteSpace(playbackStopInfo.PlaySessionId))
{ {
await _transcodingJobHelper.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false); await _transcodeManager.KillTranscodingJobs(User.GetDeviceId()!, playbackStopInfo.PlaySessionId, s => true).ConfigureAwait(false);
} }
playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); playbackStopInfo.SessionId = await RequestHelpers.GetSessionId(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
@ -388,7 +389,7 @@ public class PlaystateController : BaseJellyfinApiController
{ {
if (method == PlayMethod.Transcode) if (method == PlayMethod.Transcode)
{ {
var job = string.IsNullOrWhiteSpace(playSessionId) ? null : _transcodingJobHelper.GetTranscodingJob(playSessionId); var job = string.IsNullOrWhiteSpace(playSessionId) ? null : _transcodeManager.GetTranscodingJob(playSessionId);
if (job is null) if (job is null)
{ {
return PlayMethod.DirectPlay; return PlayMethod.DirectPlay;

View File

@ -209,7 +209,7 @@ public class SearchController : BaseJellyfinApiController
break; break;
} }
if (!item.ChannelId.Equals(default)) if (!item.ChannelId.IsEmpty())
{ {
var channel = _libraryManager.GetItemById(item.ChannelId); var channel = _libraryManager.GetItemById(item.ChannelId);
result.ChannelName = channel?.Name; result.ChannelName = channel?.Name;

View File

@ -10,6 +10,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.SessionDtos; using Jellyfin.Api.Models.SessionDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -71,7 +72,7 @@ public class SessionController : BaseJellyfinApiController
result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase)); result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
} }
if (controllableByUserId.HasValue && !controllableByUserId.Equals(default)) if (!controllableByUserId.IsNullOrEmpty())
{ {
result = result.Where(i => i.SupportsRemoteControl); result = result.Where(i => i.SupportsRemoteControl);
@ -83,12 +84,12 @@ public class SessionController : BaseJellyfinApiController
if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers)) if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers))
{ {
result = result.Where(i => i.UserId.Equals(default) || i.ContainsUser(controllableByUserId.Value)); result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(controllableByUserId.Value));
} }
if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl)) if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl))
{ {
result = result.Where(i => !i.UserId.Equals(default)); result = result.Where(i => !i.UserId.IsEmpty());
} }
result = result.Where(i => result = result.Where(i =>
@ -385,7 +386,6 @@ public class SessionController : BaseJellyfinApiController
/// <param name="playableMediaTypes">A list of playable media types, comma delimited. Audio, Video, Book, Photo.</param> /// <param name="playableMediaTypes">A list of playable media types, comma delimited. Audio, Video, Book, Photo.</param>
/// <param name="supportedCommands">A list of supported remote control commands, comma delimited.</param> /// <param name="supportedCommands">A list of supported remote control commands, comma delimited.</param>
/// <param name="supportsMediaControl">Determines whether media can be played remotely..</param> /// <param name="supportsMediaControl">Determines whether media can be played remotely..</param>
/// <param name="supportsSync">Determines whether sync is supported.</param>
/// <param name="supportsPersistentIdentifier">Determines whether the device supports a unique identifier.</param> /// <param name="supportsPersistentIdentifier">Determines whether the device supports a unique identifier.</param>
/// <response code="204">Capabilities posted.</response> /// <response code="204">Capabilities posted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
@ -397,7 +397,6 @@ public class SessionController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] playableMediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] playableMediaTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands,
[FromQuery] bool supportsMediaControl = false, [FromQuery] bool supportsMediaControl = false,
[FromQuery] bool supportsSync = false,
[FromQuery] bool supportsPersistentIdentifier = true) [FromQuery] bool supportsPersistentIdentifier = true)
{ {
if (string.IsNullOrWhiteSpace(id)) if (string.IsNullOrWhiteSpace(id))
@ -410,7 +409,6 @@ public class SessionController : BaseJellyfinApiController
PlayableMediaTypes = playableMediaTypes, PlayableMediaTypes = playableMediaTypes,
SupportedCommands = supportedCommands, SupportedCommands = supportedCommands,
SupportsMediaControl = supportsMediaControl, SupportsMediaControl = supportsMediaControl,
SupportsSync = supportsSync,
SupportsPersistentIdentifier = supportsPersistentIdentifier SupportsPersistentIdentifier = supportsPersistentIdentifier
}); });
return NoContent(); return NoContent();

View File

@ -5,6 +5,7 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -91,7 +92,7 @@ public class StudiosController : BaseJellyfinApiController
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = userId.Value.Equals(default) User? user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -144,7 +145,7 @@ public class StudiosController : BaseJellyfinApiController
var dtoOptions = new DtoOptions().AddClientFields(User); var dtoOptions = new DtoOptions().AddClientFields(User);
var item = _libraryManager.GetStudio(name); var item = _libraryManager.GetStudio(name);
if (!userId.Equals(default)) if (!userId.IsNullOrEmpty())
{ {
var user = _userManager.GetUserById(userId.Value); var user = _userManager.GetUserById(userId.Value);

View File

@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -62,7 +63,7 @@ public class SuggestionsController : BaseJellyfinApiController
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] bool enableTotalRecordCount = false) [FromQuery] bool enableTotalRecordCount = false)
{ {
var user = userId.Equals(default) var user = userId.IsEmpty()
? null ? null
: _userManager.GetUserById(userId); : _userManager.GetUserById(userId);

View File

@ -111,7 +111,7 @@ public class TvShowsController : BaseJellyfinApiController
}, },
options); options);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -150,7 +150,7 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] bool? enableUserData) [FromQuery] bool? enableUserData)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -222,7 +222,7 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] ItemSortBy? sortBy) [FromQuery] ItemSortBy? sortBy)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
@ -284,7 +284,7 @@ public class TvShowsController : BaseJellyfinApiController
} }
// This must be the last filter // This must be the last filter
if (adjacentTo.HasValue && !adjacentTo.Value.Equals(default)) if (!adjacentTo.IsNullOrEmpty())
{ {
episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo.Value).ToList(); episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo.Value).ToList();
} }
@ -339,7 +339,7 @@ public class TvShowsController : BaseJellyfinApiController
[FromQuery] bool? enableUserData) [FromQuery] bool? enableUserData)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);

View File

@ -11,6 +11,7 @@ using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;

View File

@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.UserDtos; using Jellyfin.Api.Models.UserDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@ -532,7 +533,7 @@ public class UserController : BaseJellyfinApiController
public ActionResult<UserDto> GetCurrentUser() public ActionResult<UserDto> GetCurrentUser()
{ {
var userId = User.GetUserId(); var userId = User.GetUserId();
if (userId.Equals(default)) if (userId.IsEmpty())
{ {
return BadRequest(); return BadRequest();
} }

View File

@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -84,7 +85,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -145,7 +146,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -185,7 +186,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -221,7 +222,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -257,7 +258,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -294,7 +295,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -330,7 +331,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -375,7 +376,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -558,7 +559,7 @@ public class UserLibraryController : BaseJellyfinApiController
return NotFound(); return NotFound();
} }
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);

View File

@ -90,7 +90,6 @@ public class UserViewsController : BaseJellyfinApiController
fields.Add(ItemFields.PrimaryImageAspectRatio); fields.Add(ItemFields.PrimaryImageAspectRatio);
fields.Add(ItemFields.DisplayPreferencesId); fields.Add(ItemFields.DisplayPreferencesId);
fields.Remove(ItemFields.BasicSyncInfo);
dtoOptions.Fields = fields.ToArray(); dtoOptions.Fields = fields.ToArray();
var user = _userManager.GetUserById(userId); var user = _userManager.GetUserById(userId);

View File

@ -11,7 +11,7 @@ using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Extensions;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@ -20,6 +20,7 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -43,7 +44,7 @@ public class VideosController : BaseJellyfinApiController
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly EncodingHelper _encodingHelper; private readonly EncodingHelper _encodingHelper;
@ -58,7 +59,7 @@ public class VideosController : BaseJellyfinApiController
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param> /// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param> /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
public VideosController( public VideosController(
@ -68,7 +69,7 @@ public class VideosController : BaseJellyfinApiController
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
EncodingHelper encodingHelper) EncodingHelper encodingHelper)
{ {
@ -78,7 +79,7 @@ public class VideosController : BaseJellyfinApiController
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_encodingHelper = encodingHelper; _encodingHelper = encodingHelper;
} }
@ -96,12 +97,12 @@ public class VideosController : BaseJellyfinApiController
public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId) public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.Value.Equals(default) var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = itemId.Equals(default) var item = itemId.IsEmpty()
? (userId.Value.Equals(default) ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder ? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder()) : _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById(itemId);
@ -427,7 +428,7 @@ public class VideosController : BaseJellyfinApiController
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
_transcodingJobType, _transcodingJobType,
cancellationTokenSource.Token) cancellationTokenSource.Token)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -466,7 +467,7 @@ public class VideosController : BaseJellyfinApiController
if (state.MediaSource.IsInfiniteStream) if (state.MediaSource.IsInfiniteStream)
{ {
var liveStream = new ProgressiveFileStream(state.MediaPath, null, _transcodingJobHelper); var liveStream = new ProgressiveFileStream(state.MediaPath, null, _transcodeManager);
return File(liveStream, contentType); return File(liveStream, contentType);
} }
@ -482,7 +483,7 @@ public class VideosController : BaseJellyfinApiController
state, state,
isHeadRequest, isHeadRequest,
HttpContext, HttpContext,
_transcodingJobHelper, _transcodeManager,
ffmpegCommandLineArguments, ffmpegCommandLineArguments,
_transcodingJobType, _transcodingJobType,
cancellationTokenSource).ConfigureAwait(false); cancellationTokenSource).ConfigureAwait(false);

View File

@ -90,7 +90,7 @@ public class YearsController : BaseJellyfinApiController
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
User? user = userId.Value.Equals(default) User? user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId); BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
@ -110,7 +110,7 @@ public class YearsController : BaseJellyfinApiController
{ {
var folder = (Folder)parentItem; var folder = (Folder)parentItem;
if (userId.Equals(default)) if (userId.IsNullOrEmpty())
{ {
items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToList(); items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToList();
} }
@ -182,7 +182,7 @@ public class YearsController : BaseJellyfinApiController
var dtoOptions = new DtoOptions() var dtoOptions = new DtoOptions()
.AddClientFields(User); .AddClientFields(User);
if (!userId.Value.Equals(default)) if (!userId.IsNullOrEmpty())
{ {
var user = _userManager.GetUserById(userId.Value); var user = _userManager.GetUserById(userId.Value);
return _dtoService.GetBaseItemDto(item, dtoOptions, user); return _dtoService.GetBaseItemDto(item, dtoOptions, user);

View File

@ -2,13 +2,13 @@
using System.Net.Http; using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -26,7 +26,7 @@ public class AudioHelper
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
private readonly IHttpClientFactory _httpClientFactory; private readonly IHttpClientFactory _httpClientFactory;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
private readonly EncodingHelper _encodingHelper; private readonly EncodingHelper _encodingHelper;
@ -39,7 +39,7 @@ public class AudioHelper
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param> /// <param name="transcodeManager">Instance of <see cref="ITranscodeManager"/> interface.</param>
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param> /// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param> /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
@ -49,7 +49,7 @@ public class AudioHelper
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
EncodingHelper encodingHelper) EncodingHelper encodingHelper)
@ -59,7 +59,7 @@ public class AudioHelper
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
_httpClientFactory = httpClientFactory; _httpClientFactory = httpClientFactory;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
_encodingHelper = encodingHelper; _encodingHelper = encodingHelper;
@ -94,7 +94,7 @@ public class AudioHelper
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
transcodingJobType, transcodingJobType,
cancellationTokenSource.Token) cancellationTokenSource.Token)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -133,7 +133,7 @@ public class AudioHelper
if (state.MediaSource.IsInfiniteStream) if (state.MediaSource.IsInfiniteStream)
{ {
var stream = new ProgressiveFileStream(state.MediaPath, null, _transcodingJobHelper); var stream = new ProgressiveFileStream(state.MediaPath, null, _transcodeManager);
return new FileStreamResult(stream, contentType); return new FileStreamResult(stream, contentType);
} }
@ -149,7 +149,7 @@ public class AudioHelper
state, state,
isHeadRequest, isHeadRequest,
_httpContextAccessor.HttpContext, _httpContextAccessor.HttpContext,
_transcodingJobHelper, _transcodeManager,
ffmpegCommandLineArguments, ffmpegCommandLineArguments,
transcodingJobType, transcodingJobType,
cancellationTokenSource).ConfigureAwait(false); cancellationTokenSource).ConfigureAwait(false);

View File

@ -8,7 +8,6 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@ -18,6 +17,7 @@ using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using MediaBrowser.Controller.Trickplay; using MediaBrowser.Controller.Trickplay;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -39,7 +39,7 @@ public class DynamicHlsHelper
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IServerConfigurationManager _serverConfigurationManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly TranscodingJobHelper _transcodingJobHelper; private readonly ITranscodeManager _transcodeManager;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly ILogger<DynamicHlsHelper> _logger; private readonly ILogger<DynamicHlsHelper> _logger;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
@ -54,7 +54,7 @@ public class DynamicHlsHelper
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of <see cref="TranscodingJobHelper"/>.</param> /// <param name="transcodeManager">Instance of <see cref="ITranscodeManager"/>.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param> /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsHelper}"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsHelper}"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param> /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
@ -66,7 +66,7 @@ public class DynamicHlsHelper
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IServerConfigurationManager serverConfigurationManager, IServerConfigurationManager serverConfigurationManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
INetworkManager networkManager, INetworkManager networkManager,
ILogger<DynamicHlsHelper> logger, ILogger<DynamicHlsHelper> logger,
IHttpContextAccessor httpContextAccessor, IHttpContextAccessor httpContextAccessor,
@ -78,7 +78,7 @@ public class DynamicHlsHelper
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_serverConfigurationManager = serverConfigurationManager; _serverConfigurationManager = serverConfigurationManager;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_transcodingJobHelper = transcodingJobHelper; _transcodeManager = transcodeManager;
_networkManager = networkManager; _networkManager = networkManager;
_logger = logger; _logger = logger;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
@ -130,7 +130,7 @@ public class DynamicHlsHelper
_serverConfigurationManager, _serverConfigurationManager,
_mediaEncoder, _mediaEncoder,
_encodingHelper, _encodingHelper,
_transcodingJobHelper, _transcodeManager,
transcodingJobType, transcodingJobType,
cancellationTokenSource.Token) cancellationTokenSource.Token)
.ConfigureAwait(false); .ConfigureAwait(false);

View File

@ -4,9 +4,9 @@ using System.Net.Http;
using System.Net.Mime; using System.Net.Mime;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@ -65,7 +65,7 @@ public static class FileStreamResponseHelpers
/// <param name="state">The current <see cref="StreamState"/>.</param> /// <param name="state">The current <see cref="StreamState"/>.</param>
/// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param> /// <param name="isHeadRequest">Whether the current request is a HTTP HEAD request so only the headers get returned.</param>
/// <param name="httpContext">The current http context.</param> /// <param name="httpContext">The current http context.</param>
/// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param> /// <param name="transcodeManager">The <see cref="ITranscodeManager"/> singleton.</param>
/// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param> /// <param name="ffmpegCommandLineArguments">The command line arguments to start ffmpeg.</param>
/// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param> /// <param name="transcodingJobType">The <see cref="TranscodingJobType"/>.</param>
/// <param name="cancellationTokenSource">The <see cref="CancellationTokenSource"/>.</param> /// <param name="cancellationTokenSource">The <see cref="CancellationTokenSource"/>.</param>
@ -74,7 +74,7 @@ public static class FileStreamResponseHelpers
StreamState state, StreamState state,
bool isHeadRequest, bool isHeadRequest,
HttpContext httpContext, HttpContext httpContext,
TranscodingJobHelper transcodingJobHelper, ITranscodeManager transcodeManager,
string ffmpegCommandLineArguments, string ffmpegCommandLineArguments,
TranscodingJobType transcodingJobType, TranscodingJobType transcodingJobType,
CancellationTokenSource cancellationTokenSource) CancellationTokenSource cancellationTokenSource)
@ -93,22 +93,28 @@ public static class FileStreamResponseHelpers
return new OkResult(); return new OkResult();
} }
var transcodingLock = transcodingJobHelper.GetTranscodingLock(outputPath); var transcodingLock = transcodeManager.GetTranscodingLock(outputPath);
await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
try try
{ {
TranscodingJobDto? job; TranscodingJob? job;
if (!File.Exists(outputPath)) if (!File.Exists(outputPath))
{ {
job = await transcodingJobHelper.StartFfMpeg(state, outputPath, ffmpegCommandLineArguments, httpContext.Request, transcodingJobType, cancellationTokenSource).ConfigureAwait(false); job = await transcodeManager.StartFfMpeg(
state,
outputPath,
ffmpegCommandLineArguments,
httpContext.User.GetUserId(),
transcodingJobType,
cancellationTokenSource).ConfigureAwait(false);
} }
else else
{ {
job = transcodingJobHelper.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); job = transcodeManager.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
state.Dispose(); state.Dispose();
} }
var stream = new ProgressiveFileStream(outputPath, job, transcodingJobHelper); var stream = new ProgressiveFileStream(outputPath, job, transcodeManager);
return new FileStreamResult(stream, contentType); return new FileStreamResult(stream, contentType);
} }
finally finally

Some files were not shown because too many files have changed in this diff Show More