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