aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines-abi.yml93
-rw-r--r--.ci/azure-pipelines-main.yml71
-rw-r--r--.ci/azure-pipelines-package.yml274
-rw-r--r--.ci/azure-pipelines-test.yml98
-rw-r--r--.ci/azure-pipelines.yml64
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs194
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs2
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs6
8 files changed, 169 insertions, 633 deletions
diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml
deleted file mode 100644
index 547a514f8..000000000
--- 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 0702aeb6b..000000000
--- 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 b0684c0d4..000000000
--- 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 3549c691c..000000000
--- 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 19c9caacb..000000000
--- 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/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index b6738e7cc..946f7266c 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -253,6 +253,14 @@ namespace MediaBrowser.Controller.MediaEncoding
&& _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVulkanFrameSync);
}
+ private bool IsVideoToolboxFullSupported()
+ {
+ return _mediaEncoder.SupportsHwaccel("videotoolbox")
+ && _mediaEncoder.SupportsFilter("yadif_videotoolbox")
+ && _mediaEncoder.SupportsFilter("overlay_videotoolbox")
+ && _mediaEncoder.SupportsFilter("scale_vt");
+ }
+
private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
if (state.VideoStream is null
@@ -272,7 +280,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
- return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder;
+ var isVideoToolBoxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
+ return isSwDecoder || isNvdecDecoder || isVaapiDecoder || isD3d11vaDecoder || isVideoToolBoxDecoder;
}
return state.VideoStream.VideoRange == VideoRange.HDR
@@ -308,6 +317,21 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.VideoStream.VideoRangeType == VideoRangeType.HDR10;
}
+ private bool IsVideoToolboxTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
+ {
+ if (state.VideoStream is null
+ || !options.EnableVideoToolboxTonemapping
+ || GetVideoColorBitDepth(state) != 10)
+ {
+ return false;
+ }
+
+ // Certain DV profile 5 video works in Safari with direct playing, but the VideoToolBox does not produce correct mapping results with transcoding.
+ // All other HDR formats working.
+ return state.VideoStream.VideoRange == VideoRange.HDR
+ && state.VideoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG or VideoRangeType.HDR10Plus;
+ }
+
/// <summary>
/// Gets the name of the output video codec.
/// </summary>
@@ -4954,22 +4978,30 @@ namespace MediaBrowser.Controller.MediaEncoding
return (null, null, null);
}
- var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
+ var isMacOS = OperatingSystem.IsMacOS();
+ var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
+ var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
+ var isVtFullSupported = isMacOS && IsVideoToolboxFullSupported();
+ var isVtOclSupported = isVtFullSupported && IsOpenclFullSupported();
- if (!options.EnableHardwareEncoding)
+ // legacy videotoolbox pipeline (disable hw filters)
+ if (!isVtEncoder
+ || !isVtOclSupported
+ || !_mediaEncoder.SupportsFilter("alphasrc"))
{
- return swFilterChain;
+ return GetSwVidFilterChain(state, options, vidEncoder);
}
- if (_mediaEncoder.EncoderVersion.CompareTo(new Version("5.0.0")) < 0)
- {
- // All features used here requires ffmpeg 5.0 or later, fallback to software filters if using an old ffmpeg
- return swFilterChain;
- }
+ // preferred videotoolbox + vt/ocl filters pipeline
+ return GetAppleVidFiltersPreferred(state, options, vidDecoder, vidEncoder);
+ }
- var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
- var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
- var doDeintH2645 = doDeintH264 || doDeintHevc;
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFiltersPreferred(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidDecoder,
+ string vidEncoder)
+ {
var inW = state.VideoStream?.Width;
var inH = state.VideoStream?.Height;
var reqW = state.BaseRequest.Width;
@@ -4977,33 +5009,114 @@ namespace MediaBrowser.Controller.MediaEncoding
var reqMaxW = state.BaseRequest.MaxWidth;
var reqMaxH = state.BaseRequest.MaxHeight;
var threeDFormat = state.MediaSource.Video3DFormat;
- var newfilters = new List<string>();
- var noOverlay = swFilterChain.OverlayFilters.Count == 0;
- var supportsHwDeint = _mediaEncoder.SupportsFilter("yadif_videotoolbox");
- // fallback to software filters if we are using filters not supported by hardware yet.
- var useHardwareFilters = noOverlay && (!doDeintH2645 || supportsHwDeint);
- if (!useHardwareFilters)
+ var isVtEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
+
+ var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
+ var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
+ var doDeintH2645 = doDeintH264 || doDeintHevc;
+ var doVtTonemap = IsVideoToolboxTonemapAvailable(state, options);
+ var doOclTonemap = !doVtTonemap && IsHwTonemapAvailable(state, options);
+
+ var scaleFormat = string.Empty;
+ if (!string.Equals(state.VideoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase))
{
- return swFilterChain;
+ // Use P010 for OpenCL tone mapping, otherwise force an 8bit output.
+ scaleFormat = doOclTonemap ? "p010le" : "nv12";
}
- // ffmpeg cannot use videotoolbox to scale
- var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
- newfilters.Add(swScaleFilter);
+ var hwScaleFilter = GetHwScaleFilter("vt", scaleFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- // hwupload on videotoolbox encoders can automatically convert AVFrame into its CVPixelBuffer equivalent
- // videotoolbox will automatically convert the CVPixelBuffer to a pixel format the encoder supports, so we don't have to set a pixel format explicitly here
- // This will reduce CPU usage significantly on UHD videos with 10 bit colors because we bypassed the ffmpeg pixel format conversion
- newfilters.Add("hwupload");
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
+ var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
+ var hasAssSubs = hasSubs
+ && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+
+ if (!isVtEncoder)
+ {
+ // should not happen.
+ return (null, null, null);
+ }
+
+ /* Make main filters for video stream */
+ var mainFilters = new List<string>();
+ // Color override is only required for OpenCL where hardware surface is in use
+ if (doOclTonemap)
+ {
+ mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
+ }
+
+ // INPUT videotoolbox/memory surface(vram/uma)
+ // this will pass-through automatically if in/out format matches.
+ mainFilters.Add("format=nv12|p010le|videotoolbox_vld");
+ mainFilters.Add("hwupload=derive_device=videotoolbox");
+
+ // hw deint
if (doDeintH2645)
{
var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
- newfilters.Add(deintFilter);
+ mainFilters.Add(deintFilter);
}
- return (newfilters, swFilterChain.SubFilters, swFilterChain.OverlayFilters);
+ if (doVtTonemap)
+ {
+ const string VtTonemapArgs = "color_matrix=bt709:color_primaries=bt709:color_transfer=bt709";
+
+ // scale_vt can handle scaling & tonemapping in one shot, just like vpp_qsv.
+ hwScaleFilter = string.IsNullOrEmpty(hwScaleFilter)
+ ? "scale_vt=" + VtTonemapArgs
+ : hwScaleFilter + ":" + VtTonemapArgs;
+ }
+
+ // hw scale & vt tonemap
+ mainFilters.Add(hwScaleFilter);
+
+ // ocl tonemap
+ if (doOclTonemap)
+ {
+ // map from videotoolbox to opencl via videotoolbox-opencl interop.
+ mainFilters.Add("hwmap=derive_device=opencl:mode=read");
+
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ mainFilters.Add(tonemapFilter);
+
+ // OUTPUT videotoolbox(nv12) surface(vram/uma)
+ // reverse-mapping via videotoolbox-opencl interop.
+ mainFilters.Add("hwmap=derive_device=videotoolbox:mode=write:reverse=1");
+ }
+
+ /* Make sub and overlay filters for subtitle stream */
+ var subFilters = new List<string>();
+ var overlayFilters = new List<string>();
+
+ if (hasSubs)
+ {
+ if (hasGraphicalSubs)
+ {
+ var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subPreProcFilters);
+ subFilters.Add("format=bgra");
+ }
+ else if (hasTextSubs)
+ {
+ var framerate = state.VideoStream?.RealFrameRate;
+ var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
+
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate);
+ var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
+ subFilters.Add(alphaSrcFilter);
+ subFilters.Add("format=bgra");
+ subFilters.Add(subTextSubtitlesFilter);
+ }
+
+ subFilters.Add("hwupload=derive_device=videotoolbox");
+ overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0");
+ }
+
+ return (mainFilters, subFilters, overlayFilters);
}
/// <summary>
@@ -5995,22 +6108,37 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ // Hardware surface only make sense when interop with OpenCL
+ // VideoToolbox's Hardware surface in ffmpeg is not only slower than hwupload, but also breaks HDR in many cases.
+ // For example: https://trac.ffmpeg.org/ticket/10884
+ var useOclToneMapping = !IsVideoToolboxTonemapAvailable(state, options)
+ && options.EnableTonemapping
+ && state.VideoStream is not null
+ && GetVideoColorBitDepth(state) == 10
+ && state.VideoStream.VideoRange == VideoRange.HDR
+ && (state.VideoStream.VideoRangeType == VideoRangeType.HDR10
+ || state.VideoStream.VideoRangeType == VideoRangeType.HLG
+ || (state.VideoStream.VideoRangeType == VideoRangeType.DOVI
+ && string.Equals(state.VideoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)));
+
+ var useHwSurface = useOclToneMapping && IsVideoToolboxFullSupported() && _mediaEncoder.SupportsFilter("alphasrc");
+
if (is8bitSwFormatsVt)
{
if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
|| string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- return GetHwaccelType(state, options, "h264", bitDepth, false);
+ return GetHwaccelType(state, options, "h264", bitDepth, useHwSurface);
}
if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- return GetHwaccelType(state, options, "mpeg2video", bitDepth, false);
+ return GetHwaccelType(state, options, "mpeg2video", bitDepth, useHwSurface);
}
if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- return GetHwaccelType(state, options, "mpeg4", bitDepth, false);
+ return GetHwaccelType(state, options, "mpeg4", bitDepth, useHwSurface);
}
}
@@ -6019,12 +6147,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
|| string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- return GetHwaccelType(state, options, "hevc", bitDepth, false);
+ return GetHwaccelType(state, options, "hevc", bitDepth, useHwSurface);
}
if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- return GetHwaccelType(state, options, "vp9", bitDepth, false);
+ return GetHwaccelType(state, options, "vp9", bitDepth, useHwSurface);
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index fdca28390..6549125d3 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -128,6 +128,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
"overlay_vulkan",
// videotoolbox
"yadif_videotoolbox",
+ "scale_vt",
+ "overlay_videotoolbox",
// rkrga
"scale_rkrga",
"vpp_rkrga",
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 84c735f9c..ab6f0d867 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -28,6 +28,7 @@ public class EncodingOptions
VaapiDevice = "/dev/dri/renderD128";
EnableTonemapping = false;
EnableVppTonemapping = false;
+ EnableVideoToolboxTonemapping = false;
TonemappingAlgorithm = "bt2390";
TonemappingMode = "auto";
TonemappingRange = "auto";
@@ -147,6 +148,11 @@ public class EncodingOptions
public bool EnableVppTonemapping { get; set; }
/// <summary>
+ /// Gets or sets a value indicating whether videotoolbox tonemapping is enabled.
+ /// </summary>
+ public bool EnableVideoToolboxTonemapping { get; set; }
+
+ /// <summary>
/// Gets or sets the tone-mapping algorithm.
/// </summary>
public string TonemappingAlgorithm { get; set; }