mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-04 03:27:21 -05:00 
			
		
		
		
	Merge branch 'master' into bhowe34/fix-replace-missing-metadata-for-music
This commit is contained in:
		
						commit
						54eb81395e
					
				@ -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')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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"
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										28
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.devcontainer/devcontainer.json
									
									
									
									
									
										Normal 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
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								.github/workflows/ci-codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci-codeql-analysis.yml
									
									
									
									
										vendored
									
									
								
							@ -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
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								.github/workflows/ci-openapi.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.github/workflows/ci-openapi.yml
									
									
									
									
										vendored
									
									
								
							@ -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
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								.github/workflows/ci-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/ci-tests.yml
									
									
									
									
										vendored
									
									
								
							@ -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"
 | 
					 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/issue-stale.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/issue-stale.yml
									
									
									
									
										vendored
									
									
								
							@ -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
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								.github/workflows/project-automation.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/project-automation.yml
									
									
									
									
										vendored
									
									
								
							@ -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:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.github/workflows/pull-request-stale.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/pull-request-stale.yaml
									
									
									
									
										vendored
									
									
								
							@ -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
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
								
							@ -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": [
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	]
 | 
						]
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										68
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										68
									
								
								Dockerfile
									
									
									
									
									
								
							@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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>());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -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));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -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)
 | 
				
			||||||
 | 
				
			|||||||
@ -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" />
 | 
				
			||||||
 | 
				
			|||||||
@ -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();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -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 ||
 | 
				
			||||||
 | 
				
			|||||||
@ -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)
 | 
				
			||||||
 | 
				
			|||||||
@ -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)
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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))
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
 | 
				
			|||||||
@ -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))
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
 | 
				
			|||||||
        private CollectionType?[] _musicPlaylistCollectionTypes =
 | 
					        private CollectionType?[] _musicPlaylistCollectionTypes =
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            null,
 | 
					            null,
 | 
				
			||||||
            CollectionType.Music
 | 
					            CollectionType.music
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <inheritdoc/>
 | 
					        /// <inheritdoc/>
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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>
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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"
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -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."
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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}",
 | 
				
			||||||
 | 
				
			|||||||
@ -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",
 | 
				
			||||||
 | 
				
			|||||||
@ -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"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -124,5 +124,7 @@
 | 
				
			|||||||
    "TaskKeyframeExtractorDescription": "فریم های کلیدی را از فایل های ویدئویی استخراج می کند تا لیست های پخش HLS دقیق تری ایجاد کند. این کار ممکن است برای مدت طولانی اجرا شود.",
 | 
					    "TaskKeyframeExtractorDescription": "فریم های کلیدی را از فایل های ویدئویی استخراج می کند تا لیست های پخش HLS دقیق تری ایجاد کند. این کار ممکن است برای مدت طولانی اجرا شود.",
 | 
				
			||||||
    "TaskKeyframeExtractor": "استخراج کننده فریم کلیدی",
 | 
					    "TaskKeyframeExtractor": "استخراج کننده فریم کلیدی",
 | 
				
			||||||
    "External": "خارجی",
 | 
					    "External": "خارجی",
 | 
				
			||||||
    "HearingImpaired": "مشکل شنوایی"
 | 
					    "HearingImpaired": "مشکل شنوایی",
 | 
				
			||||||
 | 
					    "TaskRefreshTrickplayImages": "تولید تصاویر Trickplay",
 | 
				
			||||||
 | 
					    "TaskRefreshTrickplayImagesDescription": "تولید پیشنمایش های trickplay برای ویدیو های فعال شده در کتابخانه."
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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."
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										39
									
								
								Emby.Server.Implementations/Localization/Core/hy.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								Emby.Server.Implementations/Localization/Core/hy.json
									
									
									
									
									
										Normal 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}-ն միացված է"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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 სურათების გენერირება"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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."
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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."
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -117,5 +117,6 @@
 | 
				
			|||||||
    "TaskCleanActivityLog": "Slett aktivitetslogg",
 | 
					    "TaskCleanActivityLog": "Slett aktivitetslogg",
 | 
				
			||||||
    "Undefined": "Udefinert",
 | 
					    "Undefined": "Udefinert",
 | 
				
			||||||
    "Forced": "Tvungen",
 | 
					    "Forced": "Tvungen",
 | 
				
			||||||
    "Default": "Standard"
 | 
					    "Default": "Standard",
 | 
				
			||||||
 | 
					    "External": "Ekstern"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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",
 | 
				
			||||||
 | 
				
			|||||||
@ -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": "అప్లికేషన్ అప్డేట్ అందుబాటులో ఉంది"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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",
 | 
				
			||||||
 | 
				
			|||||||
@ -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} незавершено, збій",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								Emby.Server.Implementations/Localization/Core/ur.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Emby.Server.Implementations/Localization/Core/ur.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "Books": "کتابیں"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -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 預覽圖。"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -696,7 +696,7 @@
 | 
				
			|||||||
        "TwoLetterISORegionName": "SI"
 | 
					        "TwoLetterISORegionName": "SI"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        "DisplayName": "Soomaaliya",
 | 
					        "DisplayName": "Somalia",
 | 
				
			||||||
        "Name": "SO",
 | 
					        "Name": "SO",
 | 
				
			||||||
        "ThreeLetterISORegionName": "SOM",
 | 
					        "ThreeLetterISORegionName": "SOM",
 | 
				
			||||||
        "TwoLetterISORegionName": "SO"
 | 
					        "TwoLetterISORegionName": "SO"
 | 
				
			||||||
 | 
				
			|||||||
@ -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)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
				
			|||||||
@ -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)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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));
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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>
 | 
				
			||||||
 | 
				
			|||||||
@ -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)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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 }
 | 
				
			||||||
 | 
				
			|||||||
@ -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)
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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" }
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -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));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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 }
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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();
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user