diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml
deleted file mode 100644
index 547a514f86..0000000000
--- a/.ci/azure-pipelines-abi.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-parameters:
-- name: Packages
- type: object
- default: {}
-- name: LinuxImage
- type: string
- default: "ubuntu-latest"
-- name: DotNetSdkVersion
- type: string
- default: 8.0.x
-
-jobs:
- - job: CompatibilityCheck
- displayName: Compatibility Check
- dependsOn: Build
- condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
-
- pool:
- vmImage: "${{ parameters.LinuxImage }}"
-
- strategy:
- matrix:
- ${{ each Package in parameters.Packages }}:
- ${{ Package.key }}:
- NugetPackageName: ${{ Package.value.NugetPackageName }}
- AssemblyFileName: ${{ Package.value.AssemblyFileName }}
- maxParallel: 2
-
- steps:
- - checkout: none
-
- - task: UseDotNet@2
- displayName: "Update DotNet"
- inputs:
- packageType: sdk
- version: ${{ parameters.DotNetSdkVersion }}
-
- - task: DotNetCoreCLI@2
- displayName: 'Install ABI CompatibilityChecker Tool'
- inputs:
- command: custom
- custom: tool
- arguments: 'update compatibilitychecker -g'
-
- - task: DownloadPipelineArtifact@2
- displayName: 'Download New Assembly Build Artifact'
- inputs:
- source: 'current'
- artifact: "$(NugetPackageName)"
- path: "$(System.ArtifactsDirectory)/new-artifacts"
- runVersion: "latest"
-
- - task: CopyFiles@2
- displayName: 'Copy New Assembly Build Artifact'
- inputs:
- sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
- contents: '**/*.dll'
- targetFolder: $(System.ArtifactsDirectory)/new-release
- cleanTargetFolder: true
- overWrite: true
- flattenFolders: true
-
- - task: DownloadPipelineArtifact@2
- displayName: 'Download Reference Assembly Build Artifact'
- enabled: false
- inputs:
- source: "specific"
- artifact: "$(NugetPackageName)"
- path: "$(System.ArtifactsDirectory)/current-artifacts"
- project: "$(System.TeamProjectId)"
- pipeline: "$(System.DefinitionId)"
- runVersion: "latestFromBranch"
- runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
-
- - task: CopyFiles@2
- displayName: 'Copy Reference Assembly Build Artifact'
- enabled: false
- inputs:
- sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
- contents: '**/*.dll'
- targetFolder: $(System.ArtifactsDirectory)/current-release
- cleanTargetFolder: true
- overWrite: true
- flattenFolders: true
-
- - task: DotNetCoreCLI@2
- displayName: 'Execute ABI Compatibility Check Tool'
- enabled: false
- inputs:
- command: custom
- custom: compat
- arguments: 'current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
- workingDirectory: $(System.ArtifactsDirectory)
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
deleted file mode 100644
index 0702aeb6b6..0000000000
--- a/.ci/azure-pipelines-main.yml
+++ /dev/null
@@ -1,71 +0,0 @@
-parameters:
- LinuxImage: 'ubuntu-latest'
- RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
- DotNetSdkVersion: 8.0.x
-
-jobs:
- - job: Build
- displayName: Build
- strategy:
- matrix:
- Release:
- BuildConfiguration: Release
- Debug:
- BuildConfiguration: Debug
- pool:
- vmImage: '${{ parameters.LinuxImage }}'
- steps:
- - checkout: self
- clean: true
- submodules: true
- persistCredentials: true
-
- - task: UseDotNet@2
- displayName: 'Update DotNet'
- inputs:
- packageType: sdk
- version: ${{ parameters.DotNetSdkVersion }}
-
- - task: DotNetCoreCLI@2
- displayName: 'Publish Server'
- inputs:
- command: publish
- publishWebProjects: false
- projects: '${{ parameters.RestoreBuildProjects }}'
- arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
- zipAfterPublish: false
-
- - task: PublishPipelineArtifact@1
- displayName: 'Publish Artifact Naming'
- condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
- inputs:
- targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
- artifactName: 'Jellyfin.Naming'
-
- - task: PublishPipelineArtifact@1
- displayName: 'Publish Artifact Controller'
- condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
- inputs:
- targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
- artifactName: 'Jellyfin.Controller'
-
- - task: PublishPipelineArtifact@1
- displayName: 'Publish Artifact Model'
- condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
- inputs:
- targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
- artifactName: 'Jellyfin.Model'
-
- - task: PublishPipelineArtifact@1
- displayName: 'Publish Artifact Common'
- condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
- inputs:
- targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
- artifactName: 'Jellyfin.Common'
-
- - task: PublishPipelineArtifact@1
- displayName: 'Publish Artifact Extensions'
- condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
- inputs:
- targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Jellyfin.Extensions.dll'
- artifactName: 'Jellyfin.Extensions'
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
deleted file mode 100644
index b0684c0d4c..0000000000
--- a/.ci/azure-pipelines-package.yml
+++ /dev/null
@@ -1,274 +0,0 @@
-jobs:
-- job: BuildPackage
- displayName: 'Build Packages'
-
- strategy:
- matrix:
- CentOS.amd64:
- BuildConfiguration: centos.amd64
- Fedora.amd64:
- BuildConfiguration: fedora.amd64
- Debian.amd64:
- BuildConfiguration: debian.amd64
- Debian.arm64:
- BuildConfiguration: debian.arm64
- Debian.armhf:
- BuildConfiguration: debian.armhf
- Ubuntu.amd64:
- BuildConfiguration: ubuntu.amd64
- Ubuntu.arm64:
- BuildConfiguration: ubuntu.arm64
- Ubuntu.armhf:
- BuildConfiguration: ubuntu.armhf
- Linux.amd64:
- BuildConfiguration: linux.amd64
- Linux.amd64-musl:
- BuildConfiguration: linux.amd64-musl
- Linux.arm64:
- BuildConfiguration: linux.arm64
- Linux.musl-linux-arm64:
- BuildConfiguration: linux.musl-linux-arm64
- Linux.armhf:
- BuildConfiguration: linux.armhf
- Windows.amd64:
- BuildConfiguration: windows.amd64
- MacOS.amd64:
- BuildConfiguration: macos.amd64
- MacOS.arm64:
- BuildConfiguration: macos.arm64
- Portable:
- BuildConfiguration: portable
-
- pool:
- vmImage: 'ubuntu-latest'
-
- steps:
- - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
- displayName: Set release version (stable)
- condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
-
- - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) --label "org.opencontainers.image.url=$(Build.Repository.Uri)" --label "org.opencontainers.image.revision=$(Build.SourceVersion)" deployment'
- displayName: 'Build Dockerfile'
-
- - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
- displayName: 'Run Dockerfile (unstable)'
- condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
-
- - script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="no" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
- displayName: 'Run Dockerfile (stable)'
- condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
-
- - task: PublishPipelineArtifact@1
- displayName: 'Publish Release'
- inputs:
- targetPath: '$(Build.SourcesDirectory)/deployment/dist'
- artifactName: 'jellyfin-server-$(BuildConfiguration)'
-
- - task: SSH@0
- displayName: 'Create target directory on repository server'
- inputs:
- sshEndpoint: repository
- runOptions: 'inline'
- inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
-
- - task: CopyFilesOverSSH@0
- displayName: 'Upload artifacts to repository server'
- inputs:
- sshEndpoint: repository
- sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
- contents: '**'
- targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
-
-- job: OpenAPISpec
- dependsOn: Test
- condition: or(startsWith(variables['Build.SourceBranch'], 'refs/heads/master'),startsWith(variables['Build.SourceBranch'], 'refs/tags/v'))
- displayName: 'Push OpenAPI Spec to repository'
-
- pool:
- vmImage: 'ubuntu-latest'
-
- steps:
- - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
- displayName: Set release version (stable)
- condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
-
- - task: DownloadPipelineArtifact@2
- displayName: 'Download OpenAPI Spec'
- inputs:
- source: 'current'
- artifact: "OpenAPI Spec"
- path: "$(System.ArtifactsDirectory)/openapispec"
- runVersion: "latest"
-
- - task: SSH@0
- displayName: 'Create target directory on repository server'
- inputs:
- sshEndpoint: repository
- runOptions: 'inline'
- inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)'
-
- - task: CopyFilesOverSSH@0
- displayName: 'Upload artifacts to repository server'
- inputs:
- sshEndpoint: repository
- sourceFolder: '$(System.ArtifactsDirectory)/openapispec'
- contents: 'openapi.json'
- targetFolder: '/srv/repository/incoming/azure/$(Build.BuildNumber)'
-
-- job: BuildDocker
- displayName: 'Build Docker'
-
- strategy:
- matrix:
- amd64:
- BuildConfiguration: amd64
- arm64:
- BuildConfiguration: arm64
- armhf:
- BuildConfiguration: armhf
-
- pool:
- vmImage: 'ubuntu-latest'
-
- variables:
- - name: JellyfinVersion
- value: 0.0.0
-
- steps:
- - script: echo "##vso[task.setvariable variable=JellyfinVersion]$( awk -F '/' '{ print $NF }' <<<'$(Build.SourceBranch)' | sed 's/^v//' )"
- displayName: Set release version (stable)
- condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
-
- - task: Docker@2
- displayName: 'Push Unstable Image'
- condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
- inputs:
- repository: 'jellyfin/jellyfin-server'
- command: buildAndPush
- buildContext: '.'
- Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
- containerRegistry: Docker Hub
- tags: |
- unstable-$(Build.BuildNumber)-$(BuildConfiguration)
- unstable-$(BuildConfiguration)
-
- - task: Docker@2
- displayName: 'Push Stable Image'
- condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- inputs:
- repository: 'jellyfin/jellyfin-server'
- command: buildAndPush
- buildContext: '.'
- Dockerfile: 'deployment/Dockerfile.docker.$(BuildConfiguration)'
- containerRegistry: Docker Hub
- tags: |
- stable-$(Build.BuildNumber)-$(BuildConfiguration)
- $(JellyfinVersion)-$(BuildConfiguration)
-
-- job: CollectArtifacts
- timeoutInMinutes: 20
- displayName: 'Collect Artifacts'
- condition: succeededOrFailed()
- continueOnError: true
- dependsOn:
- - BuildPackage
- - BuildDocker
-
- pool:
- vmImage: 'ubuntu-latest'
-
- steps:
- - task: SSH@0
- displayName: 'Update Unstable Repository'
- continueOnError: true
- condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
- inputs:
- sshEndpoint: repository
- runOptions: 'commands'
- commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable &
-
- - task: SSH@0
- displayName: 'Update Stable Repository'
- continueOnError: true
- condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- inputs:
- sshEndpoint: repository
- runOptions: 'commands'
- commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) $(Build.SourceBranch) &
-
-- job: PublishNuget
- displayName: 'Publish NuGet packages'
-
- pool:
- vmImage: 'ubuntu-latest'
-
- variables:
- - name: JellyfinVersion
- value: $[replace(variables['Build.SourceBranch'],'refs/tags/v','')]
-
- steps:
- - task: UseDotNet@2
- displayName: 'Use .NET 8.0 sdk'
- inputs:
- packageType: 'sdk'
- version: '8.0.x'
-
- - task: DotNetCoreCLI@2
- displayName: 'Build Stable Nuget packages'
- condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- inputs:
- command: 'custom'
- projects: |
- Jellyfin.Data/Jellyfin.Data.csproj
- MediaBrowser.Common/MediaBrowser.Common.csproj
- MediaBrowser.Controller/MediaBrowser.Controller.csproj
- MediaBrowser.Model/MediaBrowser.Model.csproj
- Emby.Naming/Emby.Naming.csproj
- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
- custom: 'pack'
- arguments: -o $(Build.ArtifactStagingDirectory) -p:Version=$(JellyfinVersion)
-
- - task: DotNetCoreCLI@2
- displayName: 'Build Unstable Nuget packages'
- condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
- inputs:
- command: 'custom'
- projects: |
- Jellyfin.Data/Jellyfin.Data.csproj
- MediaBrowser.Common/MediaBrowser.Common.csproj
- MediaBrowser.Controller/MediaBrowser.Controller.csproj
- MediaBrowser.Model/MediaBrowser.Model.csproj
- Emby.Naming/Emby.Naming.csproj
- src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
- custom: 'pack'
- arguments: '--version-suffix $(Build.BuildNumber) -o $(Build.ArtifactStagingDirectory) -p:Stability=Unstable'
-
- - task: PublishBuildArtifacts@1
- displayName: 'Publish Nuget packages'
- inputs:
- pathToPublish: $(Build.ArtifactStagingDirectory)
- artifactName: Jellyfin Nuget Packages
-
- - task: NuGetCommand@2
- displayName: 'Push Nuget packages to stable feed'
- condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- inputs:
- command: 'push'
- packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
- nuGetFeedType: 'external'
- publishFeedCredentials: 'NugetOrg'
- allowPackageConflicts: true # This ignores an error if the version already exists
-
- - task: NuGetAuthenticate@1
- displayName: 'Authenticate to unstable Nuget feed'
- condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
-
- - task: NuGetCommand@2
- displayName: 'Push Nuget packages to unstable feed'
- condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
- inputs:
- command: 'push'
- packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;!$(Build.ArtifactStagingDirectory)/**/*.symbols.nupkg' # No symbols since Azure Artifact does not support it
- nuGetFeedType: 'internal'
- publishVstsFeed: '7cce6c46-d610-45e3-9fb7-65a6bfd1b671/a5746b79-f369-42db-93ff-59cd066f9327'
- allowPackageConflicts: true # This ignores an error if the version already exists
diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml
deleted file mode 100644
index 3549c691cb..0000000000
--- a/.ci/azure-pipelines-test.yml
+++ /dev/null
@@ -1,98 +0,0 @@
-parameters:
-- name: ImageNames
- type: object
- default:
- Linux: "ubuntu-latest"
- Windows: "windows-latest"
- macOS: "macos-latest"
-- name: TestProjects
- type: string
- default: "tests/**/*Tests.csproj"
-- name: DotNetSdkVersion
- type: string
- default: 8.0.x
-
-jobs:
- - job: Test
- displayName: Test
- strategy:
- matrix:
- ${{ each imageName in parameters.ImageNames }}:
- ${{ imageName.key }}:
- ImageName: ${{ imageName.value }}
- pool:
- vmImage: "$(ImageName)"
- steps:
- - checkout: self
- clean: true
- submodules: true
- persistCredentials: false
-
- # This is required for the SonarCloud analyzer
- - task: UseDotNet@2
- displayName: "Install .NET SDK 5.x"
- condition: eq(variables['ImageName'], 'ubuntu-latest')
- inputs:
- packageType: sdk
- version: '5.x'
-
- - task: UseDotNet@2
- displayName: "Update DotNet"
- inputs:
- packageType: sdk
- version: ${{ parameters.DotNetSdkVersion }}
-
- - task: SonarCloudPrepare@1
- displayName: 'Prepare analysis on SonarCloud'
- condition: eq(variables['ImageName'], 'ubuntu-latest')
- enabled: false
- inputs:
- SonarCloud: 'Sonarcloud for Jellyfin'
- organization: 'jellyfin'
- projectKey: 'jellyfin_jellyfin'
-
- - task: DotNetCoreCLI@2
- displayName: 'Run CLI Tests'
- inputs:
- command: "test"
- projects: ${{ parameters.TestProjects }}
- arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal'
- publishTestResults: true
- testRunTitle: $(Agent.JobName)
- workingDirectory: "$(Build.SourcesDirectory)"
-
- - task: SonarCloudAnalyze@1
- displayName: 'Run Code Analysis'
- condition: eq(variables['ImageName'], 'ubuntu-latest')
- enabled: false
-
- - task: SonarCloudPublish@1
- displayName: 'Publish Quality Gate Result'
- condition: eq(variables['ImageName'], 'ubuntu-latest')
- enabled: false
-
- - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
- condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
- displayName: 'Run ReportGenerator'
- inputs:
- reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
- targetdir: "$(Agent.TempDirectory)/merged/"
- reporttypes: "Cobertura"
-
- ## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
- - task: PublishCodeCoverageResults@1
- condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
- displayName: 'Publish Code Coverage'
- inputs:
- codeCoverageTool: "cobertura"
- #summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
- summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
- pathToSources: $(Build.SourcesDirectory)
- failIfCoverageEmpty: true
-
- - task: PublishPipelineArtifact@1
- displayName: 'Publish OpenAPI Artifact'
- condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- inputs:
- targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net8.0/openapi.json"
- artifactName: 'OpenAPI Spec'
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
deleted file mode 100644
index 19c9caacbb..0000000000
--- a/.ci/azure-pipelines.yml
+++ /dev/null
@@ -1,64 +0,0 @@
-name: $(Date:yyyyMMdd)$(Rev:.r)
-
-variables:
-- name: TestProjects
- value: 'tests/**/*Tests.csproj'
-- name: RestoreBuildProjects
- value: 'Jellyfin.Server/Jellyfin.Server.csproj'
-
-pr:
- autoCancel: true
-
-trigger:
- batch: true
- branches:
- include:
- - '*'
- tags:
- include:
- - 'v*'
-
-jobs:
-- ${{ if not(startsWith(variables['Build.SourceBranch'], 'refs/tags/v')) }}:
- - template: azure-pipelines-main.yml
- parameters:
- LinuxImage: 'ubuntu-latest'
- RestoreBuildProjects: $(RestoreBuildProjects)
-
-- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- - template: azure-pipelines-test.yml
- parameters:
- ImageNames:
- Linux: 'ubuntu-latest'
- Windows: 'windows-latest'
- macOS: 'macos-latest'
-
-- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- - template: azure-pipelines-test.yml
- parameters:
- ImageNames:
- Linux: 'ubuntu-latest'
-
-- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- - template: azure-pipelines-abi.yml
- parameters:
- Packages:
- Naming:
- NugetPackageName: Jellyfin.Naming
- AssemblyFileName: Emby.Naming.dll
- Controller:
- NugetPackageName: Jellyfin.Controller
- AssemblyFileName: MediaBrowser.Controller.dll
- Model:
- NugetPackageName: Jellyfin.Model
- AssemblyFileName: MediaBrowser.Model.dll
- Common:
- NugetPackageName: Jellyfin.Common
- AssemblyFileName: MediaBrowser.Common.dll
- Extensions:
- NugetPackageName: Jellyfin.Extensions
- AssemblyFileName: Jellyfin.Extensions.dll
- LinuxImage: 'ubuntu-latest'
-
-- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- - template: azure-pipelines-package.yml
diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index d9b689bb64..c6670e9f58 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
- "version": "8.0.2",
+ "version": "8.0.3",
"commands": [
"dotnet-ef"
]
diff --git a/.copr b/.copr
deleted file mode 120000
index 100fe0cd7b..0000000000
--- a/.copr
+++ /dev/null
@@ -1 +0,0 @@
-fedora
\ No newline at end of file
diff --git a/.github/workflows/ci-codeql-analysis.yml b/.github/workflows/ci-codeql-analysis.yml
index 20307dd7dd..4e2d3cdfeb 100644
--- a/.github/workflows/ci-codeql-analysis.yml
+++ b/.github/workflows/ci-codeql-analysis.yml
@@ -27,11 +27,11 @@ jobs:
dotnet-version: '8.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6
+ uses: github/codeql-action/init@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6
+ uses: github/codeql-action/autobuild@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6
+ uses: github/codeql-action/analyze@05963f47d870e2cb19a537396c1f668a348c7d8f # v3.24.8
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 8ee6b3028b..96c2df3549 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -34,7 +34,7 @@ jobs:
--verbosity minimal
- name: Merge code coverage results
- uses: danielpalme/ReportGenerator-GitHub-Action@b067e0c5d288fb4277b9f397b2dc6013f60381f0 # 5.2.2
+ uses: danielpalme/ReportGenerator-GitHub-Action@7a0988e399533f3680a732dceda1a967cafdafcd # 5.2.3
with:
reports: "**/coverage.cobertura.xml"
targetdir: "merged/"
diff --git a/.github/workflows/issue-template-check.yml b/.github/workflows/issue-template-check.yml
new file mode 100644
index 0000000000..b553db6e2c
--- /dev/null
+++ b/.github/workflows/issue-template-check.yml
@@ -0,0 +1,29 @@
+name: Check Issue Template
+on:
+ issues:
+ types:
+ - opened
+jobs:
+ check_issue:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - name: pull in script
+ uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
+ with:
+ repository: jellyfin/jellyfin-triage-script
+ - name: install python
+ uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
+ with:
+ python-version: '3.12'
+ cache: 'pip'
+ - name: install python packages
+ run: pip install -r main-repo-triage/requirements.txt
+ - name: check and comment issue
+ working-directory: ./main-repo-triage
+ run: python3 single_issue_gha.py
+ env:
+ GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
+ GH_REPO: ${{ github.repository }}
+ ISSUE: ${{ github.event.issue.number }}
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 55642e4e21..e9c0fb2ad7 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -180,6 +180,7 @@
_ [Barasingha](https://github.com/MaVdbussche)
- [Gauvino](https://github.com/Gauvino)
- [felix920506](https://github.com/felix920506)
+ - [btopherjohnson](https://github.com/btopherjohnson)
# Emby Contributors
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 7400edd93d..093193e608 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -8,11 +8,11 @@
-
+
-
+
@@ -24,28 +24,28 @@
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
+
+
-
+
@@ -72,16 +72,16 @@
-
+
-
+
-
+
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 550c3203d7..0000000000
--- a/Dockerfile
+++ /dev/null
@@ -1,87 +0,0 @@
-# DESIGNED FOR BUILDING ON AMD64 ONLY
-#####################################
-# Requires binfm_misc registration
-# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
-ARG DOTNET_VERSION=8.0
-
-FROM node:20-alpine as web-builder
-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 \
- && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
- && apk del curl \
- && cd jellyfin-web-* \
- && npm ci --no-audit --unsafe-perm \
- && npm run build:production \
- && mv dist /dist
-
-FROM debian:bookworm-slim as app
-
-# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
-ARG DEBIAN_FRONTEND="noninteractive"
-# http://stackoverflow.com/questions/48162574/ddg#49462622
-ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
-# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
-ENV NVIDIA_VISIBLE_DEVICES="all"
-ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
-
-ENV JELLYFIN_DATA_DIR=/config
-ENV JELLYFIN_CACHE_DIR=/cache
-
-# https://github.com/intel/compute-runtime/releases
-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
-
-RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
- && 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 \
- && apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y mesa-va-drivers jellyfin-ffmpeg6 openssl locales \
-# Intel VAAPI Tone mapping dependencies:
-# 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.
- && mkdir intel-compute-runtime \
- && cd intel-compute-runtime \
- && curl -LO 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 \
- -LO 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}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \
- -LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/libigdgmm12_${GMMLIB_VERSION}_amd64.deb \
- && dpkg -i *.deb \
- && cd .. \
- && rm -rf intel-compute-runtime \
- && apt-get remove gnupg -y \
- && apt-get clean autoclean -y \
- && apt-get autoremove -y \
- && rm -rf /var/lib/apt/lists/* \
- && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
- && 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
-
-ENV LC_ALL=en_US.UTF-8
-ENV LANG=en_US.UTF-8
-ENV LANGUAGE=en_US:en
-
-FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
-WORKDIR /repo
-COPY . .
-ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
-
-FROM app
-
-ENV HEALTHCHECK_URL=http://localhost:8096/health
-
-COPY --from=builder /jellyfin /jellyfin
-COPY --from=web-builder /dist /jellyfin/jellyfin-web
-
-EXPOSE 8096
-VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
-ENTRYPOINT [ "./jellyfin/jellyfin", \
- "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
-
-HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
- CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
diff --git a/Dockerfile.arm b/Dockerfile.arm
deleted file mode 100644
index 07039e43b5..0000000000
--- a/Dockerfile.arm
+++ /dev/null
@@ -1,74 +0,0 @@
-# DESIGNED FOR BUILDING ON ARM ONLY
-#####################################
-# Requires binfm_misc registration
-# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
-ARG DOTNET_VERSION=8.0
-
-FROM node:20-alpine as web-builder
-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 \
- && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
- && apk del curl \
- && cd jellyfin-web-* \
- && npm ci --no-audit --unsafe-perm \
- && npm run build:production \
- && mv dist /dist
-
-FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM arm32v7/debian:bookworm-slim as app
-
-# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
-ARG DEBIAN_FRONTEND="noninteractive"
-# http://stackoverflow.com/questions/48162574/ddg#49462622
-ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
-# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
-ENV NVIDIA_VISIBLE_DEVICES="all"
-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
-
-RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
- && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
- && 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=$( 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 install --no-install-recommends --no-install-suggests -y \
- jellyfin-ffmpeg6 libssl-dev libfontconfig1 \
- libfreetype6 vainfo libva2 locales \
- && apt-get remove gnupg -y \
- && apt-get clean autoclean -y \
- && apt-get autoremove -y \
- && rm -rf /var/lib/apt/lists/* \
- && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
- && 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
-
-ENV LC_ALL=en_US.UTF-8
-ENV LANG=en_US.UTF-8
-ENV LANGUAGE=en_US:en
-
-FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
-WORKDIR /repo
-COPY . .
-ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
-
-FROM app
-
-ENV HEALTHCHECK_URL=http://localhost:8096/health
-
-COPY --from=builder /jellyfin /jellyfin
-COPY --from=web-builder /dist /jellyfin/jellyfin-web
-
-EXPOSE 8096
-VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
-ENTRYPOINT [ "/jellyfin/jellyfin", \
- "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
-
-HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
- CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
deleted file mode 100644
index 54023794fc..0000000000
--- a/Dockerfile.arm64
+++ /dev/null
@@ -1,74 +0,0 @@
-# DESIGNED FOR BUILDING ON ARM64 ONLY
-#####################################
-# Requires binfm_misc registration
-# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
-ARG DOTNET_VERSION=8.0
-
-FROM node:20-alpine as web-builder
-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 \
- && curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
- && apk del curl \
- && cd jellyfin-web-* \
- && npm ci --no-audit --unsafe-perm \
- && npm run build:production \
- && mv dist /dist
-
-FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM arm64v8/debian:bookworm-slim as app
-
-# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
-ARG DEBIAN_FRONTEND="noninteractive"
-# http://stackoverflow.com/questions/48162574/ddg#49462622
-ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
-# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
-ENV NVIDIA_VISIBLE_DEVICES="all"
-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
-
-RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
- && curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
- && 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=$( 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 install --no-install-recommends --no-install-suggests -y \
- jellyfin-ffmpeg6 locales libssl-dev libfontconfig1 \
- libfreetype6 libomxil-bellagio0 libomxil-bellagio-bin \
- && apt-get remove gnupg -y \
- && apt-get clean autoclean -y \
- && apt-get autoremove -y \
- && rm -rf /var/lib/apt/lists/* \
- && mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
- && 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
-
-ENV LC_ALL=en_US.UTF-8
-ENV LANG=en_US.UTF-8
-ENV LANGUAGE=en_US:en
-
-FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
-WORKDIR /repo
-COPY . .
-ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
-
-RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
-
-FROM app
-
-ENV HEALTHCHECK_URL=http://localhost:8096/health
-
-COPY --from=builder /jellyfin /jellyfin
-COPY --from=web-builder /dist /jellyfin/jellyfin-web
-
-EXPOSE 8096
-VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
-ENTRYPOINT [ "/jellyfin/jellyfin", \
- "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
-
-HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
- CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
diff --git a/Emby.Photos/PhotoProvider.cs b/Emby.Photos/PhotoProvider.cs
index 27329a7f2f..e2f1ca813c 100644
--- a/Emby.Photos/PhotoProvider.cs
+++ b/Emby.Photos/PhotoProvider.cs
@@ -16,167 +16,160 @@ using TagLib.IFD;
using TagLib.IFD.Entries;
using TagLib.IFD.Tags;
-namespace Emby.Photos
+namespace Emby.Photos;
+
+///
+/// Metadata provider for photos.
+///
+public class PhotoProvider : ICustomMetadataProvider, IForcedProvider, IHasItemChangeMonitor
{
+ private readonly ILogger _logger;
+ private readonly IImageProcessor _imageProcessor;
+
+ // These are causing taglib to hang
+ private readonly string[] _includeExtensions = [".jpg", ".jpeg", ".png", ".tiff", ".cr2", ".webp", ".avif"];
+
///
- /// Metadata provider for photos.
+ /// Initializes a new instance of the class.
///
- public class PhotoProvider : ICustomMetadataProvider, IForcedProvider, IHasItemChangeMonitor
+ /// The logger.
+ /// The image processor.
+ public PhotoProvider(ILogger logger, IImageProcessor imageProcessor)
{
- private readonly ILogger _logger;
- private readonly IImageProcessor _imageProcessor;
+ _logger = logger;
+ _imageProcessor = imageProcessor;
+ }
- // These are causing taglib to hang
- private readonly string[] _includeExtensions = new string[] { ".jpg", ".jpeg", ".png", ".tiff", ".cr2", ".webp", ".avif" };
+ ///
+ public string Name => "Embedded Information";
- ///
- /// Initializes a new instance of the class.
- ///
- /// The logger.
- /// The image processor.
- public PhotoProvider(ILogger logger, IImageProcessor imageProcessor)
+ ///
+ public bool HasChanged(BaseItem item, IDirectoryService directoryService)
+ {
+ if (item.IsFileProtocol)
{
- _logger = logger;
- _imageProcessor = imageProcessor;
+ var file = directoryService.GetFile(item.Path);
+ return file is not null && file.LastWriteTimeUtc != item.DateModified;
}
- ///
- public string Name => "Embedded Information";
+ return false;
+ }
- ///
- public bool HasChanged(BaseItem item, IDirectoryService directoryService)
+ ///
+ public Task FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken)
+ {
+ item.SetImagePath(ImageType.Primary, item.Path);
+
+ // Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
+ if (_includeExtensions.Contains(Path.GetExtension(item.Path.AsSpan()), StringComparison.OrdinalIgnoreCase))
{
- if (item.IsFileProtocol)
+ try
{
- var file = directoryService.GetFile(item.Path);
- return file is not null && file.LastWriteTimeUtc != item.DateModified;
- }
-
- return false;
- }
-
- ///
- public Task FetchAsync(Photo item, MetadataRefreshOptions options, CancellationToken cancellationToken)
- {
- item.SetImagePath(ImageType.Primary, item.Path);
-
- // Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
- if (_includeExtensions.Contains(Path.GetExtension(item.Path.AsSpan()), StringComparison.OrdinalIgnoreCase))
- {
- try
+ using var file = TagLib.File.Create(item.Path);
+ if (file.GetTag(TagTypes.TiffIFD) is IFDTag tag)
{
- using (var file = TagLib.File.Create(item.Path))
+ var structure = tag.Structure;
+ if (structure?.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) is SubIFDEntry exif)
{
- if (file.GetTag(TagTypes.TiffIFD) is IFDTag tag)
+ var exifStructure = exif.Structure;
+ if (exifStructure is not null)
{
- var structure = tag.Structure;
- if (structure is not null
- && structure.GetEntry(0, (ushort)IFDEntryTag.ExifIFD) is SubIFDEntry exif)
+ if (exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) is RationalIFDEntry apertureEntry)
{
- var exifStructure = exif.Structure;
- if (exifStructure is not null)
- {
- var entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ApertureValue) as RationalIFDEntry;
- if (entry is not null)
- {
- item.Aperture = (double)entry.Value.Numerator / entry.Value.Denominator;
- }
-
- entry = exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) as RationalIFDEntry;
- if (entry is not null)
- {
- item.ShutterSpeed = (double)entry.Value.Numerator / entry.Value.Denominator;
- }
- }
- }
- }
-
- if (file is TagLib.Image.File image)
- {
- item.CameraMake = image.ImageTag.Make;
- item.CameraModel = image.ImageTag.Model;
-
- item.Width = image.Properties.PhotoWidth;
- item.Height = image.Properties.PhotoHeight;
-
- var rating = image.ImageTag.Rating;
- item.CommunityRating = rating.HasValue ? rating : null;
-
- item.Overview = image.ImageTag.Comment;
-
- if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
- && !item.LockedFields.Contains(MetadataField.Name))
- {
- item.Name = image.ImageTag.Title;
+ item.Aperture = (double)apertureEntry.Value.Numerator / apertureEntry.Value.Denominator;
}
- var dateTaken = image.ImageTag.DateTime;
- if (dateTaken.HasValue)
+ if (exifStructure.GetEntry(0, (ushort)ExifEntryTag.ShutterSpeedValue) is RationalIFDEntry shutterSpeedEntry)
{
- item.DateCreated = dateTaken.Value;
- item.PremiereDate = dateTaken.Value;
- item.ProductionYear = dateTaken.Value.Year;
- }
-
- item.Genres = image.ImageTag.Genres;
- item.Tags = image.ImageTag.Keywords;
- item.Software = image.ImageTag.Software;
-
- if (image.ImageTag.Orientation == TagLib.Image.ImageOrientation.None)
- {
- item.Orientation = null;
- }
- else if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation))
- {
- item.Orientation = orientation;
- }
-
- item.ExposureTime = image.ImageTag.ExposureTime;
- item.FocalLength = image.ImageTag.FocalLength;
-
- item.Latitude = image.ImageTag.Latitude;
- item.Longitude = image.ImageTag.Longitude;
- item.Altitude = image.ImageTag.Altitude;
-
- if (image.ImageTag.ISOSpeedRatings.HasValue)
- {
- item.IsoSpeedRating = Convert.ToInt32(image.ImageTag.ISOSpeedRatings.Value);
- }
- else
- {
- item.IsoSpeedRating = null;
+ item.ShutterSpeed = (double)shutterSpeedEntry.Value.Numerator / shutterSpeedEntry.Value.Denominator;
}
}
}
}
- catch (Exception ex)
+
+ if (file is TagLib.Image.File image)
{
- _logger.LogError(ex, "Image Provider - Error reading image tag for {0}", item.Path);
- }
- }
+ item.CameraMake = image.ImageTag.Make;
+ item.CameraModel = image.ImageTag.Model;
- if (item.Width <= 0 || item.Height <= 0)
- {
- var img = item.GetImageInfo(ImageType.Primary, 0);
+ item.Width = image.Properties.PhotoWidth;
+ item.Height = image.Properties.PhotoHeight;
- try
- {
- var size = _imageProcessor.GetImageDimensions(item, img);
+ item.CommunityRating = image.ImageTag.Rating;
- if (size.Width > 0 && size.Height > 0)
+ item.Overview = image.ImageTag.Comment;
+
+ if (!string.IsNullOrWhiteSpace(image.ImageTag.Title)
+ && !item.LockedFields.Contains(MetadataField.Name))
{
- item.Width = size.Width;
- item.Height = size.Height;
+ item.Name = image.ImageTag.Title;
+ }
+
+ var dateTaken = image.ImageTag.DateTime;
+ if (dateTaken.HasValue)
+ {
+ item.DateCreated = dateTaken.Value;
+ item.PremiereDate = dateTaken.Value;
+ item.ProductionYear = dateTaken.Value.Year;
+ }
+
+ item.Genres = image.ImageTag.Genres;
+ item.Tags = image.ImageTag.Keywords;
+ item.Software = image.ImageTag.Software;
+
+ if (image.ImageTag.Orientation == TagLib.Image.ImageOrientation.None)
+ {
+ item.Orientation = null;
+ }
+ else if (Enum.TryParse(image.ImageTag.Orientation.ToString(), true, out ImageOrientation orientation))
+ {
+ item.Orientation = orientation;
+ }
+
+ item.ExposureTime = image.ImageTag.ExposureTime;
+ item.FocalLength = image.ImageTag.FocalLength;
+
+ item.Latitude = image.ImageTag.Latitude;
+ item.Longitude = image.ImageTag.Longitude;
+ item.Altitude = image.ImageTag.Altitude;
+
+ if (image.ImageTag.ISOSpeedRatings.HasValue)
+ {
+ item.IsoSpeedRating = Convert.ToInt32(image.ImageTag.ISOSpeedRatings.Value);
+ }
+ else
+ {
+ item.IsoSpeedRating = null;
}
}
- catch (ArgumentException)
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Image Provider - Error reading image tag for {0}", item.Path);
+ }
+ }
+
+ if (item.Width <= 0 || item.Height <= 0)
+ {
+ var img = item.GetImageInfo(ImageType.Primary, 0);
+
+ try
+ {
+ var size = _imageProcessor.GetImageDimensions(item, img);
+
+ if (size.Width > 0 && size.Height > 0)
{
- // format not supported
+ item.Width = size.Width;
+ item.Height = size.Height;
}
}
-
- const ItemUpdateType Result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport;
- return Task.FromResult(Result);
+ catch (ArgumentException)
+ {
+ // format not supported
+ }
}
+
+ const ItemUpdateType Result = ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataImport;
+ return Task.FromResult(Result);
}
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 745753440d..acabbb059b 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -146,7 +146,7 @@ namespace Emby.Server.Implementations
_startupConfig = startupConfig;
Logger = LoggerFactory.CreateLogger();
- _deviceId = new DeviceId(ApplicationPaths, LoggerFactory);
+ _deviceId = new DeviceId(ApplicationPaths, LoggerFactory.CreateLogger());
ApplicationVersion = typeof(ApplicationHost).Assembly.GetName().Version;
ApplicationVersionString = ApplicationVersion.ToString(3);
diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs
index b3f5549bcd..2459178d81 100644
--- a/Emby.Server.Implementations/Devices/DeviceId.cs
+++ b/Emby.Server.Implementations/Devices/DeviceId.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -17,19 +15,19 @@ namespace Emby.Server.Implementations.Devices
private readonly ILogger _logger;
private readonly object _syncLock = new object();
- private string _id;
+ private string? _id;
- public DeviceId(IApplicationPaths appPaths, ILoggerFactory loggerFactory)
+ public DeviceId(IApplicationPaths appPaths, ILogger logger)
{
_appPaths = appPaths;
- _logger = loggerFactory.CreateLogger();
+ _logger = logger;
}
- public string Value => _id ?? (_id = GetDeviceId());
+ public string Value => _id ??= GetDeviceId();
private string CachePath => Path.Combine(_appPaths.DataPath, "device.txt");
- private string GetCachedId()
+ private string? GetCachedId()
{
try
{
@@ -65,7 +63,7 @@ namespace Emby.Server.Implementations.Devices
{
var path = CachePath;
- Directory.CreateDirectory(Path.GetDirectoryName(path));
+ Directory.CreateDirectory(Path.GetDirectoryName(path) ?? throw new InvalidOperationException("Path can't be a root directory."));
lock (_syncLock)
{
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index c380d67db1..67854a2a7a 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Security;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.IO;
@@ -643,7 +644,15 @@ namespace Emby.Server.Implementations.IO
///
public virtual IEnumerable GetFileSystemEntryPaths(string path, bool recursive = false)
{
- return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
+ try
+ {
+ return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
+ }
+ catch (Exception ex) when (ex is UnauthorizedAccessException or DirectoryNotFoundException or SecurityException)
+ {
+ _logger.LogError(ex, "Failed to enumerate path {Path}", path);
+ return Enumerable.Empty();
+ }
}
///
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index d4aeae41a5..0ebfe3ae71 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -37,16 +35,16 @@ namespace Emby.Server.Implementations.Library
_appPaths = appPaths;
}
- public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string cacheKey, bool addProbeDelay, CancellationToken cancellationToken)
+ public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string? cacheKey, bool addProbeDelay, CancellationToken cancellationToken)
{
var originalRuntime = mediaSource.RunTimeTicks;
var now = DateTime.UtcNow;
- MediaInfo mediaInfo = null;
+ MediaInfo? mediaInfo = null;
var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json");
- if (!string.IsNullOrEmpty(cacheKey))
+ if (cacheFilePath is not null)
{
try
{
@@ -91,7 +89,7 @@ namespace Emby.Server.Implementations.Library
if (cacheFilePath is not null)
{
- Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
+ Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath) ?? throw new InvalidOperationException("Path can't be a root directory."));
FileStream createStream = AsyncFile.OpenWrite(cacheFilePath);
await using (createStream.ConfigureAwait(false))
{
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index 078f4ad219..a69a0f33f3 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -13,7 +11,6 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
-using MediaBrowser.Model.Querying;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
namespace Emby.Server.Implementations.Library
@@ -27,33 +24,35 @@ namespace Emby.Server.Implementations.Library
_libraryManager = libraryManager;
}
- public List GetInstantMixFromSong(Audio item, User user, DtoOptions dtoOptions)
+ public List GetInstantMixFromSong(Audio item, User? user, DtoOptions dtoOptions)
{
- var list = new List