diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml
new file mode 100644
index 0000000000..31f861f63f
--- /dev/null
+++ b/.ci/azure-pipelines-abi.yml
@@ -0,0 +1,94 @@
+parameters:
+- name: Packages
+ type: object
+ default: {}
+- name: LinuxImage
+ type: string
+ default: "ubuntu-latest"
+- name: DotNetSdkVersion
+ type: string
+ default: 6.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 }}
+ includePreviewVersions: true
+
+ - 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
new file mode 100644
index 0000000000..1086d51d27
--- /dev/null
+++ b/.ci/azure-pipelines-main.yml
@@ -0,0 +1,101 @@
+parameters:
+ LinuxImage: 'ubuntu-latest'
+ RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
+ DotNetSdkVersion: 6.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: DownloadPipelineArtifact@2
+ displayName: 'Download Web Branch'
+ condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
+ inputs:
+ path: '$(Agent.TempDirectory)'
+ artifact: 'jellyfin-web-production'
+ source: 'specific'
+ project: 'jellyfin'
+ pipeline: 'Jellyfin Web'
+ runBranch: variables['Build.SourceBranch']
+
+ - task: DownloadPipelineArtifact@2
+ displayName: 'Download Web Target'
+ condition: eq(variables['Build.Reason'], 'PullRequest')
+ inputs:
+ path: '$(Agent.TempDirectory)'
+ artifact: 'jellyfin-web-production'
+ source: 'specific'
+ project: 'jellyfin'
+ pipeline: 'Jellyfin Web'
+ runBranch: variables['System.PullRequest.TargetBranch']
+
+ - task: ExtractFiles@1
+ displayName: 'Extract Web Client'
+ inputs:
+ archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
+ destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
+ cleanDestinationFolder: false
+
+ - task: UseDotNet@2
+ displayName: 'Update DotNet'
+ inputs:
+ packageType: sdk
+ version: ${{ parameters.DotNetSdkVersion }}
+ includePreviewVersions: true
+
+ - 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
new file mode 100644
index 0000000000..4abe52b43b
--- /dev/null
+++ b/.ci/azure-pipelines-package.yml
@@ -0,0 +1,262 @@
+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.armhf:
+ BuildConfiguration: linux.armhf
+ Windows.amd64:
+ BuildConfiguration: windows.amd64
+ MacOS:
+ BuildConfiguration: macos
+ Portable:
+ BuildConfiguration: portable
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+ - script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) 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:
+ - 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'
+ 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) &
+
+- 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 6.0 sdk'
+ inputs:
+ packageType: 'sdk'
+ version: '6.0.x'
+ includePreviewVersions: true
+
+ - 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@0
+ 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
new file mode 100644
index 0000000000..80a5732eee
--- /dev/null
+++ b/.ci/azure-pipelines-test.yml
@@ -0,0 +1,99 @@
+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: 6.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 }}
+ includePreviewVersions: true
+
+ - 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/net6.0/openapi.json"
+ artifactName: 'OpenAPI Spec'
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
new file mode 100644
index 0000000000..19c9caacbb
--- /dev/null
+++ b/.ci/azure-pipelines.yml
@@ -0,0 +1,64 @@
+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/.copr/Makefile b/.copr/Makefile
deleted file mode 100644
index 84b98a0116..0000000000
--- a/.copr/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
-srpm:
- dnf -y install git
- git submodule update --init --recursive
- cd deployment/fedora-package-x64; \
- ./create_tarball.sh; \
- rpmbuild -bs pkg-src/jellyfin.spec \
- --define "_sourcedir $$PWD/pkg-src/" \
- --define "_srcrpmdir $(outdir)"
diff --git a/.copr/Makefile b/.copr/Makefile
new file mode 120000
index 0000000000..ec3c90dfd9
--- /dev/null
+++ b/.copr/Makefile
@@ -0,0 +1 @@
+../fedora/Makefile
\ No newline at end of file
diff --git a/.drone.yml b/.drone.yml
deleted file mode 100644
index 7705f4f936..0000000000
--- a/.drone.yml
+++ /dev/null
@@ -1,111 +0,0 @@
----
-kind: pipeline
-name: build-debug
-
-steps:
-- name: submodules
- image: docker:git
- commands:
- - git submodule update --init --recursive
-
-- name: build
- image: microsoft/dotnet:2-sdk
- commands:
- - dotnet publish "Jellyfin.Server" --configuration Debug --output "../ci/ci-debug"
-
----
-kind: pipeline
-name: build-release
-
-steps:
-- name: submodules
- image: docker:git
- commands:
- - git submodule update --init --recursive
-
-- name: build
- image: microsoft/dotnet:2-sdk
- commands:
- - dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"
-
----
-
-kind: pipeline
-name: check-abi
-
-steps:
-- name: submodules
- image: docker:git
- commands:
- - git submodule update --init --recursive
-
-- name: build
- image: microsoft/dotnet:2-sdk
- commands:
- - dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"
-
-- name: clone-dotnet-compat
- image: docker:git
- commands:
- - git clone --depth 1 https://github.com/EraYaN/dotnet-compatibility ci/dotnet-compatibility
-
-- name: build-dotnet-compat
- image: microsoft/dotnet:2-sdk
- commands:
- - dotnet publish "ci/dotnet-compatibility/CompatibilityCheckerCoreCLI" --configuration Release --output "../../ci-tools"
-
-- name: download-last-nuget-release-common
- image: plugins/download
- settings:
- source: https://www.nuget.org/api/v2/package/Jellyfin.Common
- destination: ci/Jellyfin.Common.nupkg
-
-- name: download-last-nuget-release-model
- image: plugins/download
- settings:
- source: https://www.nuget.org/api/v2/package/Jellyfin.Model
- destination: ci/Jellyfin.Model.nupkg
-
-- name: download-last-nuget-release-controller
- image: plugins/download
- settings:
- source: https://www.nuget.org/api/v2/package/Jellyfin.Controller
- destination: ci/Jellyfin.Controller.nupkg
-
-- name: download-last-nuget-release-naming
- image: plugins/download
- settings:
- source: https://www.nuget.org/api/v2/package/Jellyfin.Naming
- destination: ci/Jellyfin.Naming.nupkg
-
-- name: extract-downloaded-nuget-packages
- image: garthk/unzip
- commands:
- - unzip -j ci/Jellyfin.Common.nupkg "*.dll" -d ci/nuget-packages
- - unzip -j ci/Jellyfin.Model.nupkg "*.dll" -d ci/nuget-packages
- - unzip -j ci/Jellyfin.Controller.nupkg "*.dll" -d ci/nuget-packages
- - unzip -j ci/Jellyfin.Naming.nupkg "*.dll" -d ci/nuget-packages
-
-- name: run-dotnet-compat-common
- image: microsoft/dotnet:2-runtime
- err_ignore: true
- commands:
- - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Common.dll ci/ci-release/MediaBrowser.Common.dll
-
-- name: run-dotnet-compat-model
- image: microsoft/dotnet:2-runtime
- err_ignore: true
- commands:
- - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Model.dll ci/ci-release/MediaBrowser.Model.dll
-
-- name: run-dotnet-compat-controller
- image: microsoft/dotnet:2-runtime
- err_ignore: true
- commands:
- - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/MediaBrowser.Controller.dll ci/ci-release/MediaBrowser.Controller.dll
-
-- name: run-dotnet-compat-naming
- image: microsoft/dotnet:2-runtime
- err_ignore: true
- commands:
- - dotnet ci/ci-tools/CompatibilityCheckerCoreCLI.dll ci/nuget-packages/Emby.Naming.dll ci/ci-release/Emby.Naming.dll
diff --git a/.editorconfig b/.editorconfig
index dc9aaa3ed5..b84e563efa 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -13,7 +13,7 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
-max_line_length = null
+max_line_length = off
# YAML indentation
[*.{yml,yaml}]
@@ -22,6 +22,7 @@ indent_size = 2
# XML indentation
[*.{csproj,xml}]
indent_size = 2
+
###############################
# .NET Coding Conventions #
###############################
@@ -51,11 +52,12 @@ dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
-dotnet_prefer_inferred_tuple_names = true:suggestion
-dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
+
###############################
# Naming Conventions #
###############################
@@ -67,7 +69,7 @@ dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
-dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
+dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
@@ -159,6 +161,7 @@ csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
+
###############################
# C# Formatting Rules #
###############################
@@ -189,9 +192,3 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
-###############################
-# VB Coding Conventions #
-###############################
-[*.vb]
-# Modifier preferences
-visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
diff --git a/.gitattributes b/.gitattributes
index 0c0ab960a0..8ae5997258 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1 +1,5 @@
+* text=auto eol=lf
+*.png binary
+*.jpg binary
+
CONTRIBUTORS.md merge=union
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000000..e1900d5833
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,4 @@
+# Joshua must review all changes to deployment and build.sh
+.ci/* @joshuaboniface
+deployment/* @joshuaboniface
+build.sh @joshuaboniface
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 137a689e8b..c1d49778e3 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -10,6 +10,21 @@ assignees: ''
**Describe the bug**
+**System (please complete the following information):**
+ - OS: [e.g. Debian, Windows]
+ - Virtualization: [e.g. Docker, KVM, LXC]
+ - Clients: [Browser, Android, Fire Stick, etc.]
+ - Browser: [e.g. Firefox 91, Chrome 93, Safari 13]
+ - Jellyfin Version: [e.g. 10.7.6, unstable 20191231]
+ - FFmpeg Version: [e.g. 4.3.2-Jellyfin]
+ - Playback: [Direct Play, Remux, Direct Stream, Transcode]
+ - Hardware Acceleration: [e.g. none, VAAPI, NVENC, etc.]
+ - Installed Plugins: [e.g. none, Fanart, Anime, etc.]
+ - Reverse Proxy: [e.g. none, nginx, apache, etc.]
+ - Base URL: [e.g. none, yes: /example]
+ - Networking: [e.g. Host, Bridge/NAT]
+ - Storage: [e.g. local, NFS, cloud]
+
**To Reproduce**
1. Go to '...'
@@ -20,16 +35,17 @@ assignees: ''
**Expected behavior**
-**Logs**
+**Server Logs**
+
+
+**FFmpeg Logs**
+
+
+**Browser Console Logs**
**Screenshots**
-**System (please complete the following information):**
- - OS: [e.g. Docker, Debian, Windows]
- - Browser: [e.g. Firefox, Chrome, Safari]
- - Jellyfin Version: [e.g. 10.0.1]
-
**Additional context**
diff --git a/.github/ISSUE_TEMPLATE/enhancement-request.md b/.github/ISSUE_TEMPLATE/enhancement-request.md
deleted file mode 100644
index a655b60f53..0000000000
--- a/.github/ISSUE_TEMPLATE/enhancement-request.md
+++ /dev/null
@@ -1,20 +0,0 @@
----
-name: Enhancement request
-about: Suggest an modification to an existing feature
-title: ''
-labels: enhancement
-assignees: ''
-
----
-
-**Is your feature request related to a problem? Please describe.**
-
-
-**Describe the solution you'd like**
-
-
-**Describe alternatives you've considered**
-
-
-**Additional context**
-
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 3cbc8cbb91..d886c64871 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,14 +1,13 @@
---
-name: Feature request
-about: Suggest a new feature
+name: Feature Request
+about: Request a new feature
title: ''
-labels: feature
+labels: feature-request
assignees: ''
-
---
-**Describe the feature you'd like**
-
+**PLEASE DO NOT OPEN FEATURE REQUEST ISSUES ON GITHUB**
-**Additional context**
-
+**Feature requests should be opened on our dedicated [feature request](https://features.jellyfin.org/) hub so they can be appropriately discussed and prioritized.**
+
+However, if you are willing to contribute to the project by adding a new feature yourself, then please ensure that you first review our [documentation](https://docs.jellyfin.org/general/contributing/development.html) on contributing code. Once you have reviewed the documentation, feel free to come back here and open an issue here outlining your proposed approach so that it can be documented, tracked, and discussed by other team members.
diff --git a/.github/ISSUE_TEMPLATE/media_playback.md b/.github/ISSUE_TEMPLATE/media_playback.md
new file mode 100644
index 0000000000..b51500f870
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/media_playback.md
@@ -0,0 +1,34 @@
+---
+name: Media playback issue
+about: Create a media playback issue report
+title: ''
+labels: mediaplayback
+assignees: ''
+
+---
+
+**Media Info of the file**
+
+
+**Logs**
+
+
+**FFmpeg Logs**
+
+
+**Stats for Nerds Screenshots**
+
+
+**Server System (please complete the following information):**
+ - OS: [e.g. Docker on Linux, Docker on Windows, Debian, Windows]
+ - Jellyfin Version: [e.g. 10.0.1]
+ - Hardware settings & device: [e.g. NVENC on GTX1060, VAAPI on Intel i7 8700K]
+ - Reverse proxy: [e.g. no, nginx, apache, etc.]
+ - Other hardware notes: [e.g. Media mounted in CIFS/SMB share, Media mounted from Google Drive]
+
+**Client System (please complete the following information):**
+ - Device: [e.g. Apple iPhone XS, Xbox One S, LG OLED55C8, Samsung Galaxy Note9, Custom HTPC]
+ - OS: [e.g. iOS, Android, Windows, macOS]
+ - Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron]
+ - Browser (if Web client): [e.g. Firefox, Chrome, Safari]
+ - Client and Browser Version: [e.g. 10.3.4 and 68.0]
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..70bcd49737
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,15 @@
+version: 2
+updates:
+- package-ecosystem: nuget
+ directory: "/"
+ schedule:
+ interval: weekly
+ time: '12:00'
+ open-pull-requests-limit: 10
+
+- package-ecosystem: github-actions
+ directory: '/'
+ schedule:
+ interval: weekly
+ time: '12:00'
+ open-pull-requests-limit: 10
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 967be0fb73..dc93d2c84e 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,6 +1,6 @@
**Changes**
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 0000000000..cba9c33b2a
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,29 @@
+# Number of days of inactivity before an issue becomes stale
+daysUntilStale: 120
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 21
+# Issues with these labels will never be considered stale
+exemptLabels:
+ - regression
+ - security
+ - dotnet-3.0-future
+ - roadmap
+ - future
+ - feature
+ - enhancement
+ - confirmed
+# Label to use when marking an issue as stale
+staleLabel: stale
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+ This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
+
+ If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or nightlies, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
+
+ This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: false
+
+# Disable automatic closing of pull requests
+pulls:
+ daysUntilClose: false
diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml
new file mode 100644
index 0000000000..20294843d5
--- /dev/null
+++ b/.github/workflows/automation.yml
@@ -0,0 +1,76 @@
+name: Automation
+
+on:
+ push:
+ branches:
+ - master
+ pull_request_target:
+ issue_comment:
+
+jobs:
+ label:
+ name: Labeling
+ runs-on: ubuntu-latest
+ if: ${{ github.repository == 'jellyfin/jellyfin' }}
+ steps:
+ - name: Apply label
+ uses: eps1lon/actions-label-merge-conflict@v2.0.1
+ if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
+ with:
+ dirtyLabel: 'merge conflict'
+ repoToken: ${{ secrets.JF_BOT_TOKEN }}
+
+ project:
+ name: Project board
+ runs-on: ubuntu-latest
+ if: ${{ github.repository == 'jellyfin/jellyfin' }}
+ steps:
+ - name: Remove from 'Current Release' project
+ uses: alex-page/github-project-automation-plus@v0.8.1
+ if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
+ continue-on-error: true
+ with:
+ project: Current Release
+ action: delete
+ repo-token: ${{ secrets.JF_BOT_TOKEN }}
+
+ - name: Add to 'Release Next' project
+ uses: alex-page/github-project-automation-plus@v0.8.1
+ if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
+ continue-on-error: true
+ with:
+ project: Release Next
+ column: In progress
+ repo-token: ${{ secrets.JF_BOT_TOKEN }}
+
+ - name: Add to 'Current Release' project
+ uses: alex-page/github-project-automation-plus@v0.8.1
+ if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
+ continue-on-error: true
+ with:
+ project: Current Release
+ column: In progress
+ repo-token: ${{ secrets.JF_BOT_TOKEN }}
+
+ - name: Check number of comments from the team member
+ if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
+ id: member_comments
+ run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
+
+ - name: Move issue to needs triage
+ uses: alex-page/github-project-automation-plus@v0.8.1
+ if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
+ continue-on-error: true
+ with:
+ project: Issue Triage for Main Repo
+ column: Needs triage
+ repo-token: ${{ secrets.JF_BOT_TOKEN }}
+
+ - name: Add issue to triage project
+ uses: alex-page/github-project-automation-plus@v0.8.1
+ if: github.event.issue.pull_request == '' && github.event.action == 'opened'
+ continue-on-error: true
+ with:
+ project: Issue Triage for Main Repo
+ column: Pending response
+ repo-token: ${{ secrets.JF_BOT_TOKEN }}
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000000..e07d913b5a
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,38 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+ schedule:
+ - cron: '24 2 * * 4'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'csharp' ]
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+ - name: Setup .NET Core
+ uses: actions/setup-dotnet@v1
+ with:
+ dotnet-version: '6.0.x'
+ include-prerelease: true
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ queries: +security-extended
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml
new file mode 100644
index 0000000000..af4d8beb93
--- /dev/null
+++ b/.github/workflows/commands.yml
@@ -0,0 +1,119 @@
+name: Commands
+on:
+ issue_comment:
+ types:
+ - created
+ - edited
+ pull_request_target:
+ types:
+ - labeled
+ - synchronize
+
+jobs:
+ rebase:
+ name: Rebase
+ if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '@jellyfin-bot rebase') && github.event.comment.author_association == 'MEMBER'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Notify as seen
+ uses: peter-evans/create-or-update-comment@v1.4.5
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ comment-id: ${{ github.event.comment.id }}
+ reactions: '+1'
+
+ - name: Checkout the latest code
+ uses: actions/checkout@v2
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ fetch-depth: 0
+
+ - name: Automatic Rebase
+ uses: cirrus-actions/rebase@1.5
+ env:
+ GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
+
+ check-backport:
+ name: Check Backport
+ if: ${{ ( github.event.issue.pull_request && contains(github.event.comment.body, '@jellyfin-bot check backport') ) || github.event.label.name == 'stable backport' || contains(github.event.pull_request.labels.*.name, 'stable backport' ) }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Notify as seen
+ uses: peter-evans/create-or-update-comment@v1.4.5
+ if: ${{ github.event.comment != null }}
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ comment-id: ${{ github.event.comment.id }}
+ reactions: eyes
+
+ - name: Checkout the latest code
+ uses: actions/checkout@v2
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ fetch-depth: 0
+
+ - name: Notify as running
+ id: comment_running
+ uses: peter-evans/create-or-update-comment@v1.4.5
+ if: ${{ github.event.comment != null }}
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ issue-number: ${{ github.event.issue.number }}
+ body: |
+ Running backport tests...
+
+ - name: Perform test backport
+ id: run_tests
+ run: |
+ set +o errexit
+ git config --global user.name "Jellyfin Bot"
+ git config --global user.email "team@jellyfin.org"
+ CURRENT_BRANCH="origin/${GITHUB_HEAD_REF}"
+ git checkout master
+ git merge --no-ff ${CURRENT_BRANCH}
+ MERGE_COMMIT_HASH=$( git log -q -1 | head -1 | awk '{ print $2 }' )
+ git fetch --all
+ CURRENT_STABLE=$( git branch -r | grep 'origin/release' | sort -rV | head -1 | awk -F '/' '{ print $NF }' )
+ stable_branch="Current stable release branch: ${CURRENT_STABLE}"
+ echo ${stable_branch}
+ echo ::set-output name=branch::${stable_branch}
+ git checkout -t origin/${CURRENT_STABLE} -b ${CURRENT_STABLE}
+ git cherry-pick -sx -m1 ${MERGE_COMMIT_HASH} &>output.txt
+ retcode=$?
+ cat output.txt | grep -v 'hint:'
+ output="$( grep -v 'hint:' output.txt )"
+ output="${output//'%'/'%25'}"
+ output="${output//$'\n'/'%0A'}"
+ output="${output//$'\r'/'%0D'}"
+ echo ::set-output name=output::$output
+ exit ${retcode}
+
+ - name: Notify with result success
+ uses: peter-evans/create-or-update-comment@v1.4.5
+ if: ${{ github.event.comment != null && success() }}
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ comment-id: ${{ steps.comment_running.outputs.comment-id }}
+ body: |
+ ${{ steps.run_tests.outputs.branch }}
+ Output from `git cherry-pick`:
+
+ ---
+
+ ${{ steps.run_tests.outputs.output }}
+ reactions: hooray
+
+ - name: Notify with result failure
+ uses: peter-evans/create-or-update-comment@v1.4.5
+ if: ${{ github.event.comment != null && failure() }}
+ with:
+ token: ${{ secrets.JF_BOT_TOKEN }}
+ comment-id: ${{ steps.comment_running.outputs.comment-id }}
+ body: |
+ ${{ steps.run_tests.outputs.branch }}
+ Output from `git cherry-pick`:
+
+ ---
+
+ ${{ steps.run_tests.outputs.output }}
+ reactions: confused
diff --git a/.gitignore b/.gitignore
index 65e47747ee..c2ae76c1e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -239,24 +239,19 @@ pip-log.txt
##########
.idea/
-##########
-# Visual Studio Code
-##########
-.vscode/
-
#########################
# Build artifacts
#########################
# Artifacts for debian-x64
-deployment/debian-package-x64/pkg-src/.debhelper/
-deployment/debian-package-x64/pkg-src/*.debhelper
-deployment/debian-package-x64/pkg-src/debhelper-build-stamp
-deployment/debian-package-x64/pkg-src/files
-deployment/debian-package-x64/pkg-src/jellyfin.substvars
-deployment/debian-package-x64/pkg-src/jellyfin/
+debian/.debhelper/
+debian/*.debhelper
+debian/debhelper-build-stamp
+debian/files
+debian/jellyfin.substvars
+debian/jellyfin/
# Don't ignore the debian/bin folder
-!deployment/debian-package-x64/pkg-src/bin/
+!debian/bin/
deployment/**/dist/
deployment/**/pkg-dist/
@@ -266,3 +261,23 @@ deployment/collect-dist/
jellyfin_version.ini
ci/
+
+# Doxygen
+doc/
+
+# Deployment artifacts
+dist
+*.exe
+*.dll
+
+# BenchmarkDotNet artifacts
+BenchmarkDotNet.Artifacts
+
+# Ignore web artifacts from native builds
+web/
+web-src.*
+MediaBrowser.WebDashboard/jellyfin-web
+apiclient/generated
+
+# Omnisharp crash logs
+mono_crash.*.json
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index c10f5905c4..0000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "MediaBrowser.WebDashboard/jellyfin-web"]
- path = MediaBrowser.WebDashboard/jellyfin-web
- url = https://github.com/jellyfin/jellyfin-web.git
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000000..b7a317000b
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,3 @@
+registry=https://registry.npmjs.org/
+@jellyfin:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/
+always-auth=true
\ No newline at end of file
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000000..59d9452fed
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,14 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+
+ // List of extensions which should be recommended for users of this workspace.
+ "recommendations": [
+ "ms-dotnettools.csharp",
+ "editorconfig.editorconfig"
+ ],
+ // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+ "unwantedRecommendations": [
+
+ ]
+}
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 21b8323ea0..b82956a721 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -1,19 +1,30 @@
{
- // Use IntelliSense to find out which attributes exist for C# debugging
- // Use hover for the description of the existing attributes
- // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
- "version": "0.2.0",
- "configurations": [
+ "version": "0.2.0",
+ "configurations": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
- // If you have changed target frameworks, make sure to update the program path.
- "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin.dll",
+ "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net6.0/jellyfin.dll",
"args": [],
"cwd": "${workspaceFolder}/Jellyfin.Server",
- // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
+ "console": "internalConsole",
+ "stopAtEntry": false,
+ "internalConsoleOptions": "openOnSessionStart",
+ "serverReadyAction": {
+ "action": "openExternally",
+ "pattern": "Overriding address\\(es\\) \\'(https?:\\S+)\\'",
+ }
+ },
+ {
+ "name": ".NET Core Launch (nowebclient)",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ "program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net6.0/jellyfin.dll",
+ "args": ["--nowebclient"],
+ "cwd": "${workspaceFolder}/Jellyfin.Server",
"console": "internalConsole",
"stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart"
@@ -24,5 +35,8 @@
"request": "attach",
"processId": "${command:pickProcess}"
}
- ,]
-}
\ No newline at end of file
+ ],
+ "env": {
+ "DOTNET_CLI_TELEMETRY_OPTOUT": "1"
+ }
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index ac517e10c6..09c523eb28 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -10,6 +10,21 @@
"${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj"
],
"problemMatcher": "$msCompile"
+ },
+ {
+ "label": "api tests",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "test",
+ "${workspaceFolder}/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj"
+ ],
+ "problemMatcher": "$msCompile"
}
- ]
-}
\ No newline at end of file
+ ],
+ "options": {
+ "env": {
+ "DOTNET_CLI_TELEMETRY_OPTOUT": "1"
+ }
+ }
+}
diff --git a/BDInfo/BDInfo.csproj b/BDInfo/BDInfo.csproj
deleted file mode 100644
index b2c752d0c8..0000000000
--- a/BDInfo/BDInfo.csproj
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- netstandard2.0
- false
-
-
-
diff --git a/BDInfo/BDInfoSettings.cs b/BDInfo/BDInfoSettings.cs
deleted file mode 100644
index f4cb300166..0000000000
--- a/BDInfo/BDInfoSettings.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-
-namespace BDInfo
-{
- class BDInfoSettings
- {
- public static bool GenerateStreamDiagnostics => true;
-
- public static bool EnableSSIF => true;
-
- public static bool AutosaveReport => false;
-
- public static bool GenerateFrameDataFile => false;
-
- public static bool FilterLoopingPlaylists => true;
-
- public static bool FilterShortPlaylists => false;
-
- public static int FilterShortPlaylistsValue => 0;
-
- public static bool UseImagePrefix => false;
-
- public static string UseImagePrefixValue => null;
-
- ///
- /// Setting this to false throws an IComparer error on some discs.
- ///
- public static bool KeepStreamOrder => true;
-
- public static bool GenerateTextSummary => false;
-
- public static string LastPath => string.Empty;
- }
-}
diff --git a/BDInfo/BDROM.cs b/BDInfo/BDROM.cs
deleted file mode 100644
index 6759ed55ad..0000000000
--- a/BDInfo/BDROM.cs
+++ /dev/null
@@ -1,456 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using MediaBrowser.Model.IO;
-
-namespace BDInfo
-{
- public class BDROM
- {
- public FileSystemMetadata DirectoryRoot = null;
- public FileSystemMetadata DirectoryBDMV = null;
- public FileSystemMetadata DirectoryBDJO = null;
- public FileSystemMetadata DirectoryCLIPINF = null;
- public FileSystemMetadata DirectoryPLAYLIST = null;
- public FileSystemMetadata DirectorySNP = null;
- public FileSystemMetadata DirectorySSIF = null;
- public FileSystemMetadata DirectorySTREAM = null;
-
- public string VolumeLabel = null;
- public ulong Size = 0;
- public bool IsBDPlus = false;
- public bool IsBDJava = false;
- public bool IsDBOX = false;
- public bool IsPSP = false;
- public bool Is3D = false;
- public bool Is50Hz = false;
-
- private readonly IFileSystem _fileSystem;
-
- public Dictionary PlaylistFiles =
- new Dictionary();
- public Dictionary StreamClipFiles =
- new Dictionary();
- public Dictionary StreamFiles =
- new Dictionary();
- public Dictionary InterleavedFiles =
- new Dictionary();
-
- public delegate bool OnStreamClipFileScanError(
- TSStreamClipFile streamClipFile, Exception ex);
-
- public event OnStreamClipFileScanError StreamClipFileScanError;
-
- public delegate bool OnStreamFileScanError(
- TSStreamFile streamClipFile, Exception ex);
-
- public event OnStreamFileScanError StreamFileScanError;
-
- public delegate bool OnPlaylistFileScanError(
- TSPlaylistFile playlistFile, Exception ex);
-
- public event OnPlaylistFileScanError PlaylistFileScanError;
-
- public BDROM(string path, IFileSystem fileSystem)
- {
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- _fileSystem = fileSystem;
- //
- // Locate BDMV directories.
- //
-
- DirectoryBDMV =
- GetDirectoryBDMV(path);
-
- if (DirectoryBDMV == null)
- {
- throw new Exception("Unable to locate BD structure.");
- }
-
- DirectoryRoot =
- _fileSystem.GetDirectoryInfo(Path.GetDirectoryName(DirectoryBDMV.FullName));
- DirectoryBDJO =
- GetDirectory("BDJO", DirectoryBDMV, 0);
- DirectoryCLIPINF =
- GetDirectory("CLIPINF", DirectoryBDMV, 0);
- DirectoryPLAYLIST =
- GetDirectory("PLAYLIST", DirectoryBDMV, 0);
- DirectorySNP =
- GetDirectory("SNP", DirectoryRoot, 0);
- DirectorySTREAM =
- GetDirectory("STREAM", DirectoryBDMV, 0);
- DirectorySSIF =
- GetDirectory("SSIF", DirectorySTREAM, 0);
-
- if (DirectoryCLIPINF == null
- || DirectoryPLAYLIST == null)
- {
- throw new Exception("Unable to locate BD structure.");
- }
-
- //
- // Initialize basic disc properties.
- //
-
- VolumeLabel = GetVolumeLabel(DirectoryRoot);
- Size = (ulong)GetDirectorySize(DirectoryRoot);
-
- if (null != GetDirectory("BDSVM", DirectoryRoot, 0))
- {
- IsBDPlus = true;
- }
- if (null != GetDirectory("SLYVM", DirectoryRoot, 0))
- {
- IsBDPlus = true;
- }
- if (null != GetDirectory("ANYVM", DirectoryRoot, 0))
- {
- IsBDPlus = true;
- }
-
- if (DirectoryBDJO != null &&
- _fileSystem.GetFilePaths(DirectoryBDJO.FullName).Any())
- {
- IsBDJava = true;
- }
-
- if (DirectorySNP != null &&
- GetFilePaths(DirectorySNP.FullName, ".mnv").Any())
- {
- IsPSP = true;
- }
-
- if (DirectorySSIF != null &&
- _fileSystem.GetFilePaths(DirectorySSIF.FullName).Any())
- {
- Is3D = true;
- }
-
- if (File.Exists(Path.Combine(DirectoryRoot.FullName, "FilmIndex.xml")))
- {
- IsDBOX = true;
- }
-
- //
- // Initialize file lists.
- //
-
- if (DirectoryPLAYLIST != null)
- {
- FileSystemMetadata[] files = GetFiles(DirectoryPLAYLIST.FullName, ".mpls").ToArray();
- foreach (var file in files)
- {
- PlaylistFiles.Add(
- file.Name.ToUpper(), new TSPlaylistFile(this, file));
- }
- }
-
- if (DirectorySTREAM != null)
- {
- FileSystemMetadata[] files = GetFiles(DirectorySTREAM.FullName, ".m2ts").ToArray();
- foreach (var file in files)
- {
- StreamFiles.Add(
- file.Name.ToUpper(), new TSStreamFile(file, _fileSystem));
- }
- }
-
- if (DirectoryCLIPINF != null)
- {
- FileSystemMetadata[] files = GetFiles(DirectoryCLIPINF.FullName, ".clpi").ToArray();
- foreach (var file in files)
- {
- StreamClipFiles.Add(
- file.Name.ToUpper(), new TSStreamClipFile(file));
- }
- }
-
- if (DirectorySSIF != null)
- {
- FileSystemMetadata[] files = GetFiles(DirectorySSIF.FullName, ".ssif").ToArray();
- foreach (var file in files)
- {
- InterleavedFiles.Add(
- file.Name.ToUpper(), new TSInterleavedFile(file));
- }
- }
- }
-
- private IEnumerable GetFiles(string path, string extension)
- {
- return _fileSystem.GetFiles(path, new[] { extension }, false, false);
- }
-
- private IEnumerable GetFilePaths(string path, string extension)
- {
- return _fileSystem.GetFilePaths(path, new[] { extension }, false, false);
- }
-
- public void Scan()
- {
- var errorStreamClipFiles = new List();
- foreach (var streamClipFile in StreamClipFiles.Values)
- {
- try
- {
- streamClipFile.Scan();
- }
- catch (Exception ex)
- {
- errorStreamClipFiles.Add(streamClipFile);
- if (StreamClipFileScanError != null)
- {
- if (StreamClipFileScanError(streamClipFile, ex))
- {
- continue;
- }
- else
- {
- break;
- }
- }
- else throw;
- }
- }
-
- foreach (var streamFile in StreamFiles.Values)
- {
- string ssifName = Path.GetFileNameWithoutExtension(streamFile.Name) + ".SSIF";
- if (InterleavedFiles.ContainsKey(ssifName))
- {
- streamFile.InterleavedFile = InterleavedFiles[ssifName];
- }
- }
-
- TSStreamFile[] streamFiles = new TSStreamFile[StreamFiles.Count];
- StreamFiles.Values.CopyTo(streamFiles, 0);
- Array.Sort(streamFiles, CompareStreamFiles);
-
- var errorPlaylistFiles = new List();
- foreach (var playlistFile in PlaylistFiles.Values)
- {
- try
- {
- playlistFile.Scan(StreamFiles, StreamClipFiles);
- }
- catch (Exception ex)
- {
- errorPlaylistFiles.Add(playlistFile);
- if (PlaylistFileScanError != null)
- {
- if (PlaylistFileScanError(playlistFile, ex))
- {
- continue;
- }
- else
- {
- break;
- }
- }
- else throw;
- }
- }
-
- var errorStreamFiles = new List();
- foreach (var streamFile in streamFiles)
- {
- try
- {
- var playlists = new List();
- foreach (var playlist in PlaylistFiles.Values)
- {
- foreach (var streamClip in playlist.StreamClips)
- {
- if (streamClip.Name == streamFile.Name)
- {
- playlists.Add(playlist);
- break;
- }
- }
- }
- streamFile.Scan(playlists, false);
- }
- catch (Exception ex)
- {
- errorStreamFiles.Add(streamFile);
- if (StreamFileScanError != null)
- {
- if (StreamFileScanError(streamFile, ex))
- {
- continue;
- }
- else
- {
- break;
- }
- }
- else throw;
- }
- }
-
- foreach (var playlistFile in PlaylistFiles.Values)
- {
- playlistFile.Initialize();
- if (!Is50Hz)
- {
- foreach (var videoStream in playlistFile.VideoStreams)
- {
- if (videoStream.FrameRate == TSFrameRate.FRAMERATE_25 ||
- videoStream.FrameRate == TSFrameRate.FRAMERATE_50)
- {
- Is50Hz = true;
- }
- }
- }
- }
- }
-
- private FileSystemMetadata GetDirectoryBDMV(
- string path)
- {
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
- FileSystemMetadata dir = _fileSystem.GetDirectoryInfo(path);
-
- while (dir != null)
- {
- if (string.Equals(dir.Name, "BDMV", StringComparison.OrdinalIgnoreCase))
- {
- return dir;
- }
- var parentFolder = Path.GetDirectoryName(dir.FullName);
- if (string.IsNullOrEmpty(parentFolder))
- {
- dir = null;
- }
- else
- {
- dir = _fileSystem.GetDirectoryInfo(parentFolder);
- }
- }
-
- return GetDirectory("BDMV", _fileSystem.GetDirectoryInfo(path), 0);
- }
-
- private FileSystemMetadata GetDirectory(
- string name,
- FileSystemMetadata dir,
- int searchDepth)
- {
- if (dir != null)
- {
- FileSystemMetadata[] children = _fileSystem.GetDirectories(dir.FullName).ToArray();
- foreach (var child in children)
- {
- if (string.Equals(child.Name, name, StringComparison.OrdinalIgnoreCase))
- {
- return child;
- }
- }
- if (searchDepth > 0)
- {
- foreach (var child in children)
- {
- GetDirectory(
- name, child, searchDepth - 1);
- }
- }
- }
- return null;
- }
-
- private long GetDirectorySize(FileSystemMetadata directoryInfo)
- {
- long size = 0;
-
- //if (!ExcludeDirs.Contains(directoryInfo.Name.ToUpper())) // TODO: Keep?
- {
- FileSystemMetadata[] pathFiles = _fileSystem.GetFiles(directoryInfo.FullName).ToArray();
- foreach (var pathFile in pathFiles)
- {
- if (pathFile.Extension.ToUpper() == ".SSIF")
- {
- continue;
- }
- size += pathFile.Length;
- }
-
- FileSystemMetadata[] pathChildren = _fileSystem.GetDirectories(directoryInfo.FullName).ToArray();
- foreach (var pathChild in pathChildren)
- {
- size += GetDirectorySize(pathChild);
- }
- }
-
- return size;
- }
-
- private string GetVolumeLabel(FileSystemMetadata dir)
- {
- return dir.Name;
- }
-
- public int CompareStreamFiles(
- TSStreamFile x,
- TSStreamFile y)
- {
- // TODO: Use interleaved file sizes
-
- if ((x == null || x.FileInfo == null) && (y == null || y.FileInfo == null))
- {
- return 0;
- }
- else if ((x == null || x.FileInfo == null) && (y != null && y.FileInfo != null))
- {
- return 1;
- }
- else if ((x != null || x.FileInfo != null) && (y == null || y.FileInfo == null))
- {
- return -1;
- }
- else
- {
- if (x.FileInfo.Length > y.FileInfo.Length)
- {
- return 1;
- }
- else if (y.FileInfo.Length > x.FileInfo.Length)
- {
- return -1;
- }
- else
- {
- return 0;
- }
- }
- }
-
- }
-}
diff --git a/BDInfo/LanguageCodes.cs b/BDInfo/LanguageCodes.cs
deleted file mode 100644
index ab2693ffbc..0000000000
--- a/BDInfo/LanguageCodes.cs
+++ /dev/null
@@ -1,493 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-
-namespace BDInfo
-{
- public abstract class LanguageCodes
- {
- public static string GetName(string code)
- {
- switch (code)
- {
- case "abk": return "Abkhazian";
- case "ace": return "Achinese";
- case "ach": return "Acoli";
- case "ada": return "Adangme";
- case "aar": return "Afar";
- case "afh": return "Afrihili";
- case "afr": return "Afrikaans";
- case "afa": return "Afro-Asiatic (Other)";
- case "aka": return "Akan";
- case "akk": return "Akkadian";
- case "alb": return "Albanian";
- case "sqi": return "Albanian";
- case "ale": return "Aleut";
- case "alg": return "Algonquian languages";
- case "tut": return "Altaic (Other)";
- case "amh": return "Amharic";
- case "apa": return "Apache languages";
- case "ara": return "Arabic";
- case "arc": return "Aramaic";
- case "arp": return "Arapaho";
- case "arn": return "Araucanian";
- case "arw": return "Arawak";
- case "arm": return "Armenian";
- case "hye": return "Armenian";
- case "art": return "Artificial (Other)";
- case "asm": return "Assamese";
- case "ath": return "Athapascan languages";
- case "aus": return "Australian languages";
- case "map": return "Austronesian (Other)";
- case "ava": return "Avaric";
- case "ave": return "Avestan";
- case "awa": return "Awadhi";
- case "aym": return "Aymara";
- case "aze": return "Azerbaijani";
- case "ban": return "Balinese";
- case "bat": return "Baltic (Other)";
- case "bal": return "Baluchi";
- case "bam": return "Bambara";
- case "bai": return "Bamileke languages";
- case "bad": return "Banda";
- case "bnt": return "Bantu (Other)";
- case "bas": return "Basa";
- case "bak": return "Bashkir";
- case "baq": return "Basque";
- case "eus": return "Basque";
- case "btk": return "Batak (Indonesia)";
- case "bej": return "Beja";
- case "bel": return "Belarusian";
- case "bem": return "Bemba";
- case "ben": return "Bengali";
- case "ber": return "Berber (Other)";
- case "bho": return "Bhojpuri";
- case "bih": return "Bihari";
- case "bik": return "Bikol";
- case "bin": return "Bini";
- case "bis": return "Bislama";
- case "bos": return "Bosnian";
- case "bra": return "Braj";
- case "bre": return "Breton";
- case "bug": return "Buginese";
- case "bul": return "Bulgarian";
- case "bua": return "Buriat";
- case "bur": return "Burmese";
- case "mya": return "Burmese";
- case "cad": return "Caddo";
- case "car": return "Carib";
- case "cat": return "Catalan";
- case "cau": return "Caucasian (Other)";
- case "ceb": return "Cebuano";
- case "cel": return "Celtic (Other)";
- case "cai": return "Central American Indian (Other)";
- case "chg": return "Chagatai";
- case "cmc": return "Chamic languages";
- case "cha": return "Chamorro";
- case "che": return "Chechen";
- case "chr": return "Cherokee";
- case "chy": return "Cheyenne";
- case "chb": return "Chibcha";
- case "chi": return "Chinese";
- case "zho": return "Chinese";
- case "chn": return "Chinook jargon";
- case "chp": return "Chipewyan";
- case "cho": return "Choctaw";
- case "chu": return "Church Slavic";
- case "chk": return "Chuukese";
- case "chv": return "Chuvash";
- case "cop": return "Coptic";
- case "cor": return "Cornish";
- case "cos": return "Corsican";
- case "cre": return "Cree";
- case "mus": return "Creek";
- case "crp": return "Creoles and pidgins (Other)";
- case "cpe": return "Creoles and pidgins,";
- case "cpf": return "Creoles and pidgins,";
- case "cpp": return "Creoles and pidgins,";
- case "scr": return "Croatian";
- case "hrv": return "Croatian";
- case "cus": return "Cushitic (Other)";
- case "cze": return "Czech";
- case "ces": return "Czech";
- case "dak": return "Dakota";
- case "dan": return "Danish";
- case "day": return "Dayak";
- case "del": return "Delaware";
- case "din": return "Dinka";
- case "div": return "Divehi";
- case "doi": return "Dogri";
- case "dgr": return "Dogrib";
- case "dra": return "Dravidian (Other)";
- case "dua": return "Duala";
- case "dut": return "Dutch";
- case "nld": return "Dutch";
- case "dum": return "Dutch, Middle (ca. 1050-1350)";
- case "dyu": return "Dyula";
- case "dzo": return "Dzongkha";
- case "efi": return "Efik";
- case "egy": return "Egyptian (Ancient)";
- case "eka": return "Ekajuk";
- case "elx": return "Elamite";
- case "eng": return "English";
- case "enm": return "English, Middle (1100-1500)";
- case "ang": return "English, Old (ca.450-1100)";
- case "epo": return "Esperanto";
- case "est": return "Estonian";
- case "ewe": return "Ewe";
- case "ewo": return "Ewondo";
- case "fan": return "Fang";
- case "fat": return "Fanti";
- case "fao": return "Faroese";
- case "fij": return "Fijian";
- case "fin": return "Finnish";
- case "fiu": return "Finno-Ugrian (Other)";
- case "fon": return "Fon";
- case "fre": return "French";
- case "fra": return "French";
- case "frm": return "French, Middle (ca.1400-1600)";
- case "fro": return "French, Old (842-ca.1400)";
- case "fry": return "Frisian";
- case "fur": return "Friulian";
- case "ful": return "Fulah";
- case "gaa": return "Ga";
- case "glg": return "Gallegan";
- case "lug": return "Ganda";
- case "gay": return "Gayo";
- case "gba": return "Gbaya";
- case "gez": return "Geez";
- case "geo": return "Georgian";
- case "kat": return "Georgian";
- case "ger": return "German";
- case "deu": return "German";
- case "nds": return "Saxon";
- case "gmh": return "German, Middle High (ca.1050-1500)";
- case "goh": return "German, Old High (ca.750-1050)";
- case "gem": return "Germanic (Other)";
- case "gil": return "Gilbertese";
- case "gon": return "Gondi";
- case "gor": return "Gorontalo";
- case "got": return "Gothic";
- case "grb": return "Grebo";
- case "grc": return "Greek, Ancient (to 1453)";
- case "gre": return "Greek";
- case "ell": return "Greek";
- case "grn": return "Guarani";
- case "guj": return "Gujarati";
- case "gwi": return "Gwich´in";
- case "hai": return "Haida";
- case "hau": return "Hausa";
- case "haw": return "Hawaiian";
- case "heb": return "Hebrew";
- case "her": return "Herero";
- case "hil": return "Hiligaynon";
- case "him": return "Himachali";
- case "hin": return "Hindi";
- case "hmo": return "Hiri Motu";
- case "hit": return "Hittite";
- case "hmn": return "Hmong";
- case "hun": return "Hungarian";
- case "hup": return "Hupa";
- case "iba": return "Iban";
- case "ice": return "Icelandic";
- case "isl": return "Icelandic";
- case "ibo": return "Igbo";
- case "ijo": return "Ijo";
- case "ilo": return "Iloko";
- case "inc": return "Indic (Other)";
- case "ine": return "Indo-European (Other)";
- case "ind": return "Indonesian";
- case "ina": return "Interlingua (International";
- case "ile": return "Interlingue";
- case "iku": return "Inuktitut";
- case "ipk": return "Inupiaq";
- case "ira": return "Iranian (Other)";
- case "gle": return "Irish";
- case "mga": return "Irish, Middle (900-1200)";
- case "sga": return "Irish, Old (to 900)";
- case "iro": return "Iroquoian languages";
- case "ita": return "Italian";
- case "jpn": return "Japanese";
- case "jav": return "Javanese";
- case "jrb": return "Judeo-Arabic";
- case "jpr": return "Judeo-Persian";
- case "kab": return "Kabyle";
- case "kac": return "Kachin";
- case "kal": return "Kalaallisut";
- case "kam": return "Kamba";
- case "kan": return "Kannada";
- case "kau": return "Kanuri";
- case "kaa": return "Kara-Kalpak";
- case "kar": return "Karen";
- case "kas": return "Kashmiri";
- case "kaw": return "Kawi";
- case "kaz": return "Kazakh";
- case "kha": return "Khasi";
- case "khm": return "Khmer";
- case "khi": return "Khoisan (Other)";
- case "kho": return "Khotanese";
- case "kik": return "Kikuyu";
- case "kmb": return "Kimbundu";
- case "kin": return "Kinyarwanda";
- case "kir": return "Kirghiz";
- case "kom": return "Komi";
- case "kon": return "Kongo";
- case "kok": return "Konkani";
- case "kor": return "Korean";
- case "kos": return "Kosraean";
- case "kpe": return "Kpelle";
- case "kro": return "Kru";
- case "kua": return "Kuanyama";
- case "kum": return "Kumyk";
- case "kur": return "Kurdish";
- case "kru": return "Kurukh";
- case "kut": return "Kutenai";
- case "lad": return "Ladino";
- case "lah": return "Lahnda";
- case "lam": return "Lamba";
- case "lao": return "Lao";
- case "lat": return "Latin";
- case "lav": return "Latvian";
- case "ltz": return "Letzeburgesch";
- case "lez": return "Lezghian";
- case "lin": return "Lingala";
- case "lit": return "Lithuanian";
- case "loz": return "Lozi";
- case "lub": return "Luba-Katanga";
- case "lua": return "Luba-Lulua";
- case "lui": return "Luiseno";
- case "lun": return "Lunda";
- case "luo": return "Luo (Kenya and Tanzania)";
- case "lus": return "Lushai";
- case "mac": return "Macedonian";
- case "mkd": return "Macedonian";
- case "mad": return "Madurese";
- case "mag": return "Magahi";
- case "mai": return "Maithili";
- case "mak": return "Makasar";
- case "mlg": return "Malagasy";
- case "may": return "Malay";
- case "msa": return "Malay";
- case "mal": return "Malayalam";
- case "mlt": return "Maltese";
- case "mnc": return "Manchu";
- case "mdr": return "Mandar";
- case "man": return "Mandingo";
- case "mni": return "Manipuri";
- case "mno": return "Manobo languages";
- case "glv": return "Manx";
- case "mao": return "Maori";
- case "mri": return "Maori";
- case "mar": return "Marathi";
- case "chm": return "Mari";
- case "mah": return "Marshall";
- case "mwr": return "Marwari";
- case "mas": return "Masai";
- case "myn": return "Mayan languages";
- case "men": return "Mende";
- case "mic": return "Micmac";
- case "min": return "Minangkabau";
- case "mis": return "Miscellaneous languages";
- case "moh": return "Mohawk";
- case "mol": return "Moldavian";
- case "mkh": return "Mon-Khmer (Other)";
- case "lol": return "Mongo";
- case "mon": return "Mongolian";
- case "mos": return "Mossi";
- case "mul": return "Multiple languages";
- case "mun": return "Munda languages";
- case "nah": return "Nahuatl";
- case "nau": return "Nauru";
- case "nav": return "Navajo";
- case "nde": return "Ndebele, North";
- case "nbl": return "Ndebele, South";
- case "ndo": return "Ndonga";
- case "nep": return "Nepali";
- case "new": return "Newari";
- case "nia": return "Nias";
- case "nic": return "Niger-Kordofanian (Other)";
- case "ssa": return "Nilo-Saharan (Other)";
- case "niu": return "Niuean";
- case "non": return "Norse, Old";
- case "nai": return "North American Indian (Other)";
- case "sme": return "Northern Sami";
- case "nor": return "Norwegian";
- case "nob": return "Norwegian Bokmål";
- case "nno": return "Norwegian Nynorsk";
- case "nub": return "Nubian languages";
- case "nym": return "Nyamwezi";
- case "nya": return "Nyanja";
- case "nyn": return "Nyankole";
- case "nyo": return "Nyoro";
- case "nzi": return "Nzima";
- case "oci": return "Occitan";
- case "oji": return "Ojibwa";
- case "ori": return "Oriya";
- case "orm": return "Oromo";
- case "osa": return "Osage";
- case "oss": return "Ossetian";
- case "oto": return "Otomian languages";
- case "pal": return "Pahlavi";
- case "pau": return "Palauan";
- case "pli": return "Pali";
- case "pam": return "Pampanga";
- case "pag": return "Pangasinan";
- case "pan": return "Panjabi";
- case "pap": return "Papiamento";
- case "paa": return "Papuan (Other)";
- case "per": return "Persian";
- case "fas": return "Persian";
- case "peo": return "Persian, Old (ca.600-400 B.C.)";
- case "phi": return "Philippine (Other)";
- case "phn": return "Phoenician";
- case "pon": return "Pohnpeian";
- case "pol": return "Polish";
- case "por": return "Portuguese";
- case "pra": return "Prakrit languages";
- case "pro": return "Provençal";
- case "pus": return "Pushto";
- case "que": return "Quechua";
- case "roh": return "Raeto-Romance";
- case "raj": return "Rajasthani";
- case "rap": return "Rapanui";
- case "rar": return "Rarotongan";
- case "roa": return "Romance (Other)";
- case "rum": return "Romanian";
- case "ron": return "Romanian";
- case "rom": return "Romany";
- case "run": return "Rundi";
- case "rus": return "Russian";
- case "sal": return "Salishan languages";
- case "sam": return "Samaritan Aramaic";
- case "smi": return "Sami languages (Other)";
- case "smo": return "Samoan";
- case "sad": return "Sandawe";
- case "sag": return "Sango";
- case "san": return "Sanskrit";
- case "sat": return "Santali";
- case "srd": return "Sardinian";
- case "sas": return "Sasak";
- case "sco": return "Scots";
- case "gla": return "Gaelic";
- case "sel": return "Selkup";
- case "sem": return "Semitic (Other)";
- case "scc": return "Serbian";
- case "srp": return "Serbian";
- case "srr": return "Serer";
- case "shn": return "Shan";
- case "sna": return "Shona";
- case "sid": return "Sidamo";
- case "sgn": return "Sign languages";
- case "bla": return "Siksika";
- case "snd": return "Sindhi";
- case "sin": return "Sinhalese";
- case "sit": return "Sino-Tibetan (Other)";
- case "sio": return "Siouan languages";
- case "den": return "Slave (Athapascan)";
- case "sla": return "Slavic (Other)";
- case "slo": return "Slovak";
- case "slk": return "Slovak";
- case "slv": return "Slovenian";
- case "sog": return "Sogdian";
- case "som": return "Somali";
- case "son": return "Songhai";
- case "snk": return "Soninke";
- case "wen": return "Sorbian languages";
- case "nso": return "Sotho, Northern";
- case "sot": return "Sotho, Southern";
- case "sai": return "South American Indian (Other)";
- case "spa": return "Spanish";
- case "suk": return "Sukuma";
- case "sux": return "Sumerian";
- case "sun": return "Sundanese";
- case "sus": return "Susu";
- case "swa": return "Swahili";
- case "ssw": return "Swati";
- case "swe": return "Swedish";
- case "syr": return "Syriac";
- case "tgl": return "Tagalog";
- case "tah": return "Tahitian";
- case "tai": return "Tai (Other)";
- case "tgk": return "Tajik";
- case "tmh": return "Tamashek";
- case "tam": return "Tamil";
- case "tat": return "Tatar";
- case "tel": return "Telugu";
- case "ter": return "Tereno";
- case "tet": return "Tetum";
- case "tha": return "Thai";
- case "tib": return "Tibetan";
- case "bod": return "Tibetan";
- case "tig": return "Tigre";
- case "tir": return "Tigrinya";
- case "tem": return "Timne";
- case "tiv": return "Tiv";
- case "tli": return "Tlingit";
- case "tpi": return "Tok Pisin";
- case "tkl": return "Tokelau";
- case "tog": return "Tonga (Nyasa)";
- case "ton": return "Tonga (Tonga Islands)";
- case "tsi": return "Tsimshian";
- case "tso": return "Tsonga";
- case "tsn": return "Tswana";
- case "tum": return "Tumbuka";
- case "tur": return "Turkish";
- case "ota": return "Turkish, Ottoman (1500-1928)";
- case "tuk": return "Turkmen";
- case "tvl": return "Tuvalu";
- case "tyv": return "Tuvinian";
- case "twi": return "Twi";
- case "uga": return "Ugaritic";
- case "uig": return "Uighur";
- case "ukr": return "Ukrainian";
- case "umb": return "Umbundu";
- case "und": return "Undetermined";
- case "urd": return "Urdu";
- case "uzb": return "Uzbek";
- case "vai": return "Vai";
- case "ven": return "Venda";
- case "vie": return "Vietnamese";
- case "vol": return "Volapük";
- case "vot": return "Votic";
- case "wak": return "Wakashan languages";
- case "wal": return "Walamo";
- case "war": return "Waray";
- case "was": return "Washo";
- case "wel": return "Welsh";
- case "cym": return "Welsh";
- case "wol": return "Wolof";
- case "xho": return "Xhosa";
- case "sah": return "Yakut";
- case "yao": return "Yao";
- case "yap": return "Yapese";
- case "yid": return "Yiddish";
- case "yor": return "Yoruba";
- case "ypk": return "Yupik languages";
- case "znd": return "Zande";
- case "zap": return "Zapotec";
- case "zen": return "Zenaga";
- case "zha": return "Zhuang";
- case "zul": return "Zulu";
- case "zun": return "Zuni";
-
- default: return code;
- }
- }
- }
-}
diff --git a/BDInfo/Properties/AssemblyInfo.cs b/BDInfo/Properties/AssemblyInfo.cs
deleted file mode 100644
index 788cf73666..0000000000
--- a/BDInfo/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using System.Reflection;
-using System.Resources;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("BDInfo")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("Jellyfin Project")]
-[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
-[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: NeutralResourcesLanguage("en")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
diff --git a/BDInfo/ReadMe.txt b/BDInfo/ReadMe.txt
deleted file mode 100644
index e70b0b66cd..0000000000
--- a/BDInfo/ReadMe.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-The source is taken from the BDRom folder of this project:
-
-http://www.cinemasquid.com/blu-ray/tools/bdinfo
-
-BDInfoSettings was taken from the FormSettings class, and changed so that the settings all return defaults.
diff --git a/BDInfo/TSCodecAC3.cs b/BDInfo/TSCodecAC3.cs
deleted file mode 100644
index 35d306a19d..0000000000
--- a/BDInfo/TSCodecAC3.cs
+++ /dev/null
@@ -1,309 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-#undef DEBUG
-using System.IO;
-
-namespace BDInfo
-{
- public abstract class TSCodecAC3
- {
- private static byte[] eac3_blocks = new byte[] { 1, 2, 3, 6 };
-
- public static void Scan(
- TSAudioStream stream,
- TSStreamBuffer buffer,
- ref string tag)
- {
- if (stream.IsInitialized) return;
-
- byte[] sync = buffer.ReadBytes(2);
- if (sync == null ||
- sync[0] != 0x0B ||
- sync[1] != 0x77)
- {
- return;
- }
-
- int sr_code = 0;
- int frame_size = 0;
- int frame_size_code = 0;
- int channel_mode = 0;
- int lfe_on = 0;
- int dial_norm = 0;
- int num_blocks = 0;
-
- byte[] hdr = buffer.ReadBytes(4);
- int bsid = (hdr[3] & 0xF8) >> 3;
- buffer.Seek(-4, SeekOrigin.Current);
- if (bsid <= 10)
- {
- byte[] crc = buffer.ReadBytes(2);
- sr_code = buffer.ReadBits(2);
- frame_size_code = buffer.ReadBits(6);
- bsid = buffer.ReadBits(5);
- int bsmod = buffer.ReadBits(3);
-
- channel_mode = buffer.ReadBits(3);
- int cmixlev = 0;
- if (((channel_mode & 0x1) > 0) && (channel_mode != 0x1))
- {
- cmixlev = buffer.ReadBits(2);
- }
- int surmixlev = 0;
- if ((channel_mode & 0x4) > 0)
- {
- surmixlev = buffer.ReadBits(2);
- }
- int dsurmod = 0;
- if (channel_mode == 0x2)
- {
- dsurmod = buffer.ReadBits(2);
- if (dsurmod == 0x2)
- {
- stream.AudioMode = TSAudioMode.Surround;
- }
- }
- lfe_on = buffer.ReadBits(1);
- dial_norm = buffer.ReadBits(5);
- int compr = 0;
- if (1 == buffer.ReadBits(1))
- {
- compr = buffer.ReadBits(8);
- }
- int langcod = 0;
- if (1 == buffer.ReadBits(1))
- {
- langcod = buffer.ReadBits(8);
- }
- int mixlevel = 0;
- int roomtyp = 0;
- if (1 == buffer.ReadBits(1))
- {
- mixlevel = buffer.ReadBits(5);
- roomtyp = buffer.ReadBits(2);
- }
- if (channel_mode == 0)
- {
- int dialnorm2 = buffer.ReadBits(5);
- int compr2 = 0;
- if (1 == buffer.ReadBits(1))
- {
- compr2 = buffer.ReadBits(8);
- }
- int langcod2 = 0;
- if (1 == buffer.ReadBits(1))
- {
- langcod2 = buffer.ReadBits(8);
- }
- int mixlevel2 = 0;
- int roomtyp2 = 0;
- if (1 == buffer.ReadBits(1))
- {
- mixlevel2 = buffer.ReadBits(5);
- roomtyp2 = buffer.ReadBits(2);
- }
- }
- int copyrightb = buffer.ReadBits(1);
- int origbs = buffer.ReadBits(1);
- if (bsid == 6)
- {
- if (1 == buffer.ReadBits(1))
- {
- int dmixmod = buffer.ReadBits(2);
- int ltrtcmixlev = buffer.ReadBits(3);
- int ltrtsurmixlev = buffer.ReadBits(3);
- int lorocmixlev = buffer.ReadBits(3);
- int lorosurmixlev = buffer.ReadBits(3);
- }
- if (1 == buffer.ReadBits(1))
- {
- int dsurexmod = buffer.ReadBits(2);
- int dheadphonmod = buffer.ReadBits(2);
- if (dheadphonmod == 0x2)
- {
- // TODO
- }
- int adconvtyp = buffer.ReadBits(1);
- int xbsi2 = buffer.ReadBits(8);
- int encinfo = buffer.ReadBits(1);
- if (dsurexmod == 2)
- {
- stream.AudioMode = TSAudioMode.Extended;
- }
- }
- }
- }
- else
- {
- int frame_type = buffer.ReadBits(2);
- int substreamid = buffer.ReadBits(3);
- frame_size = (buffer.ReadBits(11) + 1) << 1;
-
- sr_code = buffer.ReadBits(2);
- if (sr_code == 3)
- {
- sr_code = buffer.ReadBits(2);
- }
- else
- {
- num_blocks = buffer.ReadBits(2);
- }
- channel_mode = buffer.ReadBits(3);
- lfe_on = buffer.ReadBits(1);
- }
-
- switch (channel_mode)
- {
- case 0: // 1+1
- stream.ChannelCount = 2;
- if (stream.AudioMode == TSAudioMode.Unknown)
- {
- stream.AudioMode = TSAudioMode.DualMono;
- }
- break;
- case 1: // 1/0
- stream.ChannelCount = 1;
- break;
- case 2: // 2/0
- stream.ChannelCount = 2;
- if (stream.AudioMode == TSAudioMode.Unknown)
- {
- stream.AudioMode = TSAudioMode.Stereo;
- }
- break;
- case 3: // 3/0
- stream.ChannelCount = 3;
- break;
- case 4: // 2/1
- stream.ChannelCount = 3;
- break;
- case 5: // 3/1
- stream.ChannelCount = 4;
- break;
- case 6: // 2/2
- stream.ChannelCount = 4;
- break;
- case 7: // 3/2
- stream.ChannelCount = 5;
- break;
- default:
- stream.ChannelCount = 0;
- break;
- }
-
- switch (sr_code)
- {
- case 0:
- stream.SampleRate = 48000;
- break;
- case 1:
- stream.SampleRate = 44100;
- break;
- case 2:
- stream.SampleRate = 32000;
- break;
- default:
- stream.SampleRate = 0;
- break;
- }
-
- if (bsid <= 10)
- {
- switch (frame_size_code >> 1)
- {
- case 18:
- stream.BitRate = 640000;
- break;
- case 17:
- stream.BitRate = 576000;
- break;
- case 16:
- stream.BitRate = 512000;
- break;
- case 15:
- stream.BitRate = 448000;
- break;
- case 14:
- stream.BitRate = 384000;
- break;
- case 13:
- stream.BitRate = 320000;
- break;
- case 12:
- stream.BitRate = 256000;
- break;
- case 11:
- stream.BitRate = 224000;
- break;
- case 10:
- stream.BitRate = 192000;
- break;
- case 9:
- stream.BitRate = 160000;
- break;
- case 8:
- stream.BitRate = 128000;
- break;
- case 7:
- stream.BitRate = 112000;
- break;
- case 6:
- stream.BitRate = 96000;
- break;
- case 5:
- stream.BitRate = 80000;
- break;
- case 4:
- stream.BitRate = 64000;
- break;
- case 3:
- stream.BitRate = 56000;
- break;
- case 2:
- stream.BitRate = 48000;
- break;
- case 1:
- stream.BitRate = 40000;
- break;
- case 0:
- stream.BitRate = 32000;
- break;
- default:
- stream.BitRate = 0;
- break;
- }
- }
- else
- {
- stream.BitRate = (long)
- (4.0 * frame_size * stream.SampleRate / (num_blocks * 256));
- }
-
- stream.LFE = lfe_on;
- if (stream.StreamType != TSStreamType.AC3_PLUS_AUDIO &&
- stream.StreamType != TSStreamType.AC3_PLUS_SECONDARY_AUDIO)
- {
- stream.DialNorm = dial_norm - 31;
- }
- stream.IsVBR = false;
- stream.IsInitialized = true;
- }
- }
-}
diff --git a/BDInfo/TSCodecAVC.cs b/BDInfo/TSCodecAVC.cs
deleted file mode 100644
index 5833d169f4..0000000000
--- a/BDInfo/TSCodecAVC.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-
-namespace BDInfo
-{
- public abstract class TSCodecAVC
- {
- public static void Scan(
- TSVideoStream stream,
- TSStreamBuffer buffer,
- ref string tag)
- {
- uint parse = 0;
- byte accessUnitDelimiterParse = 0;
- byte sequenceParameterSetParse = 0;
- string profile = null;
- string level = null;
- byte constraintSet0Flag = 0;
- byte constraintSet1Flag = 0;
- byte constraintSet2Flag = 0;
- byte constraintSet3Flag = 0;
-
- for (int i = 0; i < buffer.Length; i++)
- {
- parse = (parse << 8) + buffer.ReadByte();
-
- if (parse == 0x00000109)
- {
- accessUnitDelimiterParse = 1;
- }
- else if (accessUnitDelimiterParse > 0)
- {
- --accessUnitDelimiterParse;
- if (accessUnitDelimiterParse == 0)
- {
- switch ((parse & 0xFF) >> 5)
- {
- case 0: // I
- case 3: // SI
- case 5: // I, SI
- tag = "I";
- break;
-
- case 1: // I, P
- case 4: // SI, SP
- case 6: // I, SI, P, SP
- tag = "P";
- break;
-
- case 2: // I, P, B
- case 7: // I, SI, P, SP, B
- tag = "B";
- break;
- }
- if (stream.IsInitialized) return;
- }
- }
- else if (parse == 0x00000127 || parse == 0x00000167)
- {
- sequenceParameterSetParse = 3;
- }
- else if (sequenceParameterSetParse > 0)
- {
- --sequenceParameterSetParse;
- switch (sequenceParameterSetParse)
- {
- case 2:
- switch (parse & 0xFF)
- {
- case 66:
- profile = "Baseline Profile";
- break;
- case 77:
- profile = "Main Profile";
- break;
- case 88:
- profile = "Extended Profile";
- break;
- case 100:
- profile = "High Profile";
- break;
- case 110:
- profile = "High 10 Profile";
- break;
- case 122:
- profile = "High 4:2:2 Profile";
- break;
- case 144:
- profile = "High 4:4:4 Profile";
- break;
- default:
- profile = "Unknown Profile";
- break;
- }
- break;
-
- case 1:
- constraintSet0Flag = (byte)
- ((parse & 0x80) >> 7);
- constraintSet1Flag = (byte)
- ((parse & 0x40) >> 6);
- constraintSet2Flag = (byte)
- ((parse & 0x20) >> 5);
- constraintSet3Flag = (byte)
- ((parse & 0x10) >> 4);
- break;
-
- case 0:
- byte b = (byte)(parse & 0xFF);
- if (b == 11 && constraintSet3Flag == 1)
- {
- level = "1b";
- }
- else
- {
- level = string.Format(
- "{0:D}.{1:D}",
- b / 10, (b - ((b / 10) * 10)));
- }
- stream.EncodingProfile = string.Format(
- "{0} {1}", profile, level);
- stream.IsVBR = true;
- stream.IsInitialized = true;
- break;
- }
- }
- }
- return;
- }
- }
-}
diff --git a/BDInfo/TSCodecDTS.cs b/BDInfo/TSCodecDTS.cs
deleted file mode 100644
index ff94cb7022..0000000000
--- a/BDInfo/TSCodecDTS.cs
+++ /dev/null
@@ -1,159 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-
-namespace BDInfo
-{
- public abstract class TSCodecDTS
- {
- private static int[] dca_sample_rates =
- {
- 0, 8000, 16000, 32000, 0, 0, 11025, 22050, 44100, 0, 0,
- 12000, 24000, 48000, 96000, 192000
- };
-
- private static int[] dca_bit_rates =
- {
- 32000, 56000, 64000, 96000, 112000, 128000,
- 192000, 224000, 256000, 320000, 384000,
- 448000, 512000, 576000, 640000, 768000,
- 896000, 1024000, 1152000, 1280000, 1344000,
- 1408000, 1411200, 1472000, 1509000, 1920000,
- 2048000, 3072000, 3840000, 1/*open*/, 2/*variable*/, 3/*lossless*/
- };
-
- private static int[] dca_channels =
- {
- 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8
- };
-
- private static int[] dca_bits_per_sample =
- {
- 16, 16, 20, 20, 0, 24, 24
- };
-
- public static void Scan(
- TSAudioStream stream,
- TSStreamBuffer buffer,
- long bitrate,
- ref string tag)
- {
- if (stream.IsInitialized) return;
-
- bool syncFound = false;
- uint sync = 0;
- for (int i = 0; i < buffer.Length; i++)
- {
- sync = (sync << 8) + buffer.ReadByte();
- if (sync == 0x7FFE8001)
- {
- syncFound = true;
- break;
- }
- }
- if (!syncFound) return;
-
- int frame_type = buffer.ReadBits(1);
- int samples_deficit = buffer.ReadBits(5);
- int crc_present = buffer.ReadBits(1);
- int sample_blocks = buffer.ReadBits(7);
- int frame_size = buffer.ReadBits(14);
- if (frame_size < 95)
- {
- return;
- }
- int amode = buffer.ReadBits(6);
- int sample_rate = buffer.ReadBits(4);
- if (sample_rate < 0 || sample_rate >= dca_sample_rates.Length)
- {
- return;
- }
- int bit_rate = buffer.ReadBits(5);
- if (bit_rate < 0 || bit_rate >= dca_bit_rates.Length)
- {
- return;
- }
- int downmix = buffer.ReadBits(1);
- int dynrange = buffer.ReadBits(1);
- int timestamp = buffer.ReadBits(1);
- int aux_data = buffer.ReadBits(1);
- int hdcd = buffer.ReadBits(1);
- int ext_descr = buffer.ReadBits(3);
- int ext_coding = buffer.ReadBits(1);
- int aspf = buffer.ReadBits(1);
- int lfe = buffer.ReadBits(2);
- int predictor_history = buffer.ReadBits(1);
- if (crc_present == 1)
- {
- int crc = buffer.ReadBits(16);
- }
- int multirate_inter = buffer.ReadBits(1);
- int version = buffer.ReadBits(4);
- int copy_history = buffer.ReadBits(2);
- int source_pcm_res = buffer.ReadBits(3);
- int front_sum = buffer.ReadBits(1);
- int surround_sum = buffer.ReadBits(1);
- int dialog_norm = buffer.ReadBits(4);
- if (source_pcm_res < 0 || source_pcm_res >= dca_bits_per_sample.Length)
- {
- return;
- }
- int subframes = buffer.ReadBits(4);
- int total_channels = buffer.ReadBits(3) + 1 + ext_coding;
-
- stream.SampleRate = dca_sample_rates[sample_rate];
- stream.ChannelCount = total_channels;
- stream.LFE = (lfe > 0 ? 1 : 0);
- stream.BitDepth = dca_bits_per_sample[source_pcm_res];
- stream.DialNorm = -dialog_norm;
- if ((source_pcm_res & 0x1) == 0x1)
- {
- stream.AudioMode = TSAudioMode.Extended;
- }
-
- stream.BitRate = (uint)dca_bit_rates[bit_rate];
- switch (stream.BitRate)
- {
- case 1:
- if (bitrate > 0)
- {
- stream.BitRate = bitrate;
- stream.IsVBR = false;
- stream.IsInitialized = true;
- }
- else
- {
- stream.BitRate = 0;
- }
- break;
-
- case 2:
- case 3:
- stream.IsVBR = true;
- stream.IsInitialized = true;
- break;
-
- default:
- stream.IsVBR = false;
- stream.IsInitialized = true;
- break;
- }
- }
- }
-}
diff --git a/BDInfo/TSCodecDTSHD.cs b/BDInfo/TSCodecDTSHD.cs
deleted file mode 100644
index 57a136d2d5..0000000000
--- a/BDInfo/TSCodecDTSHD.cs
+++ /dev/null
@@ -1,246 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-
-namespace BDInfo
-{
- public abstract class TSCodecDTSHD
- {
- private static int[] SampleRates = new int[]
- { 0x1F40, 0x3E80, 0x7D00, 0x0FA00, 0x1F400, 0x5622, 0x0AC44, 0x15888, 0x2B110, 0x56220, 0x2EE0, 0x5DC0, 0x0BB80, 0x17700, 0x2EE00, 0x5DC00 };
-
- public static void Scan(
- TSAudioStream stream,
- TSStreamBuffer buffer,
- long bitrate,
- ref string tag)
- {
- if (stream.IsInitialized &&
- (stream.StreamType == TSStreamType.DTS_HD_SECONDARY_AUDIO ||
- (stream.CoreStream != null &&
- stream.CoreStream.IsInitialized))) return;
-
- bool syncFound = false;
- uint sync = 0;
- for (int i = 0; i < buffer.Length; i++)
- {
- sync = (sync << 8) + buffer.ReadByte();
- if (sync == 0x64582025)
- {
- syncFound = true;
- break;
- }
- }
-
- if (!syncFound)
- {
- tag = "CORE";
- if (stream.CoreStream == null)
- {
- stream.CoreStream = new TSAudioStream();
- stream.CoreStream.StreamType = TSStreamType.DTS_AUDIO;
- }
- if (!stream.CoreStream.IsInitialized)
- {
- buffer.BeginRead();
- TSCodecDTS.Scan(stream.CoreStream, buffer, bitrate, ref tag);
- }
- return;
- }
-
- tag = "HD";
- int temp1 = buffer.ReadBits(8);
- int nuSubStreamIndex = buffer.ReadBits(2);
- int nuExtSSHeaderSize = 0;
- int nuExtSSFSize = 0;
- int bBlownUpHeader = buffer.ReadBits(1);
- if (1 == bBlownUpHeader)
- {
- nuExtSSHeaderSize = buffer.ReadBits(12) + 1;
- nuExtSSFSize = buffer.ReadBits(20) + 1;
- }
- else
- {
- nuExtSSHeaderSize = buffer.ReadBits(8) + 1;
- nuExtSSFSize = buffer.ReadBits(16) + 1;
- }
- int nuNumAudioPresent = 1;
- int nuNumAssets = 1;
- int bStaticFieldsPresent = buffer.ReadBits(1);
- if (1 == bStaticFieldsPresent)
- {
- int nuRefClockCode = buffer.ReadBits(2);
- int nuExSSFrameDurationCode = buffer.ReadBits(3) + 1;
- long nuTimeStamp = 0;
- if (1 == buffer.ReadBits(1))
- {
- nuTimeStamp = (buffer.ReadBits(18) << 18) + buffer.ReadBits(18);
- }
- nuNumAudioPresent = buffer.ReadBits(3) + 1;
- nuNumAssets = buffer.ReadBits(3) + 1;
- int[] nuActiveExSSMask = new int[nuNumAudioPresent];
- for (int i = 0; i < nuNumAudioPresent; i++)
- {
- nuActiveExSSMask[i] = buffer.ReadBits(nuSubStreamIndex + 1); //?
- }
- for (int i = 0; i < nuNumAudioPresent; i++)
- {
- for (int j = 0; j < nuSubStreamIndex + 1; j++)
- {
- if (((j + 1) % 2) == 1)
- {
- int mask = buffer.ReadBits(8);
- }
- }
- }
- if (1 == buffer.ReadBits(1))
- {
- int nuMixMetadataAdjLevel = buffer.ReadBits(2);
- int nuBits4MixOutMask = buffer.ReadBits(2) * 4 + 4;
- int nuNumMixOutConfigs = buffer.ReadBits(2) + 1;
- int[] nuMixOutChMask = new int[nuNumMixOutConfigs];
- for (int i = 0; i < nuNumMixOutConfigs; i++)
- {
- nuMixOutChMask[i] = buffer.ReadBits(nuBits4MixOutMask);
- }
- }
- }
- int[] AssetSizes = new int[nuNumAssets];
- for (int i = 0; i < nuNumAssets; i++)
- {
- if (1 == bBlownUpHeader)
- {
- AssetSizes[i] = buffer.ReadBits(20) + 1;
- }
- else
- {
- AssetSizes[i] = buffer.ReadBits(16) + 1;
- }
- }
- for (int i = 0; i < nuNumAssets; i++)
- {
- long bufferPosition = buffer.Position;
- int nuAssetDescriptorFSIZE = buffer.ReadBits(9) + 1;
- int DescriptorDataForAssetIndex = buffer.ReadBits(3);
- if (1 == bStaticFieldsPresent)
- {
- int AssetTypeDescrPresent = buffer.ReadBits(1);
- if (1 == AssetTypeDescrPresent)
- {
- int AssetTypeDescriptor = buffer.ReadBits(4);
- }
- int LanguageDescrPresent = buffer.ReadBits(1);
- if (1 == LanguageDescrPresent)
- {
- int LanguageDescriptor = buffer.ReadBits(24);
- }
- int bInfoTextPresent = buffer.ReadBits(1);
- if (1 == bInfoTextPresent)
- {
- int nuInfoTextByteSize = buffer.ReadBits(10) + 1;
- int[] InfoText = new int[nuInfoTextByteSize];
- for (int j = 0; j < nuInfoTextByteSize; j++)
- {
- InfoText[j] = buffer.ReadBits(8);
- }
- }
- int nuBitResolution = buffer.ReadBits(5) + 1;
- int nuMaxSampleRate = buffer.ReadBits(4);
- int nuTotalNumChs = buffer.ReadBits(8) + 1;
- int bOne2OneMapChannels2Speakers = buffer.ReadBits(1);
- int nuSpkrActivityMask = 0;
- if (1 == bOne2OneMapChannels2Speakers)
- {
- int bEmbeddedStereoFlag = 0;
- if (nuTotalNumChs > 2)
- {
- bEmbeddedStereoFlag = buffer.ReadBits(1);
- }
- int bEmbeddedSixChFlag = 0;
- if (nuTotalNumChs > 6)
- {
- bEmbeddedSixChFlag = buffer.ReadBits(1);
- }
- int bSpkrMaskEnabled = buffer.ReadBits(1);
- int nuNumBits4SAMask = 0;
- if (1 == bSpkrMaskEnabled)
- {
- nuNumBits4SAMask = buffer.ReadBits(2);
- nuNumBits4SAMask = nuNumBits4SAMask * 4 + 4;
- nuSpkrActivityMask = buffer.ReadBits(nuNumBits4SAMask);
- }
- // TODO...
- }
- stream.SampleRate = SampleRates[nuMaxSampleRate];
- stream.BitDepth = nuBitResolution;
-
- stream.LFE = 0;
- if ((nuSpkrActivityMask & 0x8) == 0x8)
- {
- ++stream.LFE;
- }
- if ((nuSpkrActivityMask & 0x1000) == 0x1000)
- {
- ++stream.LFE;
- }
- stream.ChannelCount = nuTotalNumChs - stream.LFE;
- }
- if (nuNumAssets > 1)
- {
- // TODO...
- break;
- }
- }
-
- // TODO
- if (stream.CoreStream != null)
- {
- var coreStream = (TSAudioStream)stream.CoreStream;
- if (coreStream.AudioMode == TSAudioMode.Extended &&
- stream.ChannelCount == 5)
- {
- stream.AudioMode = TSAudioMode.Extended;
- }
- /*
- if (coreStream.DialNorm != 0)
- {
- stream.DialNorm = coreStream.DialNorm;
- }
- */
- }
-
- if (stream.StreamType == TSStreamType.DTS_HD_MASTER_AUDIO)
- {
- stream.IsVBR = true;
- stream.IsInitialized = true;
- }
- else if (bitrate > 0)
- {
- stream.IsVBR = false;
- stream.BitRate = bitrate;
- if (stream.CoreStream != null)
- {
- stream.BitRate += stream.CoreStream.BitRate;
- stream.IsInitialized = true;
- }
- stream.IsInitialized = (stream.BitRate > 0 ? true : false);
- }
- }
- }
-}
diff --git a/BDInfo/TSCodecLPCM.cs b/BDInfo/TSCodecLPCM.cs
deleted file mode 100644
index 5709d8689f..0000000000
--- a/BDInfo/TSCodecLPCM.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-
-namespace BDInfo
-{
- public abstract class TSCodecLPCM
- {
- public static void Scan(
- TSAudioStream stream,
- TSStreamBuffer buffer,
- ref string tag)
- {
- if (stream.IsInitialized) return;
-
- byte[] header = buffer.ReadBytes(4);
- int flags = (header[2] << 8) + header[3];
-
- switch ((flags & 0xF000) >> 12)
- {
- case 1: // 1/0/0
- stream.ChannelCount = 1;
- stream.LFE = 0;
- break;
- case 3: // 2/0/0
- stream.ChannelCount = 2;
- stream.LFE = 0;
- break;
- case 4: // 3/0/0
- stream.ChannelCount = 3;
- stream.LFE = 0;
- break;
- case 5: // 2/1/0
- stream.ChannelCount = 3;
- stream.LFE = 0;
- break;
- case 6: // 3/1/0
- stream.ChannelCount = 4;
- stream.LFE = 0;
- break;
- case 7: // 2/2/0
- stream.ChannelCount = 4;
- stream.LFE = 0;
- break;
- case 8: // 3/2/0
- stream.ChannelCount = 5;
- stream.LFE = 0;
- break;
- case 9: // 3/2/1
- stream.ChannelCount = 5;
- stream.LFE = 1;
- break;
- case 10: // 3/4/0
- stream.ChannelCount = 7;
- stream.LFE = 0;
- break;
- case 11: // 3/4/1
- stream.ChannelCount = 7;
- stream.LFE = 1;
- break;
- default:
- stream.ChannelCount = 0;
- stream.LFE = 0;
- break;
- }
-
- switch ((flags & 0xC0) >> 6)
- {
- case 1:
- stream.BitDepth = 16;
- break;
- case 2:
- stream.BitDepth = 20;
- break;
- case 3:
- stream.BitDepth = 24;
- break;
- default:
- stream.BitDepth = 0;
- break;
- }
-
- switch ((flags & 0xF00) >> 8)
- {
- case 1:
- stream.SampleRate = 48000;
- break;
- case 4:
- stream.SampleRate = 96000;
- break;
- case 5:
- stream.SampleRate = 192000;
- break;
- default:
- stream.SampleRate = 0;
- break;
- }
-
- stream.BitRate = (uint)
- (stream.SampleRate * stream.BitDepth *
- (stream.ChannelCount + stream.LFE));
-
- stream.IsVBR = false;
- stream.IsInitialized = true;
- }
- }
-}
diff --git a/BDInfo/TSCodecMPEG2.cs b/BDInfo/TSCodecMPEG2.cs
deleted file mode 100644
index 8bcd07d020..0000000000
--- a/BDInfo/TSCodecMPEG2.cs
+++ /dev/null
@@ -1,208 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-#undef DEBUG
-
-
-namespace BDInfo
-{
- public abstract class TSCodecMPEG2
- {
- public static void Scan(
- TSVideoStream stream,
- TSStreamBuffer buffer,
- ref string tag)
- {
- int parse = 0;
- int pictureParse = 0;
- int sequenceHeaderParse = 0;
- int extensionParse = 0;
- int sequenceExtensionParse = 0;
-
- for (int i = 0; i < buffer.Length; i++)
- {
- parse = (parse << 8) + buffer.ReadByte();
-
- if (parse == 0x00000100)
- {
- pictureParse = 2;
- }
- else if (parse == 0x000001B3)
- {
- sequenceHeaderParse = 7;
- }
- else if (sequenceHeaderParse > 0)
- {
- --sequenceHeaderParse;
- switch (sequenceHeaderParse)
- {
-#if DEBUG
- case 6:
- break;
-
- case 5:
- break;
-
- case 4:
- stream.Width =
- (int)((parse & 0xFFF000) >> 12);
- stream.Height =
- (int)(parse & 0xFFF);
- break;
-
- case 3:
- stream.AspectRatio =
- (TSAspectRatio)((parse & 0xF0) >> 4);
-
- switch ((parse & 0xF0) >> 4)
- {
- case 0: // Forbidden
- break;
- case 1: // Square
- break;
- case 2: // 4:3
- break;
- case 3: // 16:9
- break;
- case 4: // 2.21:1
- break;
- default: // Reserved
- break;
- }
-
- switch (parse & 0xF)
- {
- case 0: // Forbidden
- break;
- case 1: // 23.976
- stream.FrameRateEnumerator = 24000;
- stream.FrameRateDenominator = 1001;
- break;
- case 2: // 24
- stream.FrameRateEnumerator = 24000;
- stream.FrameRateDenominator = 1000;
- break;
- case 3: // 25
- stream.FrameRateEnumerator = 25000;
- stream.FrameRateDenominator = 1000;
- break;
- case 4: // 29.97
- stream.FrameRateEnumerator = 30000;
- stream.FrameRateDenominator = 1001;
- break;
- case 5: // 30
- stream.FrameRateEnumerator = 30000;
- stream.FrameRateDenominator = 1000;
- break;
- case 6: // 50
- stream.FrameRateEnumerator = 50000;
- stream.FrameRateDenominator = 1000;
- break;
- case 7: // 59.94
- stream.FrameRateEnumerator = 60000;
- stream.FrameRateDenominator = 1001;
- break;
- case 8: // 60
- stream.FrameRateEnumerator = 60000;
- stream.FrameRateDenominator = 1000;
- break;
- default: // Reserved
- stream.FrameRateEnumerator = 0;
- stream.FrameRateDenominator = 0;
- break;
- }
- break;
-
- case 2:
- break;
-
- case 1:
- break;
-#endif
-
- case 0:
-#if DEBUG
- stream.BitRate =
- (((parse & 0xFFFFC0) >> 6) * 200);
-#endif
- stream.IsVBR = true;
- stream.IsInitialized = true;
- break;
- }
- }
- else if (pictureParse > 0)
- {
- --pictureParse;
- if (pictureParse == 0)
- {
- switch ((parse & 0x38) >> 3)
- {
- case 1:
- tag = "I";
- break;
- case 2:
- tag = "P";
- break;
- case 3:
- tag = "B";
- break;
- default:
- break;
- }
- if (stream.IsInitialized) return;
- }
- }
- else if (parse == 0x000001B5)
- {
- extensionParse = 1;
- }
- else if (extensionParse > 0)
- {
- --extensionParse;
- if (extensionParse == 0)
- {
- if ((parse & 0xF0) == 0x10)
- {
- sequenceExtensionParse = 1;
- }
- }
- }
- else if (sequenceExtensionParse > 0)
- {
- --sequenceExtensionParse;
-#if DEBUG
- if (sequenceExtensionParse == 0)
- {
- uint sequenceExtension =
- ((parse & 0x8) >> 3);
- if (sequenceExtension == 0)
- {
- stream.IsInterlaced = true;
- }
- else
- {
- stream.IsInterlaced = false;
- }
- }
-#endif
- }
- }
- }
- }
-}
diff --git a/BDInfo/TSCodecMVC.cs b/BDInfo/TSCodecMVC.cs
deleted file mode 100644
index abff0c1b08..0000000000
--- a/BDInfo/TSCodecMVC.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-
-namespace BDInfo
-{
- // TODO: Do something more interesting here...
-
- public abstract class TSCodecMVC
- {
- public static void Scan(
- TSVideoStream stream,
- TSStreamBuffer buffer,
- ref string tag)
- {
- stream.IsVBR = true;
- stream.IsInitialized = true;
- }
- }
-}
diff --git a/BDInfo/TSCodecTrueHD.cs b/BDInfo/TSCodecTrueHD.cs
deleted file mode 100644
index 5e81e162c5..0000000000
--- a/BDInfo/TSCodecTrueHD.cs
+++ /dev/null
@@ -1,186 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-
-namespace BDInfo
-{
- public abstract class TSCodecTrueHD
- {
- public static void Scan(
- TSAudioStream stream,
- TSStreamBuffer buffer,
- ref string tag)
- {
- if (stream.IsInitialized &&
- stream.CoreStream != null &&
- stream.CoreStream.IsInitialized) return;
-
- bool syncFound = false;
- uint sync = 0;
- for (int i = 0; i < buffer.Length; i++)
- {
- sync = (sync << 8) + buffer.ReadByte();
- if (sync == 0xF8726FBA)
- {
- syncFound = true;
- break;
- }
- }
-
- if (!syncFound)
- {
- tag = "CORE";
- if (stream.CoreStream == null)
- {
- stream.CoreStream = new TSAudioStream();
- stream.CoreStream.StreamType = TSStreamType.AC3_AUDIO;
- }
- if (!stream.CoreStream.IsInitialized)
- {
- buffer.BeginRead();
- TSCodecAC3.Scan(stream.CoreStream, buffer, ref tag);
- }
- return;
- }
-
- tag = "HD";
- int ratebits = buffer.ReadBits(4);
- if (ratebits != 0xF)
- {
- stream.SampleRate =
- (((ratebits & 8) > 0 ? 44100 : 48000) << (ratebits & 7));
- }
- int temp1 = buffer.ReadBits(8);
- int channels_thd_stream1 = buffer.ReadBits(5);
- int temp2 = buffer.ReadBits(2);
-
- stream.ChannelCount = 0;
- stream.LFE = 0;
- int c_LFE2 = buffer.ReadBits(1);
- if (c_LFE2 == 1)
- {
- stream.LFE += 1;
- }
- int c_Cvh = buffer.ReadBits(1);
- if (c_Cvh == 1)
- {
- stream.ChannelCount += 1;
- }
- int c_LRw = buffer.ReadBits(1);
- if (c_LRw == 1)
- {
- stream.ChannelCount += 2;
- }
- int c_LRsd = buffer.ReadBits(1);
- if (c_LRsd == 1)
- {
- stream.ChannelCount += 2;
- }
- int c_Ts = buffer.ReadBits(1);
- if (c_Ts == 1)
- {
- stream.ChannelCount += 1;
- }
- int c_Cs = buffer.ReadBits(1);
- if (c_Cs == 1)
- {
- stream.ChannelCount += 1;
- }
- int c_LRrs = buffer.ReadBits(1);
- if (c_LRrs == 1)
- {
- stream.ChannelCount += 2;
- }
- int c_LRc = buffer.ReadBits(1);
- if (c_LRc == 1)
- {
- stream.ChannelCount += 2;
- }
- int c_LRvh = buffer.ReadBits(1);
- if (c_LRvh == 1)
- {
- stream.ChannelCount += 2;
- }
- int c_LRs = buffer.ReadBits(1);
- if (c_LRs == 1)
- {
- stream.ChannelCount += 2;
- }
- int c_LFE = buffer.ReadBits(1);
- if (c_LFE == 1)
- {
- stream.LFE += 1;
- }
- int c_C = buffer.ReadBits(1);
- if (c_C == 1)
- {
- stream.ChannelCount += 1;
- }
- int c_LR = buffer.ReadBits(1);
- if (c_LR == 1)
- {
- stream.ChannelCount += 2;
- }
-
- int access_unit_size = 40 << (ratebits & 7);
- int access_unit_size_pow2 = 64 << (ratebits & 7);
-
- int a1 = buffer.ReadBits(16);
- int a2 = buffer.ReadBits(16);
- int a3 = buffer.ReadBits(16);
-
- int is_vbr = buffer.ReadBits(1);
- int peak_bitrate = buffer.ReadBits(15);
- peak_bitrate = (peak_bitrate * stream.SampleRate) >> 4;
-
- double peak_bitdepth =
- (double)peak_bitrate /
- (stream.ChannelCount + stream.LFE) /
- stream.SampleRate;
- if (peak_bitdepth > 14)
- {
- stream.BitDepth = 24;
- }
- else
- {
- stream.BitDepth = 16;
- }
-
-#if DEBUG
- System.Diagnostics.Debug.WriteLine(string.Format(
- "{0}\t{1}\t{2:F2}",
- stream.PID, peak_bitrate, peak_bitdepth));
-#endif
- /*
- // TODO: Get THD dialnorm from metadata
- if (stream.CoreStream != null)
- {
- TSAudioStream coreStream = (TSAudioStream)stream.CoreStream;
- if (coreStream.DialNorm != 0)
- {
- stream.DialNorm = coreStream.DialNorm;
- }
- }
- */
-
- stream.IsVBR = true;
- stream.IsInitialized = true;
- }
- }
-}
diff --git a/BDInfo/TSCodecVC1.cs b/BDInfo/TSCodecVC1.cs
deleted file mode 100644
index e2fbbf692f..0000000000
--- a/BDInfo/TSCodecVC1.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-
-namespace BDInfo
-{
- public abstract class TSCodecVC1
- {
- public static void Scan(
- TSVideoStream stream,
- TSStreamBuffer buffer,
- ref string tag)
- {
- int parse = 0;
- byte frameHeaderParse = 0;
- byte sequenceHeaderParse = 0;
- bool isInterlaced = false;
-
- for (int i = 0; i < buffer.Length; i++)
- {
- parse = (parse << 8) + buffer.ReadByte();
-
- if (parse == 0x0000010D)
- {
- frameHeaderParse = 4;
- }
- else if (frameHeaderParse > 0)
- {
- --frameHeaderParse;
- if (frameHeaderParse == 0)
- {
- uint pictureType = 0;
- if (isInterlaced)
- {
- if ((parse & 0x80000000) == 0)
- {
- pictureType =
- (uint)((parse & 0x78000000) >> 13);
- }
- else
- {
- pictureType =
- (uint)((parse & 0x3c000000) >> 12);
- }
- }
- else
- {
- pictureType =
- (uint)((parse & 0xf0000000) >> 14);
- }
-
- if ((pictureType & 0x20000) == 0)
- {
- tag = "P";
- }
- else if ((pictureType & 0x10000) == 0)
- {
- tag = "B";
- }
- else if ((pictureType & 0x8000) == 0)
- {
- tag = "I";
- }
- else if ((pictureType & 0x4000) == 0)
- {
- tag = "BI";
- }
- else
- {
- tag = null;
- }
- if (stream.IsInitialized) return;
- }
- }
- else if (parse == 0x0000010F)
- {
- sequenceHeaderParse = 6;
- }
- else if (sequenceHeaderParse > 0)
- {
- --sequenceHeaderParse;
- switch (sequenceHeaderParse)
- {
- case 5:
- int profileLevel = ((parse & 0x38) >> 3);
- if (((parse & 0xC0) >> 6) == 3)
- {
- stream.EncodingProfile = string.Format(
- "Advanced Profile {0}", profileLevel);
- }
- else
- {
- stream.EncodingProfile = string.Format(
- "Main Profile {0}", profileLevel);
- }
- break;
-
- case 0:
- if (((parse & 0x40) >> 6) > 0)
- {
- isInterlaced = true;
- }
- else
- {
- isInterlaced = false;
- }
- break;
- }
- stream.IsVBR = true;
- stream.IsInitialized = true;
- }
- }
- }
- }
-}
diff --git a/BDInfo/TSInterleavedFile.cs b/BDInfo/TSInterleavedFile.cs
deleted file mode 100644
index 0f35cfb2a1..0000000000
--- a/BDInfo/TSInterleavedFile.cs
+++ /dev/null
@@ -1,37 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-using MediaBrowser.Model.IO;
-
-// TODO: Do more interesting things here...
-
-namespace BDInfo
-{
- public class TSInterleavedFile
- {
- public FileSystemMetadata FileInfo = null;
- public string Name = null;
-
- public TSInterleavedFile(FileSystemMetadata fileInfo)
- {
- FileInfo = fileInfo;
- Name = fileInfo.Name.ToUpper();
- }
- }
-}
diff --git a/BDInfo/TSPlaylistFile.cs b/BDInfo/TSPlaylistFile.cs
deleted file mode 100644
index 1cc629b1de..0000000000
--- a/BDInfo/TSPlaylistFile.cs
+++ /dev/null
@@ -1,1282 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-#undef DEBUG
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using MediaBrowser.Model.IO;
-
-namespace BDInfo
-{
- public class TSPlaylistFile
- {
- private FileSystemMetadata FileInfo = null;
- public string FileType = null;
- public bool IsInitialized = false;
- public string Name = null;
- public BDROM BDROM = null;
- public bool HasHiddenTracks = false;
- public bool HasLoops = false;
- public bool IsCustom = false;
-
- public List Chapters = new List();
-
- public Dictionary Streams =
- new Dictionary();
- public Dictionary PlaylistStreams =
- new Dictionary();
- public List StreamClips =
- new List();
- public List> AngleStreams =
- new List>();
- public List> AngleClips =
- new List>();
- public int AngleCount = 0;
-
- public List SortedStreams =
- new List();
- public List VideoStreams =
- new List();
- public List AudioStreams =
- new List();
- public List TextStreams =
- new List();
- public List GraphicsStreams =
- new List();
-
- public TSPlaylistFile(BDROM bdrom,
- FileSystemMetadata fileInfo)
- {
- BDROM = bdrom;
- FileInfo = fileInfo;
- Name = fileInfo.Name.ToUpper();
- }
-
- public TSPlaylistFile(BDROM bdrom,
- string name,
- List clips)
- {
- BDROM = bdrom;
- Name = name;
- IsCustom = true;
- foreach (var clip in clips)
- {
- var newClip = new TSStreamClip(
- clip.StreamFile, clip.StreamClipFile);
-
- newClip.Name = clip.Name;
- newClip.TimeIn = clip.TimeIn;
- newClip.TimeOut = clip.TimeOut;
- newClip.Length = newClip.TimeOut - newClip.TimeIn;
- newClip.RelativeTimeIn = TotalLength;
- newClip.RelativeTimeOut = newClip.RelativeTimeIn + newClip.Length;
- newClip.AngleIndex = clip.AngleIndex;
- newClip.Chapters.Add(clip.TimeIn);
- StreamClips.Add(newClip);
-
- if (newClip.AngleIndex > AngleCount)
- {
- AngleCount = newClip.AngleIndex;
- }
- if (newClip.AngleIndex == 0)
- {
- Chapters.Add(newClip.RelativeTimeIn);
- }
- }
- LoadStreamClips();
- IsInitialized = true;
- }
-
- public override string ToString()
- {
- return Name;
- }
-
- public ulong InterleavedFileSize
- {
- get
- {
- ulong size = 0;
- foreach (var clip in StreamClips)
- {
- size += clip.InterleavedFileSize;
- }
- return size;
- }
- }
- public ulong FileSize
- {
- get
- {
- ulong size = 0;
- foreach (var clip in StreamClips)
- {
- size += clip.FileSize;
- }
- return size;
- }
- }
- public double TotalLength
- {
- get
- {
- double length = 0;
- foreach (var clip in StreamClips)
- {
- if (clip.AngleIndex == 0)
- {
- length += clip.Length;
- }
- }
- return length;
- }
- }
-
- public double TotalAngleLength
- {
- get
- {
- double length = 0;
- foreach (var clip in StreamClips)
- {
- length += clip.Length;
- }
- return length;
- }
- }
-
- public ulong TotalSize
- {
- get
- {
- ulong size = 0;
- foreach (var clip in StreamClips)
- {
- if (clip.AngleIndex == 0)
- {
- size += clip.PacketSize;
- }
- }
- return size;
- }
- }
-
- public ulong TotalAngleSize
- {
- get
- {
- ulong size = 0;
- foreach (var clip in StreamClips)
- {
- size += clip.PacketSize;
- }
- return size;
- }
- }
-
- public ulong TotalBitRate
- {
- get
- {
- if (TotalLength > 0)
- {
- return (ulong)Math.Round(((TotalSize * 8.0) / TotalLength));
- }
- return 0;
- }
- }
-
- public ulong TotalAngleBitRate
- {
- get
- {
- if (TotalAngleLength > 0)
- {
- return (ulong)Math.Round(((TotalAngleSize * 8.0) / TotalAngleLength));
- }
- return 0;
- }
- }
-
- public void Scan(
- Dictionary streamFiles,
- Dictionary streamClipFiles)
- {
- Stream fileStream = null;
- BinaryReader fileReader = null;
-
- try
- {
- Streams.Clear();
- StreamClips.Clear();
-
- fileStream = File.OpenRead(FileInfo.FullName);
- fileReader = new BinaryReader(fileStream);
-
- byte[] data = new byte[fileStream.Length];
- int dataLength = fileReader.Read(data, 0, data.Length);
-
- int pos = 0;
-
- FileType = ReadString(data, 8, ref pos);
- if (FileType != "MPLS0100" && FileType != "MPLS0200")
- {
- throw new Exception(string.Format(
- "Playlist {0} has an unknown file type {1}.",
- FileInfo.Name, FileType));
- }
-
- int playlistOffset = ReadInt32(data, ref pos);
- int chaptersOffset = ReadInt32(data, ref pos);
- int extensionsOffset = ReadInt32(data, ref pos);
-
- pos = playlistOffset;
-
- int playlistLength = ReadInt32(data, ref pos);
- int playlistReserved = ReadInt16(data, ref pos);
- int itemCount = ReadInt16(data, ref pos);
- int subitemCount = ReadInt16(data, ref pos);
-
- var chapterClips = new List();
- for (int itemIndex = 0; itemIndex < itemCount; itemIndex++)
- {
- int itemStart = pos;
- int itemLength = ReadInt16(data, ref pos);
- string itemName = ReadString(data, 5, ref pos);
- string itemType = ReadString(data, 4, ref pos);
-
- TSStreamFile streamFile = null;
- string streamFileName = string.Format(
- "{0}.M2TS", itemName);
- if (streamFiles.ContainsKey(streamFileName))
- {
- streamFile = streamFiles[streamFileName];
- }
- if (streamFile == null)
- {
- // Error condition
- }
-
- TSStreamClipFile streamClipFile = null;
- string streamClipFileName = string.Format(
- "{0}.CLPI", itemName);
- if (streamClipFiles.ContainsKey(streamClipFileName))
- {
- streamClipFile = streamClipFiles[streamClipFileName];
- }
- if (streamClipFile == null)
- {
- throw new Exception(string.Format(
- "Playlist {0} referenced missing file {1}.",
- FileInfo.Name, streamFileName));
- }
-
- pos += 1;
- int multiangle = (data[pos] >> 4) & 0x01;
- int condition = data[pos] & 0x0F;
- pos += 2;
-
- int inTime = ReadInt32(data, ref pos);
- if (inTime < 0) inTime &= 0x7FFFFFFF;
- double timeIn = (double)inTime / 45000;
-
- int outTime = ReadInt32(data, ref pos);
- if (outTime < 0) outTime &= 0x7FFFFFFF;
- double timeOut = (double)outTime / 45000;
-
- var streamClip = new TSStreamClip(
- streamFile, streamClipFile);
-
- streamClip.Name = streamFileName; //TODO
- streamClip.TimeIn = timeIn;
- streamClip.TimeOut = timeOut;
- streamClip.Length = streamClip.TimeOut - streamClip.TimeIn;
- streamClip.RelativeTimeIn = TotalLength;
- streamClip.RelativeTimeOut = streamClip.RelativeTimeIn + streamClip.Length;
- StreamClips.Add(streamClip);
- chapterClips.Add(streamClip);
-
- pos += 12;
- if (multiangle > 0)
- {
- int angles = data[pos];
- pos += 2;
- for (int angle = 0; angle < angles - 1; angle++)
- {
- string angleName = ReadString(data, 5, ref pos);
- string angleType = ReadString(data, 4, ref pos);
- pos += 1;
-
- TSStreamFile angleFile = null;
- string angleFileName = string.Format(
- "{0}.M2TS", angleName);
- if (streamFiles.ContainsKey(angleFileName))
- {
- angleFile = streamFiles[angleFileName];
- }
- if (angleFile == null)
- {
- throw new Exception(string.Format(
- "Playlist {0} referenced missing angle file {1}.",
- FileInfo.Name, angleFileName));
- }
-
- TSStreamClipFile angleClipFile = null;
- string angleClipFileName = string.Format(
- "{0}.CLPI", angleName);
- if (streamClipFiles.ContainsKey(angleClipFileName))
- {
- angleClipFile = streamClipFiles[angleClipFileName];
- }
- if (angleClipFile == null)
- {
- throw new Exception(string.Format(
- "Playlist {0} referenced missing angle file {1}.",
- FileInfo.Name, angleClipFileName));
- }
-
- var angleClip =
- new TSStreamClip(angleFile, angleClipFile);
- angleClip.AngleIndex = angle + 1;
- angleClip.TimeIn = streamClip.TimeIn;
- angleClip.TimeOut = streamClip.TimeOut;
- angleClip.RelativeTimeIn = streamClip.RelativeTimeIn;
- angleClip.RelativeTimeOut = streamClip.RelativeTimeOut;
- angleClip.Length = streamClip.Length;
- StreamClips.Add(angleClip);
- }
- if (angles - 1 > AngleCount) AngleCount = angles - 1;
- }
-
- int streamInfoLength = ReadInt16(data, ref pos);
- pos += 2;
- int streamCountVideo = data[pos++];
- int streamCountAudio = data[pos++];
- int streamCountPG = data[pos++];
- int streamCountIG = data[pos++];
- int streamCountSecondaryAudio = data[pos++];
- int streamCountSecondaryVideo = data[pos++];
- int streamCountPIP = data[pos++];
- pos += 5;
-
-#if DEBUG
- Debug.WriteLine(string.Format(
- "{0} : {1} -> V:{2} A:{3} PG:{4} IG:{5} 2A:{6} 2V:{7} PIP:{8}",
- Name, streamFileName, streamCountVideo, streamCountAudio, streamCountPG, streamCountIG,
- streamCountSecondaryAudio, streamCountSecondaryVideo, streamCountPIP));
-#endif
-
- for (int i = 0; i < streamCountVideo; i++)
- {
- var stream = CreatePlaylistStream(data, ref pos);
- if (stream != null) PlaylistStreams[stream.PID] = stream;
- }
- for (int i = 0; i < streamCountAudio; i++)
- {
- var stream = CreatePlaylistStream(data, ref pos);
- if (stream != null) PlaylistStreams[stream.PID] = stream;
- }
- for (int i = 0; i < streamCountPG; i++)
- {
- var stream = CreatePlaylistStream(data, ref pos);
- if (stream != null) PlaylistStreams[stream.PID] = stream;
- }
- for (int i = 0; i < streamCountIG; i++)
- {
- var stream = CreatePlaylistStream(data, ref pos);
- if (stream != null) PlaylistStreams[stream.PID] = stream;
- }
- for (int i = 0; i < streamCountSecondaryAudio; i++)
- {
- var stream = CreatePlaylistStream(data, ref pos);
- if (stream != null) PlaylistStreams[stream.PID] = stream;
- pos += 2;
- }
- for (int i = 0; i < streamCountSecondaryVideo; i++)
- {
- var stream = CreatePlaylistStream(data, ref pos);
- if (stream != null) PlaylistStreams[stream.PID] = stream;
- pos += 6;
- }
- /*
- * TODO
- *
- for (int i = 0; i < streamCountPIP; i++)
- {
- TSStream stream = CreatePlaylistStream(data, ref pos);
- if (stream != null) PlaylistStreams[stream.PID] = stream;
- }
- */
-
- pos += itemLength - (pos - itemStart) + 2;
- }
-
- pos = chaptersOffset + 4;
-
- int chapterCount = ReadInt16(data, ref pos);
-
- for (int chapterIndex = 0;
- chapterIndex < chapterCount;
- chapterIndex++)
- {
- int chapterType = data[pos + 1];
-
- if (chapterType == 1)
- {
- int streamFileIndex =
- ((int)data[pos + 2] << 8) + data[pos + 3];
-
- long chapterTime =
- ((long)data[pos + 4] << 24) +
- ((long)data[pos + 5] << 16) +
- ((long)data[pos + 6] << 8) +
- ((long)data[pos + 7]);
-
- var streamClip = chapterClips[streamFileIndex];
-
- double chapterSeconds = (double)chapterTime / 45000;
-
- double relativeSeconds =
- chapterSeconds -
- streamClip.TimeIn +
- streamClip.RelativeTimeIn;
-
- // TODO: Ignore short last chapter?
- if (TotalLength - relativeSeconds > 1.0)
- {
- streamClip.Chapters.Add(chapterSeconds);
- this.Chapters.Add(relativeSeconds);
- }
- }
- else
- {
- // TODO: Handle other chapter types?
- }
- pos += 14;
- }
- }
- finally
- {
- if (fileReader != null)
- {
- fileReader.Dispose();
- }
- if (fileStream != null)
- {
- fileStream.Dispose();
- }
- }
- }
-
- public void Initialize()
- {
- LoadStreamClips();
-
- var clipTimes = new Dictionary>();
- foreach (var clip in StreamClips)
- {
- if (clip.AngleIndex == 0)
- {
- if (clipTimes.ContainsKey(clip.Name))
- {
- if (clipTimes[clip.Name].Contains(clip.TimeIn))
- {
- HasLoops = true;
- break;
- }
- else
- {
- clipTimes[clip.Name].Add(clip.TimeIn);
- }
- }
- else
- {
- clipTimes[clip.Name] = new List { clip.TimeIn };
- }
- }
- }
- ClearBitrates();
- IsInitialized = true;
- }
-
- protected TSStream CreatePlaylistStream(byte[] data, ref int pos)
- {
- TSStream stream = null;
-
- int start = pos;
-
- int headerLength = data[pos++];
- int headerPos = pos;
- int headerType = data[pos++];
-
- int pid = 0;
- int subpathid = 0;
- int subclipid = 0;
-
- switch (headerType)
- {
- case 1:
- pid = ReadInt16(data, ref pos);
- break;
- case 2:
- subpathid = data[pos++];
- subclipid = data[pos++];
- pid = ReadInt16(data, ref pos);
- break;
- case 3:
- subpathid = data[pos++];
- pid = ReadInt16(data, ref pos);
- break;
- case 4:
- subpathid = data[pos++];
- subclipid = data[pos++];
- pid = ReadInt16(data, ref pos);
- break;
- default:
- break;
- }
-
- pos = headerPos + headerLength;
-
- int streamLength = data[pos++];
- int streamPos = pos;
-
- var streamType = (TSStreamType)data[pos++];
- switch (streamType)
- {
- case TSStreamType.MVC_VIDEO:
- // TODO
- break;
-
- case TSStreamType.AVC_VIDEO:
- case TSStreamType.MPEG1_VIDEO:
- case TSStreamType.MPEG2_VIDEO:
- case TSStreamType.VC1_VIDEO:
-
- var videoFormat = (TSVideoFormat)
- (data[pos] >> 4);
- var frameRate = (TSFrameRate)
- (data[pos] & 0xF);
- var aspectRatio = (TSAspectRatio)
- (data[pos + 1] >> 4);
-
- stream = new TSVideoStream();
- ((TSVideoStream)stream).VideoFormat = videoFormat;
- ((TSVideoStream)stream).AspectRatio = aspectRatio;
- ((TSVideoStream)stream).FrameRate = frameRate;
-
-#if DEBUG
- Debug.WriteLine(string.Format(
- "\t{0} {1} {2} {3} {4}",
- pid,
- streamType,
- videoFormat,
- frameRate,
- aspectRatio));
-#endif
-
- break;
-
- case TSStreamType.AC3_AUDIO:
- case TSStreamType.AC3_PLUS_AUDIO:
- case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
- case TSStreamType.AC3_TRUE_HD_AUDIO:
- case TSStreamType.DTS_AUDIO:
- case TSStreamType.DTS_HD_AUDIO:
- case TSStreamType.DTS_HD_MASTER_AUDIO:
- case TSStreamType.DTS_HD_SECONDARY_AUDIO:
- case TSStreamType.LPCM_AUDIO:
- case TSStreamType.MPEG1_AUDIO:
- case TSStreamType.MPEG2_AUDIO:
-
- int audioFormat = ReadByte(data, ref pos);
-
- var channelLayout = (TSChannelLayout)
- (audioFormat >> 4);
- var sampleRate = (TSSampleRate)
- (audioFormat & 0xF);
-
- string audioLanguage = ReadString(data, 3, ref pos);
-
- stream = new TSAudioStream();
- ((TSAudioStream)stream).ChannelLayout = channelLayout;
- ((TSAudioStream)stream).SampleRate = TSAudioStream.ConvertSampleRate(sampleRate);
- ((TSAudioStream)stream).LanguageCode = audioLanguage;
-
-#if DEBUG
- Debug.WriteLine(string.Format(
- "\t{0} {1} {2} {3} {4}",
- pid,
- streamType,
- audioLanguage,
- channelLayout,
- sampleRate));
-#endif
-
- break;
-
- case TSStreamType.INTERACTIVE_GRAPHICS:
- case TSStreamType.PRESENTATION_GRAPHICS:
-
- string graphicsLanguage = ReadString(data, 3, ref pos);
-
- stream = new TSGraphicsStream();
- ((TSGraphicsStream)stream).LanguageCode = graphicsLanguage;
-
- if (data[pos] != 0)
- {
- }
-
-#if DEBUG
- Debug.WriteLine(string.Format(
- "\t{0} {1} {2}",
- pid,
- streamType,
- graphicsLanguage));
-#endif
-
- break;
-
- case TSStreamType.SUBTITLE:
-
- int code = ReadByte(data, ref pos); // TODO
- string textLanguage = ReadString(data, 3, ref pos);
-
- stream = new TSTextStream();
- ((TSTextStream)stream).LanguageCode = textLanguage;
-
-#if DEBUG
- Debug.WriteLine(string.Format(
- "\t{0} {1} {2}",
- pid,
- streamType,
- textLanguage));
-#endif
-
- break;
-
- default:
- break;
- }
-
- pos = streamPos + streamLength;
-
- if (stream != null)
- {
- stream.PID = (ushort)pid;
- stream.StreamType = streamType;
- }
-
- return stream;
- }
-
- private void LoadStreamClips()
- {
- AngleClips.Clear();
- if (AngleCount > 0)
- {
- for (int angleIndex = 0; angleIndex < AngleCount; angleIndex++)
- {
- AngleClips.Add(new Dictionary());
- }
- }
-
- TSStreamClip referenceClip = null;
- if (StreamClips.Count > 0)
- {
- referenceClip = StreamClips[0];
- }
- foreach (var clip in StreamClips)
- {
- if (clip.StreamClipFile.Streams.Count > referenceClip.StreamClipFile.Streams.Count)
- {
- referenceClip = clip;
- }
- else if (clip.Length > referenceClip.Length)
- {
- referenceClip = clip;
- }
- if (AngleCount > 0)
- {
- if (clip.AngleIndex == 0)
- {
- for (int angleIndex = 0; angleIndex < AngleCount; angleIndex++)
- {
- AngleClips[angleIndex][clip.RelativeTimeIn] = clip;
- }
- }
- else
- {
- AngleClips[clip.AngleIndex - 1][clip.RelativeTimeIn] = clip;
- }
- }
- }
-
- foreach (var clipStream
- in referenceClip.StreamClipFile.Streams.Values)
- {
- if (!Streams.ContainsKey(clipStream.PID))
- {
- var stream = clipStream.Clone();
- Streams[clipStream.PID] = stream;
-
- if (!IsCustom && !PlaylistStreams.ContainsKey(stream.PID))
- {
- stream.IsHidden = true;
- HasHiddenTracks = true;
- }
-
- if (stream.IsVideoStream)
- {
- VideoStreams.Add((TSVideoStream)stream);
- }
- else if (stream.IsAudioStream)
- {
- AudioStreams.Add((TSAudioStream)stream);
- }
- else if (stream.IsGraphicsStream)
- {
- GraphicsStreams.Add((TSGraphicsStream)stream);
- }
- else if (stream.IsTextStream)
- {
- TextStreams.Add((TSTextStream)stream);
- }
- }
- }
-
- if (referenceClip.StreamFile != null)
- {
- // TODO: Better way to add this in?
- if (BDInfoSettings.EnableSSIF &&
- referenceClip.StreamFile.InterleavedFile != null &&
- referenceClip.StreamFile.Streams.ContainsKey(4114) &&
- !Streams.ContainsKey(4114))
- {
- var stream = referenceClip.StreamFile.Streams[4114].Clone();
- Streams[4114] = stream;
- if (stream.IsVideoStream)
- {
- VideoStreams.Add((TSVideoStream)stream);
- }
- }
-
- foreach (var clipStream
- in referenceClip.StreamFile.Streams.Values)
- {
- if (Streams.ContainsKey(clipStream.PID))
- {
- var stream = Streams[clipStream.PID];
-
- if (stream.StreamType != clipStream.StreamType) continue;
-
- if (clipStream.BitRate > stream.BitRate)
- {
- stream.BitRate = clipStream.BitRate;
- }
- stream.IsVBR = clipStream.IsVBR;
-
- if (stream.IsVideoStream &&
- clipStream.IsVideoStream)
- {
- ((TSVideoStream)stream).EncodingProfile =
- ((TSVideoStream)clipStream).EncodingProfile;
- }
- else if (stream.IsAudioStream &&
- clipStream.IsAudioStream)
- {
- var audioStream = (TSAudioStream)stream;
- var clipAudioStream = (TSAudioStream)clipStream;
-
- if (clipAudioStream.ChannelCount > audioStream.ChannelCount)
- {
- audioStream.ChannelCount = clipAudioStream.ChannelCount;
- }
- if (clipAudioStream.LFE > audioStream.LFE)
- {
- audioStream.LFE = clipAudioStream.LFE;
- }
- if (clipAudioStream.SampleRate > audioStream.SampleRate)
- {
- audioStream.SampleRate = clipAudioStream.SampleRate;
- }
- if (clipAudioStream.BitDepth > audioStream.BitDepth)
- {
- audioStream.BitDepth = clipAudioStream.BitDepth;
- }
- if (clipAudioStream.DialNorm < audioStream.DialNorm)
- {
- audioStream.DialNorm = clipAudioStream.DialNorm;
- }
- if (clipAudioStream.AudioMode != TSAudioMode.Unknown)
- {
- audioStream.AudioMode = clipAudioStream.AudioMode;
- }
- if (clipAudioStream.CoreStream != null &&
- audioStream.CoreStream == null)
- {
- audioStream.CoreStream = (TSAudioStream)
- clipAudioStream.CoreStream.Clone();
- }
- }
- }
- }
- }
-
- for (int i = 0; i < AngleCount; i++)
- {
- AngleStreams.Add(new Dictionary());
- }
-
- if (!BDInfoSettings.KeepStreamOrder)
- {
- VideoStreams.Sort(CompareVideoStreams);
- }
- foreach (TSStream stream in VideoStreams)
- {
- SortedStreams.Add(stream);
- for (int i = 0; i < AngleCount; i++)
- {
- var angleStream = stream.Clone();
- angleStream.AngleIndex = i + 1;
- AngleStreams[i][angleStream.PID] = angleStream;
- SortedStreams.Add(angleStream);
- }
- }
-
- if (!BDInfoSettings.KeepStreamOrder)
- {
- AudioStreams.Sort(CompareAudioStreams);
- }
- foreach (TSStream stream in AudioStreams)
- {
- SortedStreams.Add(stream);
- }
-
- if (!BDInfoSettings.KeepStreamOrder)
- {
- GraphicsStreams.Sort(CompareGraphicsStreams);
- }
- foreach (TSStream stream in GraphicsStreams)
- {
- SortedStreams.Add(stream);
- }
-
- if (!BDInfoSettings.KeepStreamOrder)
- {
- TextStreams.Sort(CompareTextStreams);
- }
- foreach (TSStream stream in TextStreams)
- {
- SortedStreams.Add(stream);
- }
- }
-
- public void ClearBitrates()
- {
- foreach (var clip in StreamClips)
- {
- clip.PayloadBytes = 0;
- clip.PacketCount = 0;
- clip.PacketSeconds = 0;
-
- if (clip.StreamFile != null)
- {
- foreach (var stream in clip.StreamFile.Streams.Values)
- {
- stream.PayloadBytes = 0;
- stream.PacketCount = 0;
- stream.PacketSeconds = 0;
- }
-
- if (clip.StreamFile != null &&
- clip.StreamFile.StreamDiagnostics != null)
- {
- clip.StreamFile.StreamDiagnostics.Clear();
- }
- }
- }
-
- foreach (var stream in SortedStreams)
- {
- stream.PayloadBytes = 0;
- stream.PacketCount = 0;
- stream.PacketSeconds = 0;
- }
- }
-
- public bool IsValid
- {
- get
- {
- if (!IsInitialized) return false;
-
- if (BDInfoSettings.FilterShortPlaylists &&
- TotalLength < BDInfoSettings.FilterShortPlaylistsValue)
- {
- return false;
- }
-
- if (HasLoops &&
- BDInfoSettings.FilterLoopingPlaylists)
- {
- return false;
- }
-
- return true;
- }
- }
-
- public int CompareVideoStreams(
- TSVideoStream x,
- TSVideoStream y)
- {
- if (x == null && y == null)
- {
- return 0;
- }
- else if (x == null && y != null)
- {
- return 1;
- }
- else if (x != null && y == null)
- {
- return -1;
- }
- else
- {
- if (x.Height > y.Height)
- {
- return -1;
- }
- else if (y.Height > x.Height)
- {
- return 1;
- }
- else if (x.PID > y.PID)
- {
- return 1;
- }
- else if (y.PID > x.PID)
- {
- return -1;
- }
- else
- {
- return 0;
- }
- }
- }
-
- public int CompareAudioStreams(
- TSAudioStream x,
- TSAudioStream y)
- {
- if (x == y)
- {
- return 0;
- }
- else if (x == null && y == null)
- {
- return 0;
- }
- else if (x == null && y != null)
- {
- return -1;
- }
- else if (x != null && y == null)
- {
- return 1;
- }
- else
- {
- if (x.ChannelCount > y.ChannelCount)
- {
- return -1;
- }
- else if (y.ChannelCount > x.ChannelCount)
- {
- return 1;
- }
- else
- {
- int sortX = GetStreamTypeSortIndex(x.StreamType);
- int sortY = GetStreamTypeSortIndex(y.StreamType);
-
- if (sortX > sortY)
- {
- return -1;
- }
- else if (sortY > sortX)
- {
- return 1;
- }
- else
- {
- if (x.LanguageCode == "eng")
- {
- return -1;
- }
- else if (y.LanguageCode == "eng")
- {
- return 1;
- }
- else if (x.LanguageCode != y.LanguageCode)
- {
- return string.Compare(
- x.LanguageName, y.LanguageName);
- }
- else if (x.PID < y.PID)
- {
- return -1;
- }
- else if (y.PID < x.PID)
- {
- return 1;
- }
- return 0;
- }
- }
- }
- }
-
- public int CompareTextStreams(
- TSTextStream x,
- TSTextStream y)
- {
- if (x == y)
- {
- return 0;
- }
- else if (x == null && y == null)
- {
- return 0;
- }
- else if (x == null && y != null)
- {
- return -1;
- }
- else if (x != null && y == null)
- {
- return 1;
- }
- else
- {
- if (x.LanguageCode == "eng")
- {
- return -1;
- }
- else if (y.LanguageCode == "eng")
- {
- return 1;
- }
- else
- {
- if (x.LanguageCode == y.LanguageCode)
- {
- if (x.PID > y.PID)
- {
- return 1;
- }
- else if (y.PID > x.PID)
- {
- return -1;
- }
- else
- {
- return 0;
- }
- }
- else
- {
- return string.Compare(
- x.LanguageName, y.LanguageName);
- }
- }
- }
- }
-
- private int CompareGraphicsStreams(
- TSGraphicsStream x,
- TSGraphicsStream y)
- {
- if (x == y)
- {
- return 0;
- }
- else if (x == null && y == null)
- {
- return 0;
- }
- else if (x == null && y != null)
- {
- return -1;
- }
- else if (x != null && y == null)
- {
- return 1;
- }
- else
- {
- int sortX = GetStreamTypeSortIndex(x.StreamType);
- int sortY = GetStreamTypeSortIndex(y.StreamType);
-
- if (sortX > sortY)
- {
- return -1;
- }
- else if (sortY > sortX)
- {
- return 1;
- }
- else if (x.LanguageCode == "eng")
- {
- return -1;
- }
- else if (y.LanguageCode == "eng")
- {
- return 1;
- }
- else
- {
- if (x.LanguageCode == y.LanguageCode)
- {
- if (x.PID > y.PID)
- {
- return 1;
- }
- else if (y.PID > x.PID)
- {
- return -1;
- }
- else
- {
- return 0;
- }
- }
- else
- {
- return string.Compare(x.LanguageName, y.LanguageName);
- }
- }
- }
- }
-
- private int GetStreamTypeSortIndex(TSStreamType streamType)
- {
- switch (streamType)
- {
- case TSStreamType.Unknown:
- return 0;
- case TSStreamType.MPEG1_VIDEO:
- return 1;
- case TSStreamType.MPEG2_VIDEO:
- return 2;
- case TSStreamType.AVC_VIDEO:
- return 3;
- case TSStreamType.VC1_VIDEO:
- return 4;
- case TSStreamType.MVC_VIDEO:
- return 5;
-
- case TSStreamType.MPEG1_AUDIO:
- return 1;
- case TSStreamType.MPEG2_AUDIO:
- return 2;
- case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
- return 3;
- case TSStreamType.DTS_HD_SECONDARY_AUDIO:
- return 4;
- case TSStreamType.AC3_AUDIO:
- return 5;
- case TSStreamType.DTS_AUDIO:
- return 6;
- case TSStreamType.AC3_PLUS_AUDIO:
- return 7;
- case TSStreamType.DTS_HD_AUDIO:
- return 8;
- case TSStreamType.AC3_TRUE_HD_AUDIO:
- return 9;
- case TSStreamType.DTS_HD_MASTER_AUDIO:
- return 10;
- case TSStreamType.LPCM_AUDIO:
- return 11;
-
- case TSStreamType.SUBTITLE:
- return 1;
- case TSStreamType.INTERACTIVE_GRAPHICS:
- return 2;
- case TSStreamType.PRESENTATION_GRAPHICS:
- return 3;
-
- default:
- return 0;
- }
- }
-
- protected string ReadString(
- byte[] data,
- int count,
- ref int pos)
- {
- string val = Encoding.ASCII.GetString(data, pos, count);
-
- pos += count;
-
- return val;
- }
-
- protected int ReadInt32(
- byte[] data,
- ref int pos)
- {
- int val =
- ((int)data[pos] << 24) +
- ((int)data[pos + 1] << 16) +
- ((int)data[pos + 2] << 8) +
- ((int)data[pos + 3]);
-
- pos += 4;
-
- return val;
- }
-
- protected int ReadInt16(
- byte[] data,
- ref int pos)
- {
- int val =
- ((int)data[pos] << 8) +
- ((int)data[pos + 1]);
-
- pos += 2;
-
- return val;
- }
-
- protected byte ReadByte(
- byte[] data,
- ref int pos)
- {
- return data[pos++];
- }
- }
-}
diff --git a/BDInfo/TSStream.cs b/BDInfo/TSStream.cs
deleted file mode 100644
index 3c30a85971..0000000000
--- a/BDInfo/TSStream.cs
+++ /dev/null
@@ -1,780 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-using System;
-using System.Collections.Generic;
-
-namespace BDInfo
-{
- public enum TSStreamType : byte
- {
- Unknown = 0,
- MPEG1_VIDEO = 0x01,
- MPEG2_VIDEO = 0x02,
- AVC_VIDEO = 0x1b,
- MVC_VIDEO = 0x20,
- VC1_VIDEO = 0xea,
- MPEG1_AUDIO = 0x03,
- MPEG2_AUDIO = 0x04,
- LPCM_AUDIO = 0x80,
- AC3_AUDIO = 0x81,
- AC3_PLUS_AUDIO = 0x84,
- AC3_PLUS_SECONDARY_AUDIO = 0xA1,
- AC3_TRUE_HD_AUDIO = 0x83,
- DTS_AUDIO = 0x82,
- DTS_HD_AUDIO = 0x85,
- DTS_HD_SECONDARY_AUDIO = 0xA2,
- DTS_HD_MASTER_AUDIO = 0x86,
- PRESENTATION_GRAPHICS = 0x90,
- INTERACTIVE_GRAPHICS = 0x91,
- SUBTITLE = 0x92
- }
-
- public enum TSVideoFormat : byte
- {
- Unknown = 0,
- VIDEOFORMAT_480i = 1,
- VIDEOFORMAT_576i = 2,
- VIDEOFORMAT_480p = 3,
- VIDEOFORMAT_1080i = 4,
- VIDEOFORMAT_720p = 5,
- VIDEOFORMAT_1080p = 6,
- VIDEOFORMAT_576p = 7,
- }
-
- public enum TSFrameRate : byte
- {
- Unknown = 0,
- FRAMERATE_23_976 = 1,
- FRAMERATE_24 = 2,
- FRAMERATE_25 = 3,
- FRAMERATE_29_97 = 4,
- FRAMERATE_50 = 6,
- FRAMERATE_59_94 = 7
- }
-
- public enum TSChannelLayout : byte
- {
- Unknown = 0,
- CHANNELLAYOUT_MONO = 1,
- CHANNELLAYOUT_STEREO = 3,
- CHANNELLAYOUT_MULTI = 6,
- CHANNELLAYOUT_COMBO = 12
- }
-
- public enum TSSampleRate : byte
- {
- Unknown = 0,
- SAMPLERATE_48 = 1,
- SAMPLERATE_96 = 4,
- SAMPLERATE_192 = 5,
- SAMPLERATE_48_192 = 12,
- SAMPLERATE_48_96 = 14
- }
-
- public enum TSAspectRatio
- {
- Unknown = 0,
- ASPECT_4_3 = 2,
- ASPECT_16_9 = 3,
- ASPECT_2_21 = 4
- }
-
- public class TSDescriptor
- {
- public byte Name;
- public byte[] Value;
-
- public TSDescriptor(byte name, byte length)
- {
- Name = name;
- Value = new byte[length];
- }
-
- public TSDescriptor Clone()
- {
- var descriptor =
- new TSDescriptor(Name, (byte)Value.Length);
- Value.CopyTo(descriptor.Value, 0);
- return descriptor;
- }
- }
-
- public abstract class TSStream
- {
- public TSStream()
- {
- }
-
- public override string ToString()
- {
- return string.Format("{0} ({1})", CodecShortName, PID);
- }
-
- public ushort PID;
- public TSStreamType StreamType;
- public List Descriptors = null;
- public long BitRate = 0;
- public long ActiveBitRate = 0;
- public bool IsVBR = false;
- public bool IsInitialized = false;
- public string LanguageName;
- public bool IsHidden = false;
-
- public ulong PayloadBytes = 0;
- public ulong PacketCount = 0;
- public double PacketSeconds = 0;
- public int AngleIndex = 0;
-
- public ulong PacketSize => PacketCount * 192;
-
- private string _LanguageCode;
- public string LanguageCode
- {
- get => _LanguageCode;
- set
- {
- _LanguageCode = value;
- LanguageName = LanguageCodes.GetName(value);
- }
- }
-
- public bool IsVideoStream
- {
- get
- {
- switch (StreamType)
- {
- case TSStreamType.MPEG1_VIDEO:
- case TSStreamType.MPEG2_VIDEO:
- case TSStreamType.AVC_VIDEO:
- case TSStreamType.MVC_VIDEO:
- case TSStreamType.VC1_VIDEO:
- return true;
-
- default:
- return false;
- }
- }
- }
-
- public bool IsAudioStream
- {
- get
- {
- switch (StreamType)
- {
- case TSStreamType.MPEG1_AUDIO:
- case TSStreamType.MPEG2_AUDIO:
- case TSStreamType.LPCM_AUDIO:
- case TSStreamType.AC3_AUDIO:
- case TSStreamType.AC3_PLUS_AUDIO:
- case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
- case TSStreamType.AC3_TRUE_HD_AUDIO:
- case TSStreamType.DTS_AUDIO:
- case TSStreamType.DTS_HD_AUDIO:
- case TSStreamType.DTS_HD_SECONDARY_AUDIO:
- case TSStreamType.DTS_HD_MASTER_AUDIO:
- return true;
-
- default:
- return false;
- }
- }
- }
-
- public bool IsGraphicsStream
- {
- get
- {
- switch (StreamType)
- {
- case TSStreamType.PRESENTATION_GRAPHICS:
- case TSStreamType.INTERACTIVE_GRAPHICS:
- return true;
-
- default:
- return false;
- }
- }
- }
-
- public bool IsTextStream
- {
- get
- {
- switch (StreamType)
- {
- case TSStreamType.SUBTITLE:
- return true;
-
- default:
- return false;
- }
- }
- }
-
- public string CodecName
- {
- get
- {
- switch (StreamType)
- {
- case TSStreamType.MPEG1_VIDEO:
- return "MPEG-1 Video";
- case TSStreamType.MPEG2_VIDEO:
- return "MPEG-2 Video";
- case TSStreamType.AVC_VIDEO:
- return "MPEG-4 AVC Video";
- case TSStreamType.MVC_VIDEO:
- return "MPEG-4 MVC Video";
- case TSStreamType.VC1_VIDEO:
- return "VC-1 Video";
- case TSStreamType.MPEG1_AUDIO:
- return "MP1 Audio";
- case TSStreamType.MPEG2_AUDIO:
- return "MP2 Audio";
- case TSStreamType.LPCM_AUDIO:
- return "LPCM Audio";
- case TSStreamType.AC3_AUDIO:
- if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
- return "Dolby Digital EX Audio";
- else
- return "Dolby Digital Audio";
- case TSStreamType.AC3_PLUS_AUDIO:
- case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
- return "Dolby Digital Plus Audio";
- case TSStreamType.AC3_TRUE_HD_AUDIO:
- return "Dolby TrueHD Audio";
- case TSStreamType.DTS_AUDIO:
- if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
- return "DTS-ES Audio";
- else
- return "DTS Audio";
- case TSStreamType.DTS_HD_AUDIO:
- return "DTS-HD High-Res Audio";
- case TSStreamType.DTS_HD_SECONDARY_AUDIO:
- return "DTS Express";
- case TSStreamType.DTS_HD_MASTER_AUDIO:
- return "DTS-HD Master Audio";
- case TSStreamType.PRESENTATION_GRAPHICS:
- return "Presentation Graphics";
- case TSStreamType.INTERACTIVE_GRAPHICS:
- return "Interactive Graphics";
- case TSStreamType.SUBTITLE:
- return "Subtitle";
- default:
- return "UNKNOWN";
- }
- }
- }
-
- public string CodecAltName
- {
- get
- {
- switch (StreamType)
- {
- case TSStreamType.MPEG1_VIDEO:
- return "MPEG-1";
- case TSStreamType.MPEG2_VIDEO:
- return "MPEG-2";
- case TSStreamType.AVC_VIDEO:
- return "AVC";
- case TSStreamType.MVC_VIDEO:
- return "MVC";
- case TSStreamType.VC1_VIDEO:
- return "VC-1";
- case TSStreamType.MPEG1_AUDIO:
- return "MP1";
- case TSStreamType.MPEG2_AUDIO:
- return "MP2";
- case TSStreamType.LPCM_AUDIO:
- return "LPCM";
- case TSStreamType.AC3_AUDIO:
- return "DD AC3";
- case TSStreamType.AC3_PLUS_AUDIO:
- case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
- return "DD AC3+";
- case TSStreamType.AC3_TRUE_HD_AUDIO:
- return "Dolby TrueHD";
- case TSStreamType.DTS_AUDIO:
- return "DTS";
- case TSStreamType.DTS_HD_AUDIO:
- return "DTS-HD Hi-Res";
- case TSStreamType.DTS_HD_SECONDARY_AUDIO:
- return "DTS Express";
- case TSStreamType.DTS_HD_MASTER_AUDIO:
- return "DTS-HD Master";
- case TSStreamType.PRESENTATION_GRAPHICS:
- return "PGS";
- case TSStreamType.INTERACTIVE_GRAPHICS:
- return "IGS";
- case TSStreamType.SUBTITLE:
- return "SUB";
- default:
- return "UNKNOWN";
- }
- }
- }
-
- public string CodecShortName
- {
- get
- {
- switch (StreamType)
- {
- case TSStreamType.MPEG1_VIDEO:
- return "MPEG-1";
- case TSStreamType.MPEG2_VIDEO:
- return "MPEG-2";
- case TSStreamType.AVC_VIDEO:
- return "AVC";
- case TSStreamType.MVC_VIDEO:
- return "MVC";
- case TSStreamType.VC1_VIDEO:
- return "VC-1";
- case TSStreamType.MPEG1_AUDIO:
- return "MP1";
- case TSStreamType.MPEG2_AUDIO:
- return "MP2";
- case TSStreamType.LPCM_AUDIO:
- return "LPCM";
- case TSStreamType.AC3_AUDIO:
- if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
- return "AC3-EX";
- else
- return "AC3";
- case TSStreamType.AC3_PLUS_AUDIO:
- case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
- return "AC3+";
- case TSStreamType.AC3_TRUE_HD_AUDIO:
- return "TrueHD";
- case TSStreamType.DTS_AUDIO:
- if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
- return "DTS-ES";
- else
- return "DTS";
- case TSStreamType.DTS_HD_AUDIO:
- return "DTS-HD HR";
- case TSStreamType.DTS_HD_SECONDARY_AUDIO:
- return "DTS Express";
- case TSStreamType.DTS_HD_MASTER_AUDIO:
- return "DTS-HD MA";
- case TSStreamType.PRESENTATION_GRAPHICS:
- return "PGS";
- case TSStreamType.INTERACTIVE_GRAPHICS:
- return "IGS";
- case TSStreamType.SUBTITLE:
- return "SUB";
- default:
- return "UNKNOWN";
- }
- }
- }
-
- public virtual string Description => "";
-
- public abstract TSStream Clone();
-
- protected void CopyTo(TSStream stream)
- {
- stream.PID = PID;
- stream.StreamType = StreamType;
- stream.IsVBR = IsVBR;
- stream.BitRate = BitRate;
- stream.IsInitialized = IsInitialized;
- stream.LanguageCode = _LanguageCode;
- if (Descriptors != null)
- {
- stream.Descriptors = new List();
- foreach (var descriptor in Descriptors)
- {
- stream.Descriptors.Add(descriptor.Clone());
- }
- }
- }
- }
-
- public class TSVideoStream : TSStream
- {
- public TSVideoStream()
- {
- }
-
- public int Width;
- public int Height;
- public bool IsInterlaced;
- public int FrameRateEnumerator;
- public int FrameRateDenominator;
- public TSAspectRatio AspectRatio;
- public string EncodingProfile;
-
- private TSVideoFormat _VideoFormat;
- public TSVideoFormat VideoFormat
- {
- get => _VideoFormat;
- set
- {
- _VideoFormat = value;
- switch (value)
- {
- case TSVideoFormat.VIDEOFORMAT_480i:
- Height = 480;
- IsInterlaced = true;
- break;
- case TSVideoFormat.VIDEOFORMAT_480p:
- Height = 480;
- IsInterlaced = false;
- break;
- case TSVideoFormat.VIDEOFORMAT_576i:
- Height = 576;
- IsInterlaced = true;
- break;
- case TSVideoFormat.VIDEOFORMAT_576p:
- Height = 576;
- IsInterlaced = false;
- break;
- case TSVideoFormat.VIDEOFORMAT_720p:
- Height = 720;
- IsInterlaced = false;
- break;
- case TSVideoFormat.VIDEOFORMAT_1080i:
- Height = 1080;
- IsInterlaced = true;
- break;
- case TSVideoFormat.VIDEOFORMAT_1080p:
- Height = 1080;
- IsInterlaced = false;
- break;
- }
- }
- }
-
- private TSFrameRate _FrameRate;
- public TSFrameRate FrameRate
- {
- get => _FrameRate;
- set
- {
- _FrameRate = value;
- switch (value)
- {
- case TSFrameRate.FRAMERATE_23_976:
- FrameRateEnumerator = 24000;
- FrameRateDenominator = 1001;
- break;
- case TSFrameRate.FRAMERATE_24:
- FrameRateEnumerator = 24000;
- FrameRateDenominator = 1000;
- break;
- case TSFrameRate.FRAMERATE_25:
- FrameRateEnumerator = 25000;
- FrameRateDenominator = 1000;
- break;
- case TSFrameRate.FRAMERATE_29_97:
- FrameRateEnumerator = 30000;
- FrameRateDenominator = 1001;
- break;
- case TSFrameRate.FRAMERATE_50:
- FrameRateEnumerator = 50000;
- FrameRateDenominator = 1000;
- break;
- case TSFrameRate.FRAMERATE_59_94:
- FrameRateEnumerator = 60000;
- FrameRateDenominator = 1001;
- break;
- }
- }
- }
-
- public override string Description
- {
- get
- {
- string description = "";
-
- if (Height > 0)
- {
- description += string.Format("{0:D}{1} / ",
- Height,
- IsInterlaced ? "i" : "p");
- }
- if (FrameRateEnumerator > 0 &&
- FrameRateDenominator > 0)
- {
- if (FrameRateEnumerator % FrameRateDenominator == 0)
- {
- description += string.Format("{0:D} fps / ",
- FrameRateEnumerator / FrameRateDenominator);
- }
- else
- {
- description += string.Format("{0:F3} fps / ",
- (double)FrameRateEnumerator / FrameRateDenominator);
- }
-
- }
- if (AspectRatio == TSAspectRatio.ASPECT_4_3)
- {
- description += "4:3 / ";
- }
- else if (AspectRatio == TSAspectRatio.ASPECT_16_9)
- {
- description += "16:9 / ";
- }
- if (EncodingProfile != null)
- {
- description += EncodingProfile + " / ";
- }
- if (description.EndsWith(" / "))
- {
- description = description.Substring(0, description.Length - 3);
- }
- return description;
- }
- }
-
- public override TSStream Clone()
- {
- var stream = new TSVideoStream();
- CopyTo(stream);
-
- stream.VideoFormat = _VideoFormat;
- stream.FrameRate = _FrameRate;
- stream.Width = Width;
- stream.Height = Height;
- stream.IsInterlaced = IsInterlaced;
- stream.FrameRateEnumerator = FrameRateEnumerator;
- stream.FrameRateDenominator = FrameRateDenominator;
- stream.AspectRatio = AspectRatio;
- stream.EncodingProfile = EncodingProfile;
-
- return stream;
- }
- }
-
- public enum TSAudioMode
- {
- Unknown,
- DualMono,
- Stereo,
- Surround,
- Extended
- }
-
- public class TSAudioStream : TSStream
- {
- public TSAudioStream()
- {
- }
-
- public int SampleRate;
- public int ChannelCount;
- public int BitDepth;
- public int LFE;
- public int DialNorm;
- public TSAudioMode AudioMode;
- public TSAudioStream CoreStream;
- public TSChannelLayout ChannelLayout;
-
- public static int ConvertSampleRate(
- TSSampleRate sampleRate)
- {
- switch (sampleRate)
- {
- case TSSampleRate.SAMPLERATE_48:
- return 48000;
-
- case TSSampleRate.SAMPLERATE_96:
- case TSSampleRate.SAMPLERATE_48_96:
- return 96000;
-
- case TSSampleRate.SAMPLERATE_192:
- case TSSampleRate.SAMPLERATE_48_192:
- return 192000;
- }
- return 0;
- }
-
- public string ChannelDescription
- {
- get
- {
- if (ChannelLayout == TSChannelLayout.CHANNELLAYOUT_MONO &&
- ChannelCount == 2)
- {
- }
-
- string description = "";
- if (ChannelCount > 0)
- {
- description += string.Format(
- "{0:D}.{1:D}",
- ChannelCount, LFE);
- }
- else
- {
- switch (ChannelLayout)
- {
- case TSChannelLayout.CHANNELLAYOUT_MONO:
- description += "1.0";
- break;
- case TSChannelLayout.CHANNELLAYOUT_STEREO:
- description += "2.0";
- break;
- case TSChannelLayout.CHANNELLAYOUT_MULTI:
- description += "5.1";
- break;
- }
- }
- if (AudioMode == TSAudioMode.Extended)
- {
- if (StreamType == TSStreamType.AC3_AUDIO)
- {
- description += "-EX";
- }
- if (StreamType == TSStreamType.DTS_AUDIO ||
- StreamType == TSStreamType.DTS_HD_AUDIO ||
- StreamType == TSStreamType.DTS_HD_MASTER_AUDIO)
- {
- description += "-ES";
- }
- }
- return description;
- }
- }
-
- public override string Description
- {
- get
- {
- string description = ChannelDescription;
-
- if (SampleRate > 0)
- {
- description += string.Format(
- " / {0:D} kHz", SampleRate / 1000);
- }
- if (BitRate > 0)
- {
- description += string.Format(
- " / {0:D} kbps", (uint)Math.Round((double)BitRate / 1000));
- }
- if (BitDepth > 0)
- {
- description += string.Format(
- " / {0:D}-bit", BitDepth);
- }
- if (DialNorm != 0)
- {
- description += string.Format(
- " / DN {0}dB", DialNorm);
- }
- if (ChannelCount == 2)
- {
- switch (AudioMode)
- {
- case TSAudioMode.DualMono:
- description += " / Dual Mono";
- break;
-
- case TSAudioMode.Surround:
- description += " / Dolby Surround";
- break;
- }
- }
- if (description.EndsWith(" / "))
- {
- description = description.Substring(0, description.Length - 3);
- }
- if (CoreStream != null)
- {
- string codec = "";
- switch (CoreStream.StreamType)
- {
- case TSStreamType.AC3_AUDIO:
- codec = "AC3 Embedded";
- break;
- case TSStreamType.DTS_AUDIO:
- codec = "DTS Core";
- break;
- }
- description += string.Format(
- " ({0}: {1})",
- codec,
- CoreStream.Description);
- }
- return description;
- }
- }
-
- public override TSStream Clone()
- {
- var stream = new TSAudioStream();
- CopyTo(stream);
-
- stream.SampleRate = SampleRate;
- stream.ChannelLayout = ChannelLayout;
- stream.ChannelCount = ChannelCount;
- stream.BitDepth = BitDepth;
- stream.LFE = LFE;
- stream.DialNorm = DialNorm;
- stream.AudioMode = AudioMode;
- if (CoreStream != null)
- {
- stream.CoreStream = (TSAudioStream)CoreStream.Clone();
- }
-
- return stream;
- }
- }
-
- public class TSGraphicsStream : TSStream
- {
- public TSGraphicsStream()
- {
- IsVBR = true;
- IsInitialized = true;
- }
-
- public override TSStream Clone()
- {
- var stream = new TSGraphicsStream();
- CopyTo(stream);
- return stream;
- }
- }
-
- public class TSTextStream : TSStream
- {
- public TSTextStream()
- {
- IsVBR = true;
- IsInitialized = true;
- }
-
- public override TSStream Clone()
- {
- var stream = new TSTextStream();
- CopyTo(stream);
- return stream;
- }
- }
-}
diff --git a/BDInfo/TSStreamBuffer.cs b/BDInfo/TSStreamBuffer.cs
deleted file mode 100644
index 30bd1a3f44..0000000000
--- a/BDInfo/TSStreamBuffer.cs
+++ /dev/null
@@ -1,130 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-using System;
-using System.Collections.Specialized;
-using System.IO;
-
-namespace BDInfo
-{
- public class TSStreamBuffer
- {
- private MemoryStream Stream = new MemoryStream();
- private int SkipBits = 0;
- private byte[] Buffer;
- private int BufferLength = 0;
- public int TransferLength = 0;
-
- public TSStreamBuffer()
- {
- Buffer = new byte[4096];
- Stream = new MemoryStream(Buffer);
- }
-
- public long Length => (long)BufferLength;
-
- public long Position => Stream.Position;
-
- public void Add(
- byte[] buffer,
- int offset,
- int length)
- {
- TransferLength += length;
-
- if (BufferLength + length >= Buffer.Length)
- {
- length = Buffer.Length - BufferLength;
- }
- if (length > 0)
- {
- Array.Copy(buffer, offset, Buffer, BufferLength, length);
- BufferLength += length;
- }
- }
-
- public void Seek(
- long offset,
- SeekOrigin loc)
- {
- Stream.Seek(offset, loc);
- }
-
- public void Reset()
- {
- BufferLength = 0;
- TransferLength = 0;
- }
-
- public void BeginRead()
- {
- SkipBits = 0;
- Stream.Seek(0, SeekOrigin.Begin);
- }
-
- public void EndRead()
- {
- }
-
- public byte[] ReadBytes(int bytes)
- {
- if (Stream.Position + bytes >= BufferLength)
- {
- return null;
- }
-
- byte[] value = new byte[bytes];
- Stream.Read(value, 0, bytes);
- return value;
- }
-
- public byte ReadByte()
- {
- return (byte)Stream.ReadByte();
- }
-
- public int ReadBits(int bits)
- {
- long pos = Stream.Position;
-
- int shift = 24;
- int data = 0;
- for (int i = 0; i < 4; i++)
- {
- if (pos + i >= BufferLength) break;
- data += (Stream.ReadByte() << shift);
- shift -= 8;
- }
- var vector = new BitVector32(data);
-
- int value = 0;
- for (int i = SkipBits; i < SkipBits + bits; i++)
- {
- value <<= 1;
- value += (vector[1 << (32 - i - 1)] ? 1 : 0);
- }
-
- SkipBits += bits;
- Stream.Seek(pos + (SkipBits >> 3), SeekOrigin.Begin);
- SkipBits = SkipBits % 8;
-
- return value;
- }
- }
-}
diff --git a/BDInfo/TSStreamClip.cs b/BDInfo/TSStreamClip.cs
deleted file mode 100644
index 295eeb6b18..0000000000
--- a/BDInfo/TSStreamClip.cs
+++ /dev/null
@@ -1,107 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-using System;
-using System.Collections.Generic;
-
-namespace BDInfo
-{
- public class TSStreamClip
- {
- public int AngleIndex = 0;
- public string Name;
- public double TimeIn;
- public double TimeOut;
- public double RelativeTimeIn;
- public double RelativeTimeOut;
- public double Length;
-
- public ulong FileSize = 0;
- public ulong InterleavedFileSize = 0;
- public ulong PayloadBytes = 0;
- public ulong PacketCount = 0;
- public double PacketSeconds = 0;
-
- public List Chapters = new List();
-
- public TSStreamFile StreamFile = null;
- public TSStreamClipFile StreamClipFile = null;
-
- public TSStreamClip(
- TSStreamFile streamFile,
- TSStreamClipFile streamClipFile)
- {
- if (streamFile != null)
- {
- Name = streamFile.Name;
- StreamFile = streamFile;
- FileSize = (ulong)StreamFile.FileInfo.Length;
- if (StreamFile.InterleavedFile != null)
- {
- InterleavedFileSize = (ulong)StreamFile.InterleavedFile.FileInfo.Length;
- }
- }
- StreamClipFile = streamClipFile;
- }
-
- public string DisplayName
- {
- get
- {
- if (StreamFile != null &&
- StreamFile.InterleavedFile != null &&
- BDInfoSettings.EnableSSIF)
- {
- return StreamFile.InterleavedFile.Name;
- }
- return Name;
- }
- }
-
- public ulong PacketSize => PacketCount * 192;
-
- public ulong PacketBitRate
- {
- get
- {
- if (PacketSeconds > 0)
- {
- return (ulong)Math.Round(((PacketSize * 8.0) / PacketSeconds));
- }
- return 0;
- }
- }
-
- public bool IsCompatible(TSStreamClip clip)
- {
- foreach (var stream1 in StreamFile.Streams.Values)
- {
- if (clip.StreamFile.Streams.ContainsKey(stream1.PID))
- {
- var stream2 = clip.StreamFile.Streams[stream1.PID];
- if (stream1.StreamType != stream2.StreamType)
- {
- return false;
- }
- }
- }
- return true;
- }
- }
-}
diff --git a/BDInfo/TSStreamClipFile.cs b/BDInfo/TSStreamClipFile.cs
deleted file mode 100644
index e1097b23da..0000000000
--- a/BDInfo/TSStreamClipFile.cs
+++ /dev/null
@@ -1,244 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-#undef DEBUG
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text;
-using MediaBrowser.Model.IO;
-
-namespace BDInfo
-{
- public class TSStreamClipFile
- {
- public FileSystemMetadata FileInfo = null;
- public string FileType = null;
- public bool IsValid = false;
- public string Name = null;
-
- public Dictionary Streams =
- new Dictionary();
-
- public TSStreamClipFile(FileSystemMetadata fileInfo)
- {
- FileInfo = fileInfo;
- Name = fileInfo.Name.ToUpper();
- }
-
- public void Scan()
- {
- Stream fileStream = null;
- BinaryReader fileReader = null;
-
- try
- {
-#if DEBUG
- Debug.WriteLine(string.Format(
- "Scanning {0}...", Name));
-#endif
- Streams.Clear();
-
- fileStream = File.OpenRead(FileInfo.FullName);
- fileReader = new BinaryReader(fileStream);
-
- byte[] data = new byte[fileStream.Length];
- fileReader.Read(data, 0, data.Length);
-
- byte[] fileType = new byte[8];
- Array.Copy(data, 0, fileType, 0, fileType.Length);
-
- FileType = Encoding.ASCII.GetString(fileType, 0, fileType.Length);
- if (FileType != "HDMV0100" &&
- FileType != "HDMV0200")
- {
- throw new Exception(string.Format(
- "Clip info file {0} has an unknown file type {1}.",
- FileInfo.Name, FileType));
- }
-#if DEBUG
- Debug.WriteLine(string.Format(
- "\tFileType: {0}", FileType));
-#endif
- int clipIndex =
- ((int)data[12] << 24) +
- ((int)data[13] << 16) +
- ((int)data[14] << 8) +
- ((int)data[15]);
-
- int clipLength =
- ((int)data[clipIndex] << 24) +
- ((int)data[clipIndex + 1] << 16) +
- ((int)data[clipIndex + 2] << 8) +
- ((int)data[clipIndex + 3]);
-
- byte[] clipData = new byte[clipLength];
- Array.Copy(data, clipIndex + 4, clipData, 0, clipData.Length);
-
- int streamCount = clipData[8];
-#if DEBUG
- Debug.WriteLine(string.Format(
- "\tStreamCount: {0}", streamCount));
-#endif
- int streamOffset = 10;
- for (int streamIndex = 0;
- streamIndex < streamCount;
- streamIndex++)
- {
- TSStream stream = null;
-
- ushort PID = (ushort)
- ((clipData[streamOffset] << 8) +
- clipData[streamOffset + 1]);
-
- streamOffset += 2;
-
- var streamType = (TSStreamType)
- clipData[streamOffset + 1];
- switch (streamType)
- {
- case TSStreamType.MVC_VIDEO:
- // TODO
- break;
-
- case TSStreamType.AVC_VIDEO:
- case TSStreamType.MPEG1_VIDEO:
- case TSStreamType.MPEG2_VIDEO:
- case TSStreamType.VC1_VIDEO:
- {
- var videoFormat = (TSVideoFormat)
- (clipData[streamOffset + 2] >> 4);
- var frameRate = (TSFrameRate)
- (clipData[streamOffset + 2] & 0xF);
- var aspectRatio = (TSAspectRatio)
- (clipData[streamOffset + 3] >> 4);
-
- stream = new TSVideoStream();
- ((TSVideoStream)stream).VideoFormat = videoFormat;
- ((TSVideoStream)stream).AspectRatio = aspectRatio;
- ((TSVideoStream)stream).FrameRate = frameRate;
-#if DEBUG
- Debug.WriteLine(string.Format(
- "\t{0} {1} {2} {3} {4}",
- PID,
- streamType,
- videoFormat,
- frameRate,
- aspectRatio));
-#endif
- }
- break;
-
- case TSStreamType.AC3_AUDIO:
- case TSStreamType.AC3_PLUS_AUDIO:
- case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
- case TSStreamType.AC3_TRUE_HD_AUDIO:
- case TSStreamType.DTS_AUDIO:
- case TSStreamType.DTS_HD_AUDIO:
- case TSStreamType.DTS_HD_MASTER_AUDIO:
- case TSStreamType.DTS_HD_SECONDARY_AUDIO:
- case TSStreamType.LPCM_AUDIO:
- case TSStreamType.MPEG1_AUDIO:
- case TSStreamType.MPEG2_AUDIO:
- {
- byte[] languageBytes = new byte[3];
- Array.Copy(clipData, streamOffset + 3,
- languageBytes, 0, languageBytes.Length);
- string languageCode = Encoding.ASCII.GetString(languageBytes, 0, languageBytes.Length);
-
- var channelLayout = (TSChannelLayout)
- (clipData[streamOffset + 2] >> 4);
- var sampleRate = (TSSampleRate)
- (clipData[streamOffset + 2] & 0xF);
-
- stream = new TSAudioStream();
- ((TSAudioStream)stream).LanguageCode = languageCode;
- ((TSAudioStream)stream).ChannelLayout = channelLayout;
- ((TSAudioStream)stream).SampleRate = TSAudioStream.ConvertSampleRate(sampleRate);
- ((TSAudioStream)stream).LanguageCode = languageCode;
-#if DEBUG
- Debug.WriteLine(string.Format(
- "\t{0} {1} {2} {3} {4}",
- PID,
- streamType,
- languageCode,
- channelLayout,
- sampleRate));
-#endif
- }
- break;
-
- case TSStreamType.INTERACTIVE_GRAPHICS:
- case TSStreamType.PRESENTATION_GRAPHICS:
- {
- byte[] languageBytes = new byte[3];
- Array.Copy(clipData, streamOffset + 2,
- languageBytes, 0, languageBytes.Length);
- string languageCode = Encoding.ASCII.GetString(languageBytes, 0, languageBytes.Length);
-
- stream = new TSGraphicsStream();
- stream.LanguageCode = languageCode;
-#if DEBUG
- Debug.WriteLine(string.Format(
- "\t{0} {1} {2}",
- PID,
- streamType,
- languageCode));
-#endif
- }
- break;
-
- case TSStreamType.SUBTITLE:
- {
- byte[] languageBytes = new byte[3];
- Array.Copy(clipData, streamOffset + 3,
- languageBytes, 0, languageBytes.Length);
- string languageCode = Encoding.ASCII.GetString(languageBytes, 0, languageBytes.Length);
-#if DEBUG
- Debug.WriteLine(string.Format(
- "\t{0} {1} {2}",
- PID,
- streamType,
- languageCode));
-#endif
- stream = new TSTextStream();
- stream.LanguageCode = languageCode;
- }
- break;
- }
-
- if (stream != null)
- {
- stream.PID = PID;
- stream.StreamType = streamType;
- Streams.Add(PID, stream);
- }
-
- streamOffset += clipData[streamOffset] + 1;
- }
- IsValid = true;
- }
- finally
- {
- if (fileReader != null) fileReader.Dispose();
- if (fileStream != null) fileStream.Dispose();
- }
- }
- }
-}
diff --git a/BDInfo/TSStreamFile.cs b/BDInfo/TSStreamFile.cs
deleted file mode 100644
index ecf6609e2e..0000000000
--- a/BDInfo/TSStreamFile.cs
+++ /dev/null
@@ -1,1555 +0,0 @@
-//============================================================================
-// BDInfo - Blu-ray Video and Audio Analysis Tool
-// Copyright © 2010 Cinema Squid
-//
-// This library is free software; you can redistribute it and/or
-// modify it under the terms of the GNU Lesser General Public
-// License as published by the Free Software Foundation; either
-// version 2.1 of the License, or (at your option) any later version.
-//
-// This library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-// Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public
-// License along with this library; if not, write to the Free Software
-// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-//=============================================================================
-
-#undef DEBUG
-using System;
-using System.Collections.Generic;
-using System.IO;
-using MediaBrowser.Model.IO;
-
-namespace BDInfo
-{
- public class TSStreamState
- {
- public ulong TransferCount = 0;
-
- public string StreamTag = null;
-
- public ulong TotalPackets = 0;
- public ulong WindowPackets = 0;
-
- public ulong TotalBytes = 0;
- public ulong WindowBytes = 0;
-
- public long PeakTransferLength = 0;
- public long PeakTransferRate = 0;
-
- public double TransferMarker = 0;
- public double TransferInterval = 0;
-
- public TSStreamBuffer StreamBuffer = new TSStreamBuffer();
-
- public uint Parse = 0;
- public bool TransferState = false;
- public int TransferLength = 0;
- public int PacketLength = 0;
- public byte PacketLengthParse = 0;
- public byte PacketParse = 0;
-
- public byte PTSParse = 0;
- public ulong PTS = 0;
- public ulong PTSTemp = 0;
- public ulong PTSLast = 0;
- public ulong PTSPrev = 0;
- public ulong PTSDiff = 0;
- public ulong PTSCount = 0;
- public ulong PTSTransfer = 0;
-
- public byte DTSParse = 0;
- public ulong DTSTemp = 0;
- public ulong DTSPrev = 0;
-
- public byte PESHeaderLength = 0;
- public byte PESHeaderFlags = 0;
-#if DEBUG
- public byte PESHeaderIndex = 0;
- public byte[] PESHeader = new byte[256 + 9];
-#endif
- }
-
- public class TSPacketParser
- {
- public bool SyncState = false;
- public byte TimeCodeParse = 4;
- public byte PacketLength = 0;
- public byte HeaderParse = 0;
-
- public uint TimeCode;
- public byte TransportErrorIndicator;
- public byte PayloadUnitStartIndicator;
- public byte TransportPriority;
- public ushort PID;
- public byte TransportScramblingControl;
- public byte AdaptionFieldControl;
-
- public bool AdaptionFieldState = false;
- public byte AdaptionFieldParse = 0;
- public byte AdaptionFieldLength = 0;
-
- public ushort PCRPID = 0xFFFF;
- public byte PCRParse = 0;
- public ulong PreviousPCR = 0;
- public ulong PCR = 0;
- public ulong PCRCount = 0;
- public ulong PTSFirst = ulong.MaxValue;
- public ulong PTSLast = ulong.MinValue;
- public ulong PTSDiff = 0;
-
- public byte[] PAT = new byte[1024];
- public bool PATSectionStart = false;
- public byte PATPointerField = 0;
- public uint PATOffset = 0;
- public byte PATSectionLengthParse = 0;
- public ushort PATSectionLength = 0;
- public uint PATSectionParse = 0;
- public bool PATTransferState = false;
- public byte PATSectionNumber = 0;
- public byte PATLastSectionNumber = 0;
-
- public ushort TransportStreamId = 0xFFFF;
-
- public List PMTProgramDescriptors = new List();
- public ushort PMTPID = 0xFFFF;
- public Dictionary PMT = new Dictionary();
- public bool PMTSectionStart = false;
- public ushort PMTProgramInfoLength = 0;
- public byte PMTProgramDescriptor = 0;
- public byte PMTProgramDescriptorLengthParse = 0;
- public byte PMTProgramDescriptorLength = 0;
- public ushort PMTStreamInfoLength = 0;
- public uint PMTStreamDescriptorLengthParse = 0;
- public uint PMTStreamDescriptorLength = 0;
- public byte PMTPointerField = 0;
- public uint PMTOffset = 0;
- public uint PMTSectionLengthParse = 0;
- public ushort PMTSectionLength = 0;
- public uint PMTSectionParse = 0;
- public bool PMTTransferState = false;
- public byte PMTSectionNumber = 0;
- public byte PMTLastSectionNumber = 0;
-
- public byte PMTTemp = 0;
-
- public TSStream Stream = null;
- public TSStreamState StreamState = null;
-
- public ulong TotalPackets = 0;
- }
-
- public class TSStreamDiagnostics
- {
- public ulong Bytes = 0;
- public ulong Packets = 0;
- public double Marker = 0;
- public double Interval = 0;
- public string Tag = null;
- }
-
- public class TSStreamFile
- {
- public FileSystemMetadata FileInfo = null;
- public string Name = null;
- public long Size = 0;
- public double Length = 0;
-
- public TSInterleavedFile InterleavedFile = null;
-
- private Dictionary StreamStates =
- new Dictionary();
-
- public Dictionary Streams =
- new Dictionary();
-
- public Dictionary> StreamDiagnostics =
- new Dictionary>();
-
- private List Playlists = null;
-
- private readonly IFileSystem _fileSystem;
-
- public TSStreamFile(FileSystemMetadata fileInfo, IFileSystem fileSystem)
- {
- FileInfo = fileInfo;
- _fileSystem = fileSystem;
- Name = fileInfo.Name.ToUpper();
- }
-
- public string DisplayName
- {
- get
- {
- if (BDInfoSettings.EnableSSIF &&
- InterleavedFile != null)
- {
- return InterleavedFile.Name;
- }
- return Name;
- }
- }
-
- private bool ScanStream(
- TSStream stream,
- TSStreamState streamState,
- TSStreamBuffer buffer)
- {
- streamState.StreamTag = null;
-
- long bitrate = 0;
- if (stream.IsAudioStream &&
- streamState.PTSTransfer > 0)
- {
- bitrate = (long)Math.Round(
- (buffer.TransferLength * 8.0) /
- ((double)streamState.PTSTransfer / 90000));
-
- if (bitrate > streamState.PeakTransferRate)
- {
- streamState.PeakTransferRate = bitrate;
- }
- }
- if (buffer.TransferLength > streamState.PeakTransferLength)
- {
- streamState.PeakTransferLength = buffer.TransferLength;
- }
-
- buffer.BeginRead();
- switch (stream.StreamType)
- {
- case TSStreamType.MPEG2_VIDEO:
- TSCodecMPEG2.Scan(
- (TSVideoStream)stream, buffer, ref streamState.StreamTag);
- break;
-
- case TSStreamType.AVC_VIDEO:
- TSCodecAVC.Scan(
- (TSVideoStream)stream, buffer, ref streamState.StreamTag);
- break;
-
- case TSStreamType.MVC_VIDEO:
- TSCodecMVC.Scan(
- (TSVideoStream)stream, buffer, ref streamState.StreamTag);
- break;
-
- case TSStreamType.VC1_VIDEO:
- TSCodecVC1.Scan(
- (TSVideoStream)stream, buffer, ref streamState.StreamTag);
- break;
-
- case TSStreamType.AC3_AUDIO:
- TSCodecAC3.Scan(
- (TSAudioStream)stream, buffer, ref streamState.StreamTag);
- break;
-
- case TSStreamType.AC3_PLUS_AUDIO:
- case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
- TSCodecAC3.Scan(
- (TSAudioStream)stream, buffer, ref streamState.StreamTag);
- break;
-
- case TSStreamType.AC3_TRUE_HD_AUDIO:
- TSCodecTrueHD.Scan(
- (TSAudioStream)stream, buffer, ref streamState.StreamTag);
- break;
-
- case TSStreamType.LPCM_AUDIO:
- TSCodecLPCM.Scan(
- (TSAudioStream)stream, buffer, ref streamState.StreamTag);
- break;
-
- case TSStreamType.DTS_AUDIO:
- TSCodecDTS.Scan(
- (TSAudioStream)stream, buffer, bitrate, ref streamState.StreamTag);
- break;
-
- case TSStreamType.DTS_HD_AUDIO:
- case TSStreamType.DTS_HD_MASTER_AUDIO:
- case TSStreamType.DTS_HD_SECONDARY_AUDIO:
- TSCodecDTSHD.Scan(
- (TSAudioStream)stream, buffer, bitrate, ref streamState.StreamTag);
- break;
-
- default:
- stream.IsInitialized = true;
- break;
- }
- buffer.EndRead();
- streamState.StreamBuffer.Reset();
-
- bool isAVC = false;
- bool isMVC = false;
- foreach (var finishedStream in Streams.Values)
- {
- if (!finishedStream.IsInitialized)
- {
- return false;
- }
- if (finishedStream.StreamType == TSStreamType.AVC_VIDEO)
- {
- isAVC = true;
- }
- if (finishedStream.StreamType == TSStreamType.MVC_VIDEO)
- {
- isMVC = true;
- }
- }
- if (isMVC && !isAVC)
- {
- return false;
- }
- return true;
- }
-
- private void UpdateStreamBitrates(
- ushort PTSPID,
- ulong PTS,
- ulong PTSDiff)
- {
- if (Playlists == null) return;
-
- foreach (ushort PID in StreamStates.Keys)
- {
- if (Streams.ContainsKey(PID) &&
- Streams[PID].IsVideoStream &&
- PID != PTSPID)
- {
- continue;
- }
- if (StreamStates[PID].WindowPackets == 0)
- {
- continue;
- }
- UpdateStreamBitrate(PID, PTSPID, PTS, PTSDiff);
- }
-
- foreach (var playlist in Playlists)
- {
- double packetSeconds = 0;
- foreach (var clip in playlist.StreamClips)
- {
- if (clip.AngleIndex == 0)
- {
- packetSeconds += clip.PacketSeconds;
- }
- }
- if (packetSeconds > 0)
- {
- foreach (var playlistStream in playlist.SortedStreams)
- {
- if (playlistStream.IsVBR)
- {
- playlistStream.BitRate = (long)Math.Round(
- ((playlistStream.PayloadBytes * 8.0) / packetSeconds));
-
- if (playlistStream.StreamType == TSStreamType.AC3_TRUE_HD_AUDIO &&
- ((TSAudioStream)playlistStream).CoreStream != null)
- {
- playlistStream.BitRate -=
- ((TSAudioStream)playlistStream).CoreStream.BitRate;
- }
- }
- }
- }
- }
- }
-
- private void UpdateStreamBitrate(
- ushort PID,
- ushort PTSPID,
- ulong PTS,
- ulong PTSDiff)
- {
- if (Playlists == null) return;
-
- var streamState = StreamStates[PID];
- double streamTime = (double)PTS / 90000;
- double streamInterval = (double)PTSDiff / 90000;
- double streamOffset = streamTime + streamInterval;
-
- foreach (var playlist in Playlists)
- {
- foreach (var clip in playlist.StreamClips)
- {
- if (clip.Name != this.Name) continue;
-
- if (streamTime == 0 ||
- (streamTime >= clip.TimeIn &&
- streamTime <= clip.TimeOut))
- {
- clip.PayloadBytes += streamState.WindowBytes;
- clip.PacketCount += streamState.WindowPackets;
-
- if (streamOffset > clip.TimeIn &&
- streamOffset - clip.TimeIn > clip.PacketSeconds)
- {
- clip.PacketSeconds = streamOffset - clip.TimeIn;
- }
-
- var playlistStreams = playlist.Streams;
- if (clip.AngleIndex > 0 &&
- clip.AngleIndex < playlist.AngleStreams.Count + 1)
- {
- playlistStreams = playlist.AngleStreams[clip.AngleIndex - 1];
- }
- if (playlistStreams.ContainsKey(PID))
- {
- var stream = playlistStreams[PID];
-
- stream.PayloadBytes += streamState.WindowBytes;
- stream.PacketCount += streamState.WindowPackets;
-
- if (stream.IsVideoStream)
- {
- stream.PacketSeconds += streamInterval;
-
- stream.ActiveBitRate = (long)Math.Round(
- ((stream.PayloadBytes * 8.0) /
- stream.PacketSeconds));
- }
-
- if (stream.StreamType == TSStreamType.AC3_TRUE_HD_AUDIO &&
- ((TSAudioStream)stream).CoreStream != null)
- {
- stream.ActiveBitRate -=
- ((TSAudioStream)stream).CoreStream.BitRate;
- }
- }
- }
- }
- }
-
- if (Streams.ContainsKey(PID))
- {
- var stream = Streams[PID];
- stream.PayloadBytes += streamState.WindowBytes;
- stream.PacketCount += streamState.WindowPackets;
-
- if (stream.IsVideoStream)
- {
- var diag = new TSStreamDiagnostics();
- diag.Marker = (double)PTS / 90000;
- diag.Interval = (double)PTSDiff / 90000;
- diag.Bytes = streamState.WindowBytes;
- diag.Packets = streamState.WindowPackets;
- diag.Tag = streamState.StreamTag;
- StreamDiagnostics[PID].Add(diag);
-
- stream.PacketSeconds += streamInterval;
- }
- }
- streamState.WindowPackets = 0;
- streamState.WindowBytes = 0;
- }
-
- public void Scan(List playlists, bool isFullScan)
- {
- if (playlists == null || playlists.Count == 0)
- {
- return;
- }
-
- Playlists = playlists;
- int dataSize = 16384;
- Stream fileStream = null;
- try
- {
- string fileName;
- if (BDInfoSettings.EnableSSIF &&
- InterleavedFile != null)
- {
- fileName = InterleavedFile.FileInfo.FullName;
- }
- else
- {
- fileName = FileInfo.FullName;
- }
- fileStream = _fileSystem.GetFileStream(
- fileName,
- FileOpenMode.Open,
- FileAccessMode.Read,
- FileShareMode.Read,
- false);
-
- Size = 0;
- Length = 0;
-
- Streams.Clear();
- StreamStates.Clear();
- StreamDiagnostics.Clear();
-
- var parser =
- new TSPacketParser();
-
- long fileLength = (uint)fileStream.Length;
- byte[] buffer = new byte[dataSize];
- int bufferLength = 0;
- while ((bufferLength =
- fileStream.Read(buffer, 0, buffer.Length)) > 0)
- {
- int offset = 0;
- for (int i = 0; i < bufferLength; i++)
- {
- if (parser.SyncState == false)
- {
- if (parser.TimeCodeParse > 0)
- {
- parser.TimeCodeParse--;
- switch (parser.TimeCodeParse)
- {
- case 3:
- parser.TimeCode = 0;
- parser.TimeCode |=
- ((uint)buffer[i] & 0x3F) << 24;
- break;
- case 2:
- parser.TimeCode |=
- ((uint)buffer[i] & 0xFF) << 16;
- break;
- case 1:
- parser.TimeCode |=
- ((uint)buffer[i] & 0xFF) << 8;
- break;
- case 0:
- parser.TimeCode |=
- ((uint)buffer[i] & 0xFF);
- break;
- }
- }
- else if (buffer[i] == 0x47)
- {
- parser.SyncState = true;
- parser.PacketLength = 187;
- parser.TimeCodeParse = 4;
- parser.HeaderParse = 3;
- }
- }
- else if (parser.HeaderParse > 0)
- {
- parser.PacketLength--;
- parser.HeaderParse--;
-
- switch (parser.HeaderParse)
- {
- case 2:
- {
- parser.TransportErrorIndicator =
- (byte)((buffer[i] >> 7) & 0x1);
- parser.PayloadUnitStartIndicator =
- (byte)((buffer[i] >> 6) & 0x1);
- parser.TransportPriority =
- (byte)((buffer[i] >> 5) & 0x1);
- parser.PID =
- (ushort)((buffer[i] & 0x1f) << 8);
- }
- break;
-
- case 1:
- {
- parser.PID |= (ushort)buffer[i];
- if (Streams.ContainsKey(parser.PID))
- {
- parser.Stream = Streams[parser.PID];
- }
- else
- {
- parser.Stream = null;
- }
- if (!StreamStates.ContainsKey(parser.PID))
- {
- StreamStates[parser.PID] = new TSStreamState();
- }
- parser.StreamState = StreamStates[parser.PID];
- parser.StreamState.TotalPackets++;
- parser.StreamState.WindowPackets++;
- parser.TotalPackets++;
- }
- break;
-
- case 0:
- {
- parser.TransportScramblingControl =
- (byte)((buffer[i] >> 6) & 0x3);
- parser.AdaptionFieldControl =
- (byte)((buffer[i] >> 4) & 0x3);
-
- if ((parser.AdaptionFieldControl & 0x2) == 0x2)
- {
- parser.AdaptionFieldState = true;
- }
- if (parser.PayloadUnitStartIndicator == 1)
- {
- if (parser.PID == 0)
- {
- parser.PATSectionStart = true;
- }
- else if (parser.PID == parser.PMTPID)
- {
- parser.PMTSectionStart = true;
- }
- else if (parser.StreamState != null &&
- parser.StreamState.TransferState)
- {
- parser.StreamState.TransferState = false;
- parser.StreamState.TransferCount++;
-
- bool isFinished = ScanStream(
- parser.Stream,
- parser.StreamState,
- parser.StreamState.StreamBuffer);
-
- if (!isFullScan && isFinished)
- {
- return;
- }
- }
- }
- }
- break;
- }
- }
- else if (parser.AdaptionFieldState)
- {
- parser.PacketLength--;
- parser.AdaptionFieldParse = buffer[i];
- parser.AdaptionFieldLength = buffer[i];
- parser.AdaptionFieldState = false;
- }
- else if (parser.AdaptionFieldParse > 0)
- {
- parser.PacketLength--;
- parser.AdaptionFieldParse--;
- if ((parser.AdaptionFieldLength - parser.AdaptionFieldParse) == 1)
- {
- if ((buffer[i] & 0x10) == 0x10)
- {
- parser.PCRParse = 6;
- parser.PCR = 0;
- }
- }
- else if (parser.PCRParse > 0)
- {
- parser.PCRParse--;
- parser.PCR = (parser.PCR << 8) + (ulong)buffer[i];
- if (parser.PCRParse == 0)
- {
- parser.PreviousPCR = parser.PCR;
- parser.PCR = (parser.PCR & 0x1FF) +
- ((parser.PCR >> 15) * 300);
- }
- parser.PCRCount++;
- }
- if (parser.PacketLength == 0)
- {
- parser.SyncState = false;
- }
- }
- else if (parser.PID == 0)
- {
- if (parser.PATTransferState)
- {
- if ((bufferLength - i) > parser.PATSectionLength)
- {
- offset = parser.PATSectionLength;
- }
- else
- {
- offset = (bufferLength - i);
- }
- if (parser.PacketLength <= offset)
- {
- offset = parser.PacketLength;
- }
-
- for (int k = 0; k < offset; k++)
- {
- parser.PAT[parser.PATOffset++] = buffer[i++];
- parser.PATSectionLength--;
- parser.PacketLength--;
- }
- --i;
-
- if (parser.PATSectionLength == 0)
- {
- parser.PATTransferState = false;
- if (parser.PATSectionNumber == parser.PATLastSectionNumber)
- {
- for (int k = 0; k < (parser.PATOffset - 4); k += 4)
- {
- uint programNumber = (uint)
- ((parser.PAT[k] << 8) +
- parser.PAT[k + 1]);
-
- ushort programPID = (ushort)
- (((parser.PAT[k + 2] & 0x1F) << 8) +
- parser.PAT[k + 3]);
-
- if (programNumber == 1)
- {
- parser.PMTPID = programPID;
- }
- }
- }
- }
- }
- else
- {
- --parser.PacketLength;
- if (parser.PATSectionStart)
- {
- parser.PATPointerField = buffer[i];
- if (parser.PATPointerField == 0)
- {
- parser.PATSectionLengthParse = 3;
- }
- parser.PATSectionStart = false;
- }
- else if (parser.PATPointerField > 0)
- {
- --parser.PATPointerField;
- if (parser.PATPointerField == 0)
- {
- parser.PATSectionLengthParse = 3;
- }
- }
- else if (parser.PATSectionLengthParse > 0)
- {
- --parser.PATSectionLengthParse;
- switch (parser.PATSectionLengthParse)
- {
- case 2:
- break;
- case 1:
- parser.PATSectionLength = (ushort)
- ((buffer[i] & 0xF) << 8);
- break;
- case 0:
- parser.PATSectionLength |= buffer[i];
- if (parser.PATSectionLength > 1021)
- {
- parser.PATSectionLength = 0;
- }
- else
- {
- parser.PATSectionParse = 5;
- }
- break;
- }
- }
- else if (parser.PATSectionParse > 0)
- {
- --parser.PATSectionLength;
- --parser.PATSectionParse;
-
- switch (parser.PATSectionParse)
- {
- case 4:
- parser.TransportStreamId = (ushort)
- (buffer[i] << 8);
- break;
- case 3:
- parser.TransportStreamId |= buffer[i];
- break;
- case 2:
- break;
- case 1:
- parser.PATSectionNumber = buffer[i];
- if (parser.PATSectionNumber == 0)
- {
- parser.PATOffset = 0;
- }
- break;
- case 0:
- parser.PATLastSectionNumber = buffer[i];
- parser.PATTransferState = true;
- break;
- }
- }
- }
- if (parser.PacketLength == 0)
- {
- parser.SyncState = false;
- }
- }
- else if (parser.PID == parser.PMTPID)
- {
- if (parser.PMTTransferState)
- {
- if ((bufferLength - i) >= parser.PMTSectionLength)
- {
- offset = parser.PMTSectionLength;
- }
- else
- {
- offset = (bufferLength - i);
- }
- if (parser.PacketLength <= offset)
- {
- offset = parser.PacketLength;
- }
- if (!parser.PMT.ContainsKey(parser.PID))
- {
- parser.PMT[parser.PID] = new byte[1024];
- }
-
- byte[] PMT = parser.PMT[parser.PID];
- for (int k = 0; k < offset; k++)
- {
- PMT[parser.PMTOffset++] = buffer[i++];
- --parser.PMTSectionLength;
- --parser.PacketLength;
- }
- --i;
-
- if (parser.PMTSectionLength == 0)
- {
- parser.PMTTransferState = false;
- if (parser.PMTSectionNumber == parser.PMTLastSectionNumber)
- {
- //Console.WriteLine("PMT Start: " + parser.PMTTemp);
- try
- {
- for (int k = 0; k < (parser.PMTOffset - 4); k += 5)
- {
- byte streamType = PMT[k];
-
- ushort streamPID = (ushort)
- (((PMT[k + 1] & 0x1F) << 8) +
- PMT[k + 2]);
-
- ushort streamInfoLength = (ushort)
- (((PMT[k + 3] & 0xF) << 8) +
- PMT[k + 4]);
-
- /*
- if (streamInfoLength == 2)
- {
- // TODO: Cleanup
- //streamInfoLength = 0;
- }
-
- Console.WriteLine(string.Format(
- "Type: {0} PID: {1} Length: {2}",
- streamType, streamPID, streamInfoLength));
- */
-
- if (!Streams.ContainsKey(streamPID))
- {
- var streamDescriptors =
- new List();
-
- /*
- * TODO: Getting bad streamInfoLength
- if (streamInfoLength > 0)
- {
- for (int d = 0; d < streamInfoLength; d++)
- {
- byte name = PMT[k + d + 5];
- byte length = PMT[k + d + 6];
- TSDescriptor descriptor =
- new TSDescriptor(name, length);
- for (int v = 0; v < length; v++)
- {
- descriptor.Value[v] =
- PMT[k + d + v + 7];
- }
- streamDescriptors.Add(descriptor);
- d += (length + 1);
- }
- }
- */
- CreateStream(streamPID, streamType, streamDescriptors);
- }
- k += streamInfoLength;
- }
- }
- catch
- {
- // TODO
- //Console.WriteLine(ex.Message);
- }
- }
- }
- }
- else
- {
- --parser.PacketLength;
- if (parser.PMTSectionStart)
- {
- parser.PMTPointerField = buffer[i];
- if (parser.PMTPointerField == 0)
- {
- parser.PMTSectionLengthParse = 3;
- }
- parser.PMTSectionStart = false;
- }
- else if (parser.PMTPointerField > 0)
- {
- --parser.PMTPointerField;
- if (parser.PMTPointerField == 0)
- {
- parser.PMTSectionLengthParse = 3;
- }
- }
- else if (parser.PMTSectionLengthParse > 0)
- {
- --parser.PMTSectionLengthParse;
- switch (parser.PMTSectionLengthParse)
- {
- case 2:
- if (buffer[i] != 0x2)
- {
- parser.PMTSectionLengthParse = 0;
- }
- break;
- case 1:
- parser.PMTSectionLength = (ushort)
- ((buffer[i] & 0xF) << 8);
- break;
- case 0:
- parser.PMTSectionLength |= buffer[i];
- if (parser.PMTSectionLength > 1021)
- {
- parser.PMTSectionLength = 0;
- }
- else
- {
- parser.PMTSectionParse = 9;
- }
- break;
- }
- }
- else if (parser.PMTSectionParse > 0)
- {
- --parser.PMTSectionLength;
- --parser.PMTSectionParse;
-
- switch (parser.PMTSectionParse)
- {
- case 8:
- case 7:
- break;
- case 6:
- parser.PMTTemp = buffer[i];
- break;
- case 5:
- parser.PMTSectionNumber = buffer[i];
- if (parser.PMTSectionNumber == 0)
- {
- parser.PMTOffset = 0;
- }
- break;
- case 4:
- parser.PMTLastSectionNumber = buffer[i];
- break;
- case 3:
- parser.PCRPID = (ushort)
- ((buffer[i] & 0x1F) << 8);
- break;
- case 2:
- parser.PCRPID |= buffer[i];
- break;
- case 1:
- parser.PMTProgramInfoLength = (ushort)
- ((buffer[i] & 0xF) << 8);
- break;
- case 0:
- parser.PMTProgramInfoLength |= buffer[i];
- if (parser.PMTProgramInfoLength == 0)
- {
- parser.PMTTransferState = true;
- }
- else
- {
- parser.PMTProgramDescriptorLengthParse = 2;
- }
- break;
- }
- }
- else if (parser.PMTProgramInfoLength > 0)
- {
- --parser.PMTSectionLength;
- --parser.PMTProgramInfoLength;
-
- if (parser.PMTProgramDescriptorLengthParse > 0)
- {
- --parser.PMTProgramDescriptorLengthParse;
- switch (parser.PMTProgramDescriptorLengthParse)
- {
- case 1:
- parser.PMTProgramDescriptor = buffer[i];
- break;
- case 0:
- parser.PMTProgramDescriptorLength = buffer[i];
- parser.PMTProgramDescriptors.Add(
- new TSDescriptor(
- parser.PMTProgramDescriptor,
- parser.PMTProgramDescriptorLength));
- break;
- }
- }
- else if (parser.PMTProgramDescriptorLength > 0)
- {
- --parser.PMTProgramDescriptorLength;
-
- var descriptor = parser.PMTProgramDescriptors[
- parser.PMTProgramDescriptors.Count - 1];
-
- int valueIndex =
- descriptor.Value.Length -
- parser.PMTProgramDescriptorLength - 1;
-
- descriptor.Value[valueIndex] = buffer[i];
-
- if (parser.PMTProgramDescriptorLength == 0 &&
- parser.PMTProgramInfoLength > 0)
- {
- parser.PMTProgramDescriptorLengthParse = 2;
- }
- }
- if (parser.PMTProgramInfoLength == 0)
- {
- parser.PMTTransferState = true;
- }
- }
- }
- if (parser.PacketLength == 0)
- {
- parser.SyncState = false;
- }
- }
- else if (parser.Stream != null &&
- parser.StreamState != null &&
- parser.TransportScramblingControl == 0)
- {
- var stream = parser.Stream;
- var streamState = parser.StreamState;
-
- streamState.Parse =
- (streamState.Parse << 8) + buffer[i];
-
- if (streamState.TransferState)
- {
- if ((bufferLength - i) >= streamState.PacketLength &&
- streamState.PacketLength > 0)
- {
- offset = streamState.PacketLength;
- }
- else
- {
- offset = (bufferLength - i);
- }
- if (parser.PacketLength <= offset)
- {
- offset = parser.PacketLength;
- }
- streamState.TransferLength = offset;
-
- if (!stream.IsInitialized ||
- stream.IsVideoStream)
- {
- streamState.StreamBuffer.Add(
- buffer, i, offset);
- }
- else
- {
- streamState.StreamBuffer.TransferLength += offset;
- }
-
- i += (int)(streamState.TransferLength - 1);
- streamState.PacketLength -= streamState.TransferLength;
- parser.PacketLength -= (byte)streamState.TransferLength;
-
- streamState.TotalBytes += (ulong)streamState.TransferLength;
- streamState.WindowBytes += (ulong)streamState.TransferLength;
-
- if (streamState.PacketLength == 0)
- {
- streamState.TransferState = false;
- streamState.TransferCount++;
- bool isFinished = ScanStream(
- stream,
- streamState,
- streamState.StreamBuffer);
-
- if (!isFullScan && isFinished)
- {
- return;
- }
- }
- }
- else
- {
- --parser.PacketLength;
-
- bool headerFound = false;
- if (stream.IsVideoStream &&
- streamState.Parse == 0x000001FD)
- {
- headerFound = true;
- }
- if (stream.IsVideoStream &&
- streamState.Parse >= 0x000001E0 &&
- streamState.Parse <= 0x000001EF)
- {
- headerFound = true;
- }
- if (stream.IsAudioStream &&
- streamState.Parse == 0x000001BD)
- {
- headerFound = true;
- }
- if (stream.IsAudioStream &&
- (streamState.Parse == 0x000001FA ||
- streamState.Parse == 0x000001FD))
- {
- headerFound = true;
- }
-
- if (!stream.IsVideoStream &&
- !stream.IsAudioStream &&
- (streamState.Parse == 0x000001FA ||
- streamState.Parse == 0x000001FD ||
- streamState.Parse == 0x000001BD ||
- (streamState.Parse >= 0x000001E0 &&
- streamState.Parse <= 0x000001EF)))
- {
- headerFound = true;
- }
-
- if (headerFound)
- {
- streamState.PacketLengthParse = 2;
-#if DEBUG
- streamState.PESHeaderIndex = 0;
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)((streamState.Parse >> 24) & 0xFF);
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)((streamState.Parse >> 16) & 0xFF);
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)((streamState.Parse >> 8) & 0xFF);
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- }
- else if (streamState.PacketLengthParse > 0)
- {
- --streamState.PacketLengthParse;
- switch (streamState.PacketLengthParse)
- {
- case 1:
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
-
- case 0:
- streamState.PacketLength =
- (int)(streamState.Parse & 0xFFFF);
- streamState.PacketParse = 3;
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
- }
- }
- else if (streamState.PacketParse > 0)
- {
- --streamState.PacketLength;
- --streamState.PacketParse;
-
- switch (streamState.PacketParse)
- {
- case 2:
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
-
- case 1:
- streamState.PESHeaderFlags =
- (byte)(streamState.Parse & 0xFF);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
-
- case 0:
- streamState.PESHeaderLength =
- (byte)(streamState.Parse & 0xFF);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- if ((streamState.PESHeaderFlags & 0xC0) == 0x80)
- {
- streamState.PTSParse = 5;
- }
- else if ((streamState.PESHeaderFlags & 0xC0) == 0xC0)
- {
- streamState.DTSParse = 10;
- }
- if (streamState.PESHeaderLength == 0)
- {
- streamState.TransferState = true;
- }
- break;
- }
- }
- else if (streamState.PTSParse > 0)
- {
- --streamState.PacketLength;
- --streamState.PESHeaderLength;
- --streamState.PTSParse;
-
- switch (streamState.PTSParse)
- {
- case 4:
- streamState.PTSTemp =
- ((streamState.Parse & 0xE) << 29);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xff);
-#endif
- break;
-
- case 3:
- streamState.PTSTemp |=
- ((streamState.Parse & 0xFF) << 22);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
-
- case 2:
- streamState.PTSTemp |=
- ((streamState.Parse & 0xFE) << 14);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
-
- case 1:
- streamState.PTSTemp |=
- ((streamState.Parse & 0xFF) << 7);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
-
- case 0:
- streamState.PTSTemp |=
- ((streamState.Parse & 0xFE) >> 1);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xff);
-#endif
- streamState.PTS = streamState.PTSTemp;
-
- if (streamState.PTS > streamState.PTSLast)
- {
- if (streamState.PTSLast > 0)
- {
- streamState.PTSTransfer = (streamState.PTS - streamState.PTSLast);
- }
- streamState.PTSLast = streamState.PTS;
- }
-
- streamState.PTSDiff = streamState.PTS - streamState.DTSPrev;
-
- if (streamState.PTSCount > 0 &&
- stream.IsVideoStream)
- {
- UpdateStreamBitrates(stream.PID, streamState.PTS, streamState.PTSDiff);
- if (streamState.DTSTemp < parser.PTSFirst)
- {
- parser.PTSFirst = streamState.DTSTemp;
- }
- if (streamState.DTSTemp > parser.PTSLast)
- {
- parser.PTSLast = streamState.DTSTemp;
- }
- Length = (double)(parser.PTSLast - parser.PTSFirst) / 90000;
- }
-
- streamState.DTSPrev = streamState.PTS;
- streamState.PTSCount++;
- if (streamState.PESHeaderLength == 0)
- {
- streamState.TransferState = true;
- }
- break;
- }
- }
- else if (streamState.DTSParse > 0)
- {
- --streamState.PacketLength;
- --streamState.PESHeaderLength;
- --streamState.DTSParse;
-
- switch (streamState.DTSParse)
- {
- case 9:
- streamState.PTSTemp =
- ((streamState.Parse & 0xE) << 29);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
-
- case 8:
- streamState.PTSTemp |=
- ((streamState.Parse & 0xFF) << 22);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
-
- case 7:
- streamState.PTSTemp |=
- ((streamState.Parse & 0xFE) << 14);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xff);
-#endif
- break;
-
- case 6:
- streamState.PTSTemp |=
- ((streamState.Parse & 0xFF) << 7);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
-
- case 5:
- streamState.PTSTemp |=
- ((streamState.Parse & 0xFE) >> 1);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xff);
-#endif
- streamState.PTS = streamState.PTSTemp;
- if (streamState.PTS > streamState.PTSLast)
- {
- streamState.PTSLast = streamState.PTS;
- }
- break;
-
- case 4:
- streamState.DTSTemp =
- ((streamState.Parse & 0xE) << 29);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xff);
-#endif
- break;
-
- case 3:
- streamState.DTSTemp |=
- ((streamState.Parse & 0xFF) << 22);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xff);
-#endif
- break;
-
- case 2:
- streamState.DTSTemp |=
- ((streamState.Parse & 0xFE) << 14);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xff);
-#endif
- break;
-
- case 1:
- streamState.DTSTemp |=
- ((streamState.Parse & 0xFF) << 7);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- break;
-
- case 0:
- streamState.DTSTemp |=
- ((streamState.Parse & 0xFE) >> 1);
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xff);
-#endif
- streamState.PTSDiff = streamState.DTSTemp - streamState.DTSPrev;
-
- if (streamState.PTSCount > 0 &&
- stream.IsVideoStream)
- {
- UpdateStreamBitrates(stream.PID, streamState.DTSTemp, streamState.PTSDiff);
- if (streamState.DTSTemp < parser.PTSFirst)
- {
- parser.PTSFirst = streamState.DTSTemp;
- }
- if (streamState.DTSTemp > parser.PTSLast)
- {
- parser.PTSLast = streamState.DTSTemp;
- }
- Length = (double)(parser.PTSLast - parser.PTSFirst) / 90000;
- }
- streamState.DTSPrev = streamState.DTSTemp;
- streamState.PTSCount++;
- if (streamState.PESHeaderLength == 0)
- {
- streamState.TransferState = true;
- }
- break;
- }
- }
- else if (streamState.PESHeaderLength > 0)
- {
- --streamState.PacketLength;
- --streamState.PESHeaderLength;
-#if DEBUG
- streamState.PESHeader[streamState.PESHeaderIndex++] =
- (byte)(streamState.Parse & 0xFF);
-#endif
- if (streamState.PESHeaderLength == 0)
- {
- streamState.TransferState = true;
- }
- }
- }
- if (parser.PacketLength == 0)
- {
- parser.SyncState = false;
- }
- }
- else
- {
- parser.PacketLength--;
- if ((bufferLength - i) >= parser.PacketLength)
- {
- i = i + parser.PacketLength;
- parser.PacketLength = 0;
- }
- else
- {
- parser.PacketLength -= (byte)((bufferLength - i) + 1);
- i = bufferLength;
- }
- if (parser.PacketLength == 0)
- {
- parser.SyncState = false;
- }
- }
- }
- Size += bufferLength;
- }
-
- ulong PTSLast = 0;
- ulong PTSDiff = 0;
- foreach (var stream in Streams.Values)
- {
- if (!stream.IsVideoStream) continue;
-
- if (StreamStates.ContainsKey(stream.PID) &&
- StreamStates[stream.PID].PTSLast > PTSLast)
- {
- PTSLast = StreamStates[stream.PID].PTSLast;
- PTSDiff = PTSLast - StreamStates[stream.PID].DTSPrev;
- }
- UpdateStreamBitrates(stream.PID, PTSLast, PTSDiff);
- }
- }
- finally
- {
- if (fileStream != null)
- {
- fileStream.Dispose();
- }
- }
- }
-
- private TSStream CreateStream(
- ushort streamPID,
- byte streamType,
- List streamDescriptors)
- {
- TSStream stream = null;
-
- switch ((TSStreamType)streamType)
- {
- case TSStreamType.MVC_VIDEO:
- case TSStreamType.AVC_VIDEO:
- case TSStreamType.MPEG1_VIDEO:
- case TSStreamType.MPEG2_VIDEO:
- case TSStreamType.VC1_VIDEO:
- {
- stream = new TSVideoStream();
- }
- break;
-
- case TSStreamType.AC3_AUDIO:
- case TSStreamType.AC3_PLUS_AUDIO:
- case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
- case TSStreamType.AC3_TRUE_HD_AUDIO:
- case TSStreamType.DTS_AUDIO:
- case TSStreamType.DTS_HD_AUDIO:
- case TSStreamType.DTS_HD_MASTER_AUDIO:
- case TSStreamType.DTS_HD_SECONDARY_AUDIO:
- case TSStreamType.LPCM_AUDIO:
- case TSStreamType.MPEG1_AUDIO:
- case TSStreamType.MPEG2_AUDIO:
- {
- stream = new TSAudioStream();
- }
- break;
-
- case TSStreamType.INTERACTIVE_GRAPHICS:
- case TSStreamType.PRESENTATION_GRAPHICS:
- {
- stream = new TSGraphicsStream();
- }
- break;
-
- case TSStreamType.SUBTITLE:
- {
- stream = new TSTextStream();
- }
- break;
-
- default:
- break;
- }
-
- if (stream != null &&
- !Streams.ContainsKey(streamPID))
- {
- stream.PID = streamPID;
- stream.StreamType = (TSStreamType)streamType;
- stream.Descriptors = streamDescriptors;
- Streams[stream.PID] = stream;
- }
- if (!StreamDiagnostics.ContainsKey(streamPID))
- {
- StreamDiagnostics[streamPID] =
- new List();
- }
-
- return stream;
- }
- }
-}
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 758202af6c..5c4a031bc1 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -1,26 +1,155 @@
# Jellyfin Contributors
- - [JoshuaBoniface](https://github.com/joshuaboniface)
- - [nvllsvm](https://github.com/nvllsvm)
- - [JustAMan](https://github.com/JustAMan)
- - [dcrdev](https://github.com/dcrdev)
- - [EraYaN](https://github.com/EraYaN)
- - [flemse](https://github.com/flemse)
+ - [97carmine](https://github.com/97carmine)
+ - [Abbe98](https://github.com/Abbe98)
+ - [agrenott](https://github.com/agrenott)
+ - [AndreCarvalho](https://github.com/AndreCarvalho)
+ - [anthonylavado](https://github.com/anthonylavado)
+ - [Artiume](https://github.com/Artiume)
+ - [AThomsen](https://github.com/AThomsen)
+ - [barongreenback](https://github.com/BaronGreenback)
+ - [barronpm](https://github.com/barronpm)
+ - [bilde2910](https://github.com/bilde2910)
- [bfayers](https://github.com/bfayers)
- - [Bond_009](https://github.com/Bond-009)
- - [AnthonyLavado](https://github.com/anthonylavado)
- - [sparky8251](https://github.com/sparky8251)
- - [LeoVerto](https://github.com/LeoVerto)
- - [grafixeyehero](https://github.com/grafixeyehero)
+ - [BnMcG](https://github.com/BnMcG)
+ - [Bond-009](https://github.com/Bond-009)
+ - [brianjmurrell](https://github.com/brianjmurrell)
+ - [bugfixin](https://github.com/bugfixin)
+ - [chaosinnovator](https://github.com/chaosinnovator)
+ - [ckcr4lyf](https://github.com/ckcr4lyf)
+ - [cocool97](https://github.com/cocool97)
+ - [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
+ - [crankdoofus](https://github.com/crankdoofus)
+ - [crobibero](https://github.com/crobibero)
+ - [cromefire](https://github.com/cromefire)
+ - [cryptobank](https://github.com/cryptobank)
- [cvium](https://github.com/cvium)
- - [wtayl0r](https://github.com/wtayl0r)
- - [TtheCreator](https://github.com/Tthecreator)
+ - [dannymichel](https://github.com/dannymichel)
+ - [DaveChild](https://github.com/DaveChild)
+ - [Delgan](https://github.com/Delgan)
+ - [dcrdev](https://github.com/dcrdev)
+ - [dhartung](https://github.com/dhartung)
+ - [dinki](https://github.com/dinki)
- [dkanada](https://github.com/dkanada)
- - [LogicalPhallacy](https://github.com/LogicalPhallacy/)
- - [RazeLighter777](https://github.com/RazeLighter777)
- - [WillWill56](https://github.com/WillWill56)
- - [Liggy](https://github.com/Liggy)
+ - [dlahoti](https://github.com/dlahoti)
+ - [dmitrylyzo](https://github.com/dmitrylyzo)
+ - [DMouse10462](https://github.com/DMouse10462)
+ - [DrPandemic](https://github.com/DrPandemic)
+ - [EraYaN](https://github.com/EraYaN)
+ - [escabe](https://github.com/escabe)
+ - [excelite](https://github.com/excelite)
+ - [fasheng](https://github.com/fasheng)
+ - [ferferga](https://github.com/ferferga)
+ - [fhriley](https://github.com/fhriley)
+ - [flemse](https://github.com/flemse)
+ - [Froghut](https://github.com/Froghut)
- [fruhnow](https://github.com/fruhnow)
+ - [geilername](https://github.com/geilername)
+ - [gnattu](https://github.com/gnattu)
+ - [GodTamIt](https://github.com/GodTamIt)
+ - [grafixeyehero](https://github.com/grafixeyehero)
+ - [h1nk](https://github.com/h1nk)
+ - [hawken93](https://github.com/hawken93)
+ - [HelloWorld017](https://github.com/HelloWorld017)
+ - [ikomhoog](https://github.com/ikomhoog)
+ - [jftuga](https://github.com/jftuga)
+ - [joern-h](https://github.com/joern-h)
+ - [joshuaboniface](https://github.com/joshuaboniface)
+ - [JustAMan](https://github.com/JustAMan)
+ - [justinfenn](https://github.com/justinfenn)
+ - [KerryRJ](https://github.com/KerryRJ)
+ - [Larvitar](https://github.com/Larvitar)
+ - [LeoVerto](https://github.com/LeoVerto)
+ - [Liggy](https://github.com/Liggy)
+ - [lmaonator](https://github.com/lmaonator)
+ - [LogicalPhallacy](https://github.com/LogicalPhallacy)
+ - [loli10K](https://github.com/loli10K)
+ - [lostmypillow](https://github.com/lostmypillow)
+ - [Lynxy](https://github.com/Lynxy)
+ - [ManfredRichthofen](https://github.com/ManfredRichthofen)
+ - [Marenz](https://github.com/Marenz)
+ - [marius-luca-87](https://github.com/marius-luca-87)
+ - [mark-monteiro](https://github.com/mark-monteiro)
+ - [Matt07211](https://github.com/Matt07211)
+ - [Maxr1998](https://github.com/Maxr1998)
+ - [mcarlton00](https://github.com/mcarlton00)
+ - [mitchfizz05](https://github.com/mitchfizz05)
+ - [MrTimscampi](https://github.com/MrTimscampi)
+ - [n8225](https://github.com/n8225)
+ - [Narfinger](https://github.com/Narfinger)
+ - [NathanPickard](https://github.com/NathanPickard)
+ - [neilsb](https://github.com/neilsb)
+ - [nevado](https://github.com/nevado)
+ - [Nickbert7](https://github.com/Nickbert7)
+ - [nvllsvm](https://github.com/nvllsvm)
+ - [nyanmisaka](https://github.com/nyanmisaka)
+ - [OancaAndrei](https://github.com/OancaAndrei)
+ - [obradovichv](https://github.com/obradovichv)
+ - [oddstr13](https://github.com/oddstr13)
+ - [orryverducci](https://github.com/orryverducci)
+ - [petermcneil](https://github.com/petermcneil)
+ - [Phlogi](https://github.com/Phlogi)
+ - [pjeanjean](https://github.com/pjeanjean)
+ - [ploughpuff](https://github.com/ploughpuff)
+ - [pR0Ps](https://github.com/pR0Ps)
+ - [PrplHaz4](https://github.com/PrplHaz4)
+ - [RazeLighter777](https://github.com/RazeLighter777)
+ - [redSpoutnik](https://github.com/redSpoutnik)
+ - [ringmatter](https://github.com/ringmatter)
+ - [ryan-hartzell](https://github.com/ryan-hartzell)
+ - [s0urcelab](https://github.com/s0urcelab)
+ - [sachk](https://github.com/sachk)
+ - [sammyrc34](https://github.com/sammyrc34)
+ - [samuel9554](https://github.com/samuel9554)
+ - [scheidleon](https://github.com/scheidleon)
+ - [sebPomme](https://github.com/sebPomme)
+ - [SegiH](https://github.com/SegiH)
+ - [SenorSmartyPants](https://github.com/SenorSmartyPants)
+ - [shemanaev](https://github.com/shemanaev)
+ - [skaro13](https://github.com/skaro13)
+ - [sl1288](https://github.com/sl1288)
+ - [Smith00101010](https://github.com/Smith00101010)
+ - [sorinyo2004](https://github.com/sorinyo2004)
+ - [sparky8251](https://github.com/sparky8251)
+ - [spookbits](https://github.com/spookbits)
+ - [ssenart](https://github.com/ssenart)
+ - [stanionascu](https://github.com/stanionascu)
+ - [stevehayles](https://github.com/stevehayles)
+ - [SuperSandro2000](https://github.com/SuperSandro2000)
+ - [tbraeutigam](https://github.com/tbraeutigam)
+ - [teacupx](https://github.com/teacupx)
+ - [Terror-Gene](https://github.com/Terror-Gene)
+ - [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu)
+ - [ThibaultNocchi](https://github.com/ThibaultNocchi)
+ - [thornbill](https://github.com/thornbill)
+ - [ThreeFive-O](https://github.com/ThreeFive-O)
+ - [TrisMcC](https://github.com/TrisMcC)
+ - [trumblejoe](https://github.com/trumblejoe)
+ - [TtheCreator](https://github.com/TtheCreator)
+ - [twinkybot](https://github.com/twinkybot)
+ - [Ullmie02](https://github.com/Ullmie02)
+ - [Unhelpful](https://github.com/Unhelpful)
+ - [viaregio](https://github.com/viaregio)
+ - [vitorsemeano](https://github.com/vitorsemeano)
+ - [voodoos](https://github.com/voodoos)
+ - [whooo](https://github.com/whooo)
+ - [WiiPlayer2](https://github.com/WiiPlayer2)
+ - [WillWill56](https://github.com/WillWill56)
+ - [wtayl0r](https://github.com/wtayl0r)
+ - [Wuerfelbecher](https://github.com/Wuerfelbecher)
+ - [Wunax](https://github.com/Wunax)
+ - [WWWesten](https://github.com/WWWesten)
+ - [WX9yMOXWId](https://github.com/WX9yMOXWId)
+ - [xosdy](https://github.com/xosdy)
+ - [XVicarious](https://github.com/XVicarious)
+ - [YouKnowBlom](https://github.com/YouKnowBlom)
+ - [KristupasSavickas](https://github.com/KristupasSavickas)
+ - [Pusta](https://github.com/pusta)
+ - [nielsvanvelzen](https://github.com/nielsvanvelzen)
+ - [skyfrk](https://github.com/skyfrk)
+ - [ianjazz246](https://github.com/ianjazz246)
+ - [peterspenler](https://github.com/peterspenler)
+ - [MBR-0001](https://github.com/MBR-0001)
# Emby Contributors
@@ -84,3 +213,6 @@
- [tikuf](https://github.com/tikuf/)
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
+ - [olsh](https://github.com/olsh)
+ - [lbenini](https://github.com/lbenini)
+ - [gnuyent](https://github.com/gnuyent)
diff --git a/Directory.Build.props b/Directory.Build.props
new file mode 100644
index 0000000000..b899999efb
--- /dev/null
+++ b/Directory.Build.props
@@ -0,0 +1,14 @@
+
+
+
+
+ enable
+ true
+ $(MSBuildThisFileDirectory)/jellyfin.ruleset
+
+
+
+ AllEnabledByDefault
+
+
+
diff --git a/Dockerfile b/Dockerfile
index 978b0d5409..e133c08193 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,27 +1,93 @@
-ARG DOTNET_VERSION=2
+# DESIGNED FOR BUILDING ON AMD64 ONLY
+#####################################
+# Requires binfm_misc registration
+# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
+ARG DOTNET_VERSION=6.0
-FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
+FROM node:lts-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 - \
+ && cd jellyfin-web-* \
+ && npm ci --no-audit --unsafe-perm \
+ && mv dist /dist
+
+FROM debian:stable-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_DRIVER_CAPABILITIES="compute,video,utility"
+
+# https://github.com/intel/compute-runtime/releases
+ARG GMMLIB_VERSION=21.2.1
+ARG IGC_VERSION=1.0.8517
+ARG NEO_VERSION=21.35.20826
+ARG LEVEL_ZERO_VERSION=1.2.20826
+
+# Install dependencies:
+# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
+# curl: healthcheck
+RUN apt-get update \
+ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https curl \
+ && wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
+ && 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-ffmpeg \
+ 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 \
+ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \
+ && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
+ && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
+ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl_${NEO_VERSION}_amd64.deb \
+ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-ocloc_${NEO_VERSION}_amd64.deb \
+ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
+ && dpkg -i *.deb \
+ && cd .. \
+ && rm -rf intel-compute-runtime \
+ && apt-get remove gnupg wget apt-transport-https -y \
+ && apt-get clean autoclean -y \
+ && apt-get autoremove -y \
+ && rm -rf /var/lib/apt/lists/* \
+ && mkdir -p /cache /config /media \
+ && chmod 777 /cache /config /media \
+ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
+
+# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+ENV LC_ALL en_US.UTF-8
+ENV 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 \
- --configuration release \
- --output /jellyfin \
- Jellyfin.Server
+# because of changes in docker and systemd we need to not build in parallel at the moment
+# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
+RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:DebugSymbols=false;DebugType=none"
+
+FROM app
+
+ENV HEALTHCHECK_URL=http://localhost:8096/health
-FROM jellyfin/ffmpeg as ffmpeg
-FROM microsoft/dotnet:${DOTNET_VERSION}-runtime
-# libfontconfig1 is required for Skia
-RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y \
- libfontconfig1 \
- && apt-get clean autoclean \
- && apt-get autoremove \
- && rm -rf /var/lib/{apt,dpkg,cache,log} \
- && mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media
-COPY --from=ffmpeg / /
COPY --from=builder /jellyfin /jellyfin
+COPY --from=web-builder /dist /jellyfin/jellyfin-web
+
EXPOSE 8096
VOLUME /cache /config /media
-ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache
+ENTRYPOINT ["./jellyfin/jellyfin", \
+ "--datadir", "/config", \
+ "--cachedir", "/cache", \
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
+
+HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
+ CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1
diff --git a/Dockerfile.arm b/Dockerfile.arm
index 9d1c30619b..a46fa331df 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -1,36 +1,84 @@
+# DESIGNED FOR BUILDING ON ARM ONLY
+#####################################
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
-ARG DOTNET_VERSION=3.0
+ARG DOTNET_VERSION=6.0
+FROM node:lts-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 - \
+ && cd jellyfin-web-* \
+ && npm ci --no-audit --unsafe-perm \
+ && mv dist /dist
+
FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM alpine as qemu_extract
-COPY --from=qemu /usr/bin qemu-arm-static.tar.gz
-RUN tar -xzvf qemu-arm-static.tar.gz
+FROM arm32v7/debian:stable-slim as app
-FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
+# 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_DRIVER_CAPABILITIES="compute,video,utility"
+
+COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
+
+# curl: setup & healthcheck
+RUN apt-get update \
+ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
+ curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \
+ curl -ks https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \
+ echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \
+ echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
+ apt-get update && \
+ apt-get install --no-install-recommends --no-install-suggests -y \
+ jellyfin-ffmpeg \
+ libssl-dev \
+ libfontconfig1 \
+ libfreetype6 \
+ libomxil-bellagio0 \
+ libomxil-bellagio-bin \
+ libraspberrypi0 \
+ 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 /cache /config /media \
+ && chmod 777 /cache /config /media \
+ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
+
+# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+ENV LC_ALL en_US.UTF-8
+ENV 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
-# TODO Remove or update the sed line when we update dotnet version.
-RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
-RUN dotnet publish \
- -r linux-arm \
- --configuration release \
- --output /jellyfin \
- Jellyfin.Server
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
+FROM app
+
+ENV HEALTHCHECK_URL=http://localhost:8096/health
-FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7
-COPY --from=qemu_extract qemu-arm-static /usr/bin
-RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
- && mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin
+COPY --from=web-builder /dist /jellyfin/jellyfin-web
+
EXPOSE 8096
VOLUME /cache /config /media
-ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache
+ENTRYPOINT ["./jellyfin/jellyfin", \
+ "--datadir", "/config", \
+ "--cachedir", "/cache", \
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
+
+HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
+ CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index e61aaa167c..1279c47f8e 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -1,37 +1,75 @@
+# DESIGNED FOR BUILDING ON ARM64 ONLY
+#####################################
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
-ARG DOTNET_VERSION=3.0
+ARG DOTNET_VERSION=6.0
+FROM node:lts-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 - \
+ && cd jellyfin-web-* \
+ && npm ci --no-audit --unsafe-perm \
+ && mv dist /dist
+
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM alpine as qemu_extract
-COPY --from=qemu /usr/bin qemu-aarch64-static.tar.gz
-RUN tar -xzvf qemu-aarch64-static.tar.gz
+FROM arm64v8/debian:stable-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_DRIVER_CAPABILITIES="compute,video,utility"
-FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch as builder
+COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
+
+# curl: healcheck
+RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
+ ffmpeg \
+ libssl-dev \
+ ca-certificates \
+ libfontconfig1 \
+ libfreetype6 \
+ libomxil-bellagio0 \
+ libomxil-bellagio-bin \
+ locales \
+ curl \
+ && apt-get clean autoclean -y \
+ && apt-get autoremove -y \
+ && rm -rf /var/lib/apt/lists/* \
+ && mkdir -p /cache /config /media \
+ && chmod 777 /cache /config /media \
+ && sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
+
+# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
+ENV LC_ALL en_US.UTF-8
+ENV 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
-# TODO Remove or update the sed line when we update dotnet version.
-RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
# Discard objs - may cause failures if exists
RUN find . -type d -name obj | xargs -r rm -r
# Build
-RUN dotnet publish \
- -r linux-arm64 \
- --configuration release \
- --output /jellyfin \
- Jellyfin.Server
+RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
+FROM app
+
+ENV HEALTHCHECK_URL=http://localhost:8096/health
-FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8
-COPY --from=qemu_extract qemu-aarch64-static /usr/bin
-RUN apt-get update \
- && apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
- && mkdir -p /cache /config /media \
- && chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin
+COPY --from=web-builder /dist /jellyfin/jellyfin-web
+
EXPOSE 8096
VOLUME /cache /config /media
-ENTRYPOINT dotnet /jellyfin/jellyfin.dll --datadir /config --cachedir /cache
+ENTRYPOINT ["./jellyfin/jellyfin", \
+ "--datadir", "/config", \
+ "--cachedir", "/cache", \
+ "--ffmpeg", "/usr/bin/ffmpeg"]
+
+HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
+ CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1
diff --git a/DvdLib/BigEndianBinaryReader.cs b/DvdLib/BigEndianBinaryReader.cs
index b3b2eabd5c..b3aad85cec 100644
--- a/DvdLib/BigEndianBinaryReader.cs
+++ b/DvdLib/BigEndianBinaryReader.cs
@@ -1,4 +1,6 @@
-using System;
+#pragma warning disable CS1591
+
+using System.Buffers.Binary;
using System.IO;
namespace DvdLib
@@ -12,19 +14,12 @@ namespace DvdLib
public override ushort ReadUInt16()
{
- return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
+ return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
}
public override uint ReadUInt32()
{
- return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
- }
-
- private byte[] ReadAndReverseBytes(int count)
- {
- byte[] val = base.ReadBytes(count);
- Array.Reverse(val, 0, count);
- return val;
+ return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
}
}
}
diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj
index b2c752d0c8..755d29160c 100644
--- a/DvdLib/DvdLib.csproj
+++ b/DvdLib/DvdLib.csproj
@@ -1,16 +1,20 @@
+
+
+ {713F42B5-878E-499D-A878-E4C652B1D5E8}
+
+
-
-
-
-
- netstandard2.0
+ net6.0
false
+ true
+ AllDisabledByDefault
+ disable
diff --git a/DvdLib/Ifo/AudioAttributes.cs b/DvdLib/Ifo/AudioAttributes.cs
deleted file mode 100644
index b76f9fc05e..0000000000
--- a/DvdLib/Ifo/AudioAttributes.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-namespace DvdLib.Ifo
-{
- public enum AudioCodec
- {
- AC3 = 0,
- MPEG1 = 2,
- MPEG2ext = 3,
- LPCM = 4,
- DTS = 6,
- }
-
- public enum ApplicationMode
- {
- Unspecified = 0,
- Karaoke = 1,
- Surround = 2,
- }
-
- public class AudioAttributes
- {
- public readonly AudioCodec Codec;
- public readonly bool MultichannelExtensionPresent;
- public readonly ApplicationMode Mode;
- public readonly byte QuantDRC;
- public readonly byte SampleRate;
- public readonly byte Channels;
- public readonly ushort LanguageCode;
- public readonly byte LanguageExtension;
- public readonly byte CodeExtension;
- }
-
- public class MultiChannelExtension
- {
-
- }
-}
diff --git a/DvdLib/Ifo/Cell.cs b/DvdLib/Ifo/Cell.cs
index 268ab897ee..ea0b50e430 100644
--- a/DvdLib/Ifo/Cell.cs
+++ b/DvdLib/Ifo/Cell.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
namespace DvdLib.Ifo
@@ -5,6 +7,7 @@ namespace DvdLib.Ifo
public class Cell
{
public CellPlaybackInfo PlaybackInfo { get; private set; }
+
public CellPositionInfo PositionInfo { get; private set; }
internal void ParsePlayback(BinaryReader br)
diff --git a/DvdLib/Ifo/CellPlaybackInfo.cs b/DvdLib/Ifo/CellPlaybackInfo.cs
index e588e51ac0..6e33a0ec5a 100644
--- a/DvdLib/Ifo/CellPlaybackInfo.cs
+++ b/DvdLib/Ifo/CellPlaybackInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
namespace DvdLib.Ifo
diff --git a/DvdLib/Ifo/CellPositionInfo.cs b/DvdLib/Ifo/CellPositionInfo.cs
index 2b973e0830..216aa0f77a 100644
--- a/DvdLib/Ifo/CellPositionInfo.cs
+++ b/DvdLib/Ifo/CellPositionInfo.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.IO;
namespace DvdLib.Ifo
diff --git a/DvdLib/Ifo/Chapter.cs b/DvdLib/Ifo/Chapter.cs
index bd3bd97040..e786cb5536 100644
--- a/DvdLib/Ifo/Chapter.cs
+++ b/DvdLib/Ifo/Chapter.cs
@@ -1,9 +1,13 @@
+#pragma warning disable CS1591
+
namespace DvdLib.Ifo
{
public class Chapter
{
public ushort ProgramChainNumber { get; private set; }
+
public ushort ProgramNumber { get; private set; }
+
public uint ChapterNumber { get; private set; }
public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)
diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs
index f784be83e7..7f8ece47dc 100644
--- a/DvdLib/Ifo/Dvd.cs
+++ b/DvdLib/Ifo/Dvd.cs
@@ -1,8 +1,10 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
-using MediaBrowser.Model.IO;
namespace DvdLib.Ifo
{
@@ -13,36 +15,33 @@ namespace DvdLib.Ifo
private ushort _titleCount;
public readonly Dictionary VTSPaths = new Dictionary();
- private readonly IFileSystem _fileSystem;
-
- public Dvd(string path, IFileSystem fileSystem)
+ public Dvd(string path)
{
- _fileSystem = fileSystem;
Titles = new List();
- var allFiles = _fileSystem.GetFiles(path, true).ToList();
+ var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories);
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
if (vmgPath == null)
{
- var allIfos = allFiles.Where(i => string.Equals(i.Extension, ".ifo", StringComparison.OrdinalIgnoreCase));
-
- foreach (var ifo in allIfos)
+ foreach (var ifo in allFiles)
{
- var num = ifo.Name.Split('_').ElementAtOrDefault(1);
- var numbersRead = new List();
+ if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
- if (!string.IsNullOrEmpty(num) && ushort.TryParse(num, out var ifoNumber) && !numbersRead.Contains(ifoNumber))
+ var nums = ifo.Name.Split('_', StringSplitOptions.RemoveEmptyEntries);
+ if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
{
ReadVTS(ifoNumber, ifo.FullName);
- numbersRead.Add(ifoNumber);
}
}
}
else
{
- using (var vmgFs = _fileSystem.GetFileStream(vmgPath.FullName, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var vmgRead = new BigEndianBinaryReader(vmgFs))
{
@@ -76,9 +75,9 @@ namespace DvdLib.Ifo
}
}
- private void ReadVTS(ushort vtsNum, List allFiles)
+ private void ReadVTS(ushort vtsNum, IReadOnlyList allFiles)
{
- var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
+ var filename = string.Format(CultureInfo.InvariantCulture, "VTS_{0:00}_0.IFO", vtsNum);
var vtsPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, filename, StringComparison.OrdinalIgnoreCase)) ??
allFiles.FirstOrDefault(i => string.Equals(i.Name, Path.ChangeExtension(filename, ".bup"), StringComparison.OrdinalIgnoreCase));
@@ -95,7 +94,7 @@ namespace DvdLib.Ifo
{
VTSPaths[vtsNum] = vtsPath;
- using (var vtsFs = _fileSystem.GetFileStream(vtsPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
+ using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var vtsRead = new BigEndianBinaryReader(vtsFs))
{
@@ -119,12 +118,19 @@ namespace DvdLib.Ifo
uint chapNum = 1;
vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
- if (t == null) continue;
+ if (t == null)
+ {
+ continue;
+ }
do
{
t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum));
- if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) break;
+ if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1]))
+ {
+ break;
+ }
+
chapNum++;
}
while (vtsFs.Position < (baseAddr + endaddr));
@@ -149,7 +155,10 @@ namespace DvdLib.Ifo
uint vtsPgcOffset = vtsRead.ReadUInt32();
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum));
- if (t != null) t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
+ if (t != null)
+ {
+ t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
+ }
}
}
}
diff --git a/DvdLib/Ifo/DvdTime.cs b/DvdLib/Ifo/DvdTime.cs
index 3688089ec7..d231406106 100644
--- a/DvdLib/Ifo/DvdTime.cs
+++ b/DvdLib/Ifo/DvdTime.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace DvdLib.Ifo
@@ -13,8 +15,14 @@ namespace DvdLib.Ifo
Second = GetBCDValue(data[2]);
Frames = GetBCDValue((byte)(data[3] & 0x3F));
- if ((data[3] & 0x80) != 0) FrameRate = 30;
- else if ((data[3] & 0x40) != 0) FrameRate = 25;
+ if ((data[3] & 0x80) != 0)
+ {
+ FrameRate = 30;
+ }
+ else if ((data[3] & 0x40) != 0)
+ {
+ FrameRate = 25;
+ }
}
private static byte GetBCDValue(byte data)
diff --git a/DvdLib/Ifo/PgcCommandTable.cs b/DvdLib/Ifo/PgcCommandTable.cs
deleted file mode 100644
index d329fcba2a..0000000000
--- a/DvdLib/Ifo/PgcCommandTable.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Collections.Generic;
-
-namespace DvdLib.Ifo
-{
- public class ProgramChainCommandTable
- {
- public readonly ushort LastByteAddress;
- public readonly List PreCommands;
- public readonly List PostCommands;
- public readonly List CellCommands;
- }
-
- public class VirtualMachineCommand
- {
- public readonly byte[] Command;
- }
-}
diff --git a/DvdLib/Ifo/Program.cs b/DvdLib/Ifo/Program.cs
index af08afa356..3d94fa7dc1 100644
--- a/DvdLib/Ifo/Program.cs
+++ b/DvdLib/Ifo/Program.cs
@@ -1,10 +1,12 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace DvdLib.Ifo
{
public class Program
{
- public readonly List Cells;
+ public IReadOnlyList Cells { get; }
public Program(List cells)
{
diff --git a/DvdLib/Ifo/ProgramChain.cs b/DvdLib/Ifo/ProgramChain.cs
index 80889738f1..83c0051b90 100644
--- a/DvdLib/Ifo/ProgramChain.cs
+++ b/DvdLib/Ifo/ProgramChain.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -20,27 +22,27 @@ namespace DvdLib.Ifo
public readonly List Cells;
public DvdTime PlaybackTime { get; private set; }
+
public UserOperation ProhibitedUserOperations { get; private set; }
+
public byte[] AudioStreamControl { get; private set; } // 8*2 entries
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
private ushort _nextProgramNumber;
- public readonly ProgramChain Next;
private ushort _prevProgramNumber;
- public readonly ProgramChain Previous;
private ushort _goupProgramNumber;
- public readonly ProgramChain Goup; // ?? maybe Group
public ProgramPlaybackMode PlaybackMode { get; private set; }
+
public uint ProgramCount { get; private set; }
public byte StillTime { get; private set; }
+
public byte[] Palette { get; private set; } // 16*4 entries
private ushort _commandTableOffset;
- public readonly ProgramChainCommandTable CommandTable;
private ushort _programMapOffset;
private ushort _cellPlaybackOffset;
@@ -73,8 +75,15 @@ namespace DvdLib.Ifo
StillTime = br.ReadByte();
byte pbMode = br.ReadByte();
- if (pbMode == 0) PlaybackMode = ProgramPlaybackMode.Sequential;
- else PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
+ if (pbMode == 0)
+ {
+ PlaybackMode = ProgramPlaybackMode.Sequential;
+ }
+ else
+ {
+ PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
+ }
+
ProgramCount = (uint)(pbMode & 0x7F);
Palette = br.ReadBytes(64);
diff --git a/DvdLib/Ifo/Title.cs b/DvdLib/Ifo/Title.cs
index 335e929928..29a0b95c72 100644
--- a/DvdLib/Ifo/Title.cs
+++ b/DvdLib/Ifo/Title.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using System.IO;
@@ -6,8 +8,11 @@ namespace DvdLib.Ifo
public class Title
{
public uint TitleNumber { get; private set; }
+
public uint AngleCount { get; private set; }
+
public ushort ChapterCount { get; private set; }
+
public byte VideoTitleSetNumber { get; private set; }
private ushort _parentalManagementMask;
@@ -15,6 +20,7 @@ namespace DvdLib.Ifo
private uint _vtsStartSector; // relative to start of entire disk
public ProgramChain EntryProgramChain { get; private set; }
+
public readonly List ProgramChains;
public readonly List Chapters;
@@ -53,7 +59,10 @@ namespace DvdLib.Ifo
var pgc = new ProgramChain(pgcNum);
pgc.ParseHeader(br);
ProgramChains.Add(pgc);
- if (entryPgc) EntryProgramChain = pgc;
+ if (entryPgc)
+ {
+ EntryProgramChain = pgc;
+ }
br.BaseStream.Seek(curPos, SeekOrigin.Begin);
}
diff --git a/DvdLib/Ifo/UserOperation.cs b/DvdLib/Ifo/UserOperation.cs
index 757a5a05db..5d111ebc06 100644
--- a/DvdLib/Ifo/UserOperation.cs
+++ b/DvdLib/Ifo/UserOperation.cs
@@ -1,3 +1,5 @@
+#pragma warning disable CS1591
+
using System;
namespace DvdLib.Ifo
diff --git a/DvdLib/Ifo/VideoAttributes.cs b/DvdLib/Ifo/VideoAttributes.cs
deleted file mode 100644
index 8b3996715c..0000000000
--- a/DvdLib/Ifo/VideoAttributes.cs
+++ /dev/null
@@ -1,46 +0,0 @@
-namespace DvdLib.Ifo
-{
- public enum VideoCodec
- {
- MPEG1 = 0,
- MPEG2 = 1,
- }
-
- public enum VideoFormat
- {
- NTSC = 0,
- PAL = 1,
- }
-
- public enum AspectRatio
- {
- ar4to3 = 0,
- ar16to9 = 3
- }
-
- public enum FilmMode
- {
- None = -1,
- Camera = 0,
- Film = 1,
- }
-
- public class VideoAttributes
- {
- public readonly VideoCodec Codec;
- public readonly VideoFormat Format;
- public readonly AspectRatio Aspect;
- public readonly bool AutomaticPanScan;
- public readonly bool AutomaticLetterBox;
- public readonly bool Line21CCField1;
- public readonly bool Line21CCField2;
- public readonly int Width;
- public readonly int Height;
- public readonly bool Letterboxed;
- public readonly FilmMode FilmMode;
-
- public VideoAttributes()
- {
- }
- }
-}
diff --git a/DvdLib/Properties/AssemblyInfo.cs b/DvdLib/Properties/AssemblyInfo.cs
index 5fc055d1f0..6acd571d68 100644
--- a/DvdLib/Properties/AssemblyInfo.cs
+++ b/DvdLib/Properties/AssemblyInfo.cs
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
-[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
-[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
+[assembly: AssemblyProduct("Jellyfin Server")]
+[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs
deleted file mode 100644
index 68bf801637..0000000000
--- a/Emby.Dlna/Api/DlnaServerService.cs
+++ /dev/null
@@ -1,306 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Emby.Dlna.Main;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Services;
-
-namespace Emby.Dlna.Api
-{
- [Route("/Dlna/{UuId}/description.xml", "GET", Summary = "Gets dlna server info")]
- [Route("/Dlna/{UuId}/description", "GET", Summary = "Gets dlna server info")]
- public class GetDescriptionXml
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string UuId { get; set; }
- }
-
- [Route("/Dlna/{UuId}/contentdirectory/contentdirectory.xml", "GET", Summary = "Gets dlna content directory xml")]
- [Route("/Dlna/{UuId}/contentdirectory/contentdirectory", "GET", Summary = "Gets dlna content directory xml")]
- public class GetContentDirectory
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string UuId { get; set; }
- }
-
- [Route("/Dlna/{UuId}/connectionmanager/connectionmanager.xml", "GET", Summary = "Gets dlna connection manager xml")]
- [Route("/Dlna/{UuId}/connectionmanager/connectionmanager", "GET", Summary = "Gets dlna connection manager xml")]
- public class GetConnnectionManager
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string UuId { get; set; }
- }
-
- [Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar.xml", "GET", Summary = "Gets dlna mediareceiverregistrar xml")]
- [Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar", "GET", Summary = "Gets dlna mediareceiverregistrar xml")]
- public class GetMediaReceiverRegistrar
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string UuId { get; set; }
- }
-
- [Route("/Dlna/{UuId}/contentdirectory/control", "POST", Summary = "Processes a control request")]
- public class ProcessContentDirectoryControlRequest : IRequiresRequestStream
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string UuId { get; set; }
-
- public Stream RequestStream { get; set; }
- }
-
- [Route("/Dlna/{UuId}/connectionmanager/control", "POST", Summary = "Processes a control request")]
- public class ProcessConnectionManagerControlRequest : IRequiresRequestStream
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string UuId { get; set; }
-
- public Stream RequestStream { get; set; }
- }
-
- [Route("/Dlna/{UuId}/mediareceiverregistrar/control", "POST", Summary = "Processes a control request")]
- public class ProcessMediaReceiverRegistrarControlRequest : IRequiresRequestStream
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string UuId { get; set; }
-
- public Stream RequestStream { get; set; }
- }
-
- [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
- [Route("/Dlna/{UuId}/mediareceiverregistrar/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
- public class ProcessMediaReceiverRegistrarEventRequest
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
- public string UuId { get; set; }
- }
-
- [Route("/Dlna/{UuId}/contentdirectory/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
- [Route("/Dlna/{UuId}/contentdirectory/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
- public class ProcessContentDirectoryEventRequest
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
- public string UuId { get; set; }
- }
-
- [Route("/Dlna/{UuId}/connectionmanager/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
- [Route("/Dlna/{UuId}/connectionmanager/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
- public class ProcessConnectionManagerEventRequest
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
- public string UuId { get; set; }
- }
-
- [Route("/Dlna/{UuId}/icons/{Filename}", "GET", Summary = "Gets a server icon")]
- [Route("/Dlna/icons/{Filename}", "GET", Summary = "Gets a server icon")]
- public class GetIcon
- {
- [ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string UuId { get; set; }
-
- [ApiMember(Name = "Filename", Description = "The icon filename", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Filename { get; set; }
- }
-
- public class DlnaServerService : IService, IRequiresRequest
- {
- private readonly IDlnaManager _dlnaManager;
-
- private const string XMLContentType = "text/xml; charset=UTF-8";
-
- public IRequest Request { get; set; }
- private IHttpResultFactory _resultFactory;
-
- private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
-
- private IConnectionManager ConnectionManager => DlnaEntryPoint.Current.ConnectionManager;
-
- private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
-
- public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory)
- {
- _dlnaManager = dlnaManager;
- _resultFactory = httpResultFactory;
- }
-
- private string GetHeader(string name)
- {
- return Request.Headers[name];
- }
-
- public object Get(GetDescriptionXml request)
- {
- var url = Request.AbsoluteUri;
- var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
- var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress);
-
- var cacheLength = TimeSpan.FromDays(1);
- var cacheKey = Request.RawUrl.GetMD5();
- var bytes = Encoding.UTF8.GetBytes(xml);
-
- return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult(new MemoryStream(bytes)));
- }
-
- public object Get(GetContentDirectory request)
- {
- var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary());
-
- return _resultFactory.GetResult(Request, xml, XMLContentType);
- }
-
- public object Get(GetMediaReceiverRegistrar request)
- {
- var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary());
-
- return _resultFactory.GetResult(Request, xml, XMLContentType);
- }
-
- public object Get(GetConnnectionManager request)
- {
- var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary());
-
- return _resultFactory.GetResult(Request, xml, XMLContentType);
- }
-
- public object Post(ProcessMediaReceiverRegistrarControlRequest request)
- {
- var response = PostAsync(request.RequestStream, MediaReceiverRegistrar);
-
- return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
- }
-
- public object Post(ProcessContentDirectoryControlRequest request)
- {
- var response = PostAsync(request.RequestStream, ContentDirectory);
-
- return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
- }
-
- public object Post(ProcessConnectionManagerControlRequest request)
- {
- var response = PostAsync(request.RequestStream, ConnectionManager);
-
- return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
- }
-
- private ControlResponse PostAsync(Stream requestStream, IUpnpService service)
- {
- var id = GetPathValue(2);
-
- return service.ProcessControlRequest(new ControlRequest
- {
- Headers = Request.Headers.ToDictionary(),
- InputXml = requestStream,
- TargetServerUuId = id,
- RequestedUrl = Request.AbsoluteUri
- });
- }
-
- protected string GetPathValue(int index)
- {
- var pathInfo = Parse(Request.PathInfo);
- var first = pathInfo[0];
-
- // backwards compatibility
- // TODO: Work out what this is doing.
- if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(first, "jellyfin", StringComparison.OrdinalIgnoreCase))
- {
- index++;
- }
-
- return pathInfo[index];
- }
-
- private List Parse(string pathUri)
- {
- var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
-
- var pathInfo = actionParts[actionParts.Length - 1];
-
- var optionsPos = pathInfo.LastIndexOf('?');
- if (optionsPos != -1)
- {
- pathInfo = pathInfo.Substring(0, optionsPos);
- }
-
- var args = pathInfo.Split('/');
-
- return args.Skip(1).ToList();
- }
-
- public object Get(GetIcon request)
- {
- var contentType = "image/" + Path.GetExtension(request.Filename)
- .TrimStart('.')
- .ToLowerInvariant();
-
- var cacheLength = TimeSpan.FromDays(365);
- var cacheKey = Request.RawUrl.GetMD5();
-
- return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream));
- }
-
- public object Subscribe(ProcessContentDirectoryEventRequest request)
- {
- return ProcessEventRequest(ContentDirectory);
- }
-
- public object Subscribe(ProcessConnectionManagerEventRequest request)
- {
- return ProcessEventRequest(ConnectionManager);
- }
-
- public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
- {
- return ProcessEventRequest(MediaReceiverRegistrar);
- }
-
- public object Unsubscribe(ProcessContentDirectoryEventRequest request)
- {
- return ProcessEventRequest(ContentDirectory);
- }
-
- public object Unsubscribe(ProcessConnectionManagerEventRequest request)
- {
- return ProcessEventRequest(ConnectionManager);
- }
-
- public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
- {
- return ProcessEventRequest(MediaReceiverRegistrar);
- }
-
- private object ProcessEventRequest(IEventManager eventManager)
- {
- var subscriptionId = GetHeader("SID");
-
- if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase))
- {
- var notificationType = GetHeader("NT");
-
- var callback = GetHeader("CALLBACK");
- var timeoutString = GetHeader("TIMEOUT");
-
- if (string.IsNullOrEmpty(notificationType))
- {
- return GetSubscriptionResponse(eventManager.RenewEventSubscription(subscriptionId, notificationType, timeoutString, callback));
- }
-
- return GetSubscriptionResponse(eventManager.CreateEventSubscription(notificationType, timeoutString, callback));
- }
-
- return GetSubscriptionResponse(eventManager.CancelEventSubscription(subscriptionId));
- }
-
- private object GetSubscriptionResponse(EventSubscriptionResponse response)
- {
- return _resultFactory.GetResult(Request, response.Content, response.ContentType, response.Headers);
- }
- }
-}
diff --git a/Emby.Dlna/Api/DlnaService.cs b/Emby.Dlna/Api/DlnaService.cs
deleted file mode 100644
index 7f51f477a7..0000000000
--- a/Emby.Dlna/Api/DlnaService.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-using System.Linq;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Services;
-
-namespace Emby.Dlna.Api
-{
- [Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")]
- public class GetProfileInfos : IReturn
- {
- }
-
- [Route("/Dlna/Profiles/{Id}", "DELETE", Summary = "Deletes a profile")]
- public class DeleteProfile : IReturnVoid
- {
- [ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
- public string Id { get; set; }
- }
-
- [Route("/Dlna/Profiles/Default", "GET", Summary = "Gets the default profile")]
- public class GetDefaultProfile : IReturn
- {
- }
-
- [Route("/Dlna/Profiles/{Id}", "GET", Summary = "Gets a single profile")]
- public class GetProfile : IReturn
- {
- [ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
- public string Id { get; set; }
- }
-
- [Route("/Dlna/Profiles/{Id}", "POST", Summary = "Updates a profile")]
- public class UpdateProfile : DeviceProfile, IReturnVoid
- {
- }
-
- [Route("/Dlna/Profiles", "POST", Summary = "Creates a profile")]
- public class CreateProfile : DeviceProfile, IReturnVoid
- {
- }
-
- [Authenticated(Roles = "Admin")]
- public class DlnaService : IService
- {
- private readonly IDlnaManager _dlnaManager;
-
- public DlnaService(IDlnaManager dlnaManager)
- {
- _dlnaManager = dlnaManager;
- }
-
- public object Get(GetProfileInfos request)
- {
- return _dlnaManager.GetProfileInfos().ToArray();
- }
-
- public object Get(GetProfile request)
- {
- return _dlnaManager.GetProfile(request.Id);
- }
-
- public object Get(GetDefaultProfile request)
- {
- return _dlnaManager.GetDefaultProfile();
- }
-
- public void Delete(DeleteProfile request)
- {
- _dlnaManager.DeleteProfile(request.Id);
- }
-
- public void Post(UpdateProfile request)
- {
- _dlnaManager.UpdateProfile(request);
- }
-
- public void Post(CreateProfile request)
- {
- _dlnaManager.CreateProfile(request);
- }
- }
-}
diff --git a/Emby.Dlna/Common/Argument.cs b/Emby.Dlna/Common/Argument.cs
index 3e325c41ce..e4e9c55e0d 100644
--- a/Emby.Dlna/Common/Argument.cs
+++ b/Emby.Dlna/Common/Argument.cs
@@ -1,12 +1,23 @@
-
namespace Emby.Dlna.Common
{
+ ///
+ /// DLNA Query parameter type, used when querying DLNA devices via SOAP.
+ ///
public class Argument
{
- public string Name { get; set; }
+ ///
+ /// Gets or sets name of the DLNA argument.
+ ///
+ public string Name { get; set; } = string.Empty;
- public string Direction { get; set; }
+ ///
+ /// Gets or sets the direction of the parameter.
+ ///
+ public string Direction { get; set; } = string.Empty;
- public string RelatedStateVariable { get; set; }
+ ///
+ /// Gets or sets the related DLNA state variable for this argument.
+ ///
+ public string RelatedStateVariable { get; set; } = string.Empty;
}
}
diff --git a/Emby.Dlna/Common/DeviceIcon.cs b/Emby.Dlna/Common/DeviceIcon.cs
index 3a91b952ec..f9fd1dcec6 100644
--- a/Emby.Dlna/Common/DeviceIcon.cs
+++ b/Emby.Dlna/Common/DeviceIcon.cs
@@ -1,21 +1,41 @@
+using System.Globalization;
namespace Emby.Dlna.Common
{
+ ///
+ /// Defines the .
+ ///
public class DeviceIcon
{
- public string Url { get; set; }
+ ///
+ /// Gets or sets the Url.
+ ///
+ public string Url { get; set; } = string.Empty;
- public string MimeType { get; set; }
+ ///
+ /// Gets or sets the MimeType.
+ ///
+ public string MimeType { get; set; } = string.Empty;
+ ///
+ /// Gets or sets the Width.
+ ///
public int Width { get; set; }
+ ///
+ /// Gets or sets the Height.
+ ///
public int Height { get; set; }
- public string Depth { get; set; }
+ ///
+ /// Gets or sets the Depth.
+ ///
+ public string Depth { get; set; } = string.Empty;
+ ///
public override string ToString()
{
- return string.Format("{0}x{1}", Height, Width);
+ return string.Format(CultureInfo.InvariantCulture, "{0}x{1}", Height, Width);
}
}
}
diff --git a/Emby.Dlna/Common/DeviceService.cs b/Emby.Dlna/Common/DeviceService.cs
index c60d652913..c1369558ec 100644
--- a/Emby.Dlna/Common/DeviceService.cs
+++ b/Emby.Dlna/Common/DeviceService.cs
@@ -1,21 +1,36 @@
-
namespace Emby.Dlna.Common
{
+ ///
+ /// Defines the .
+ ///
public class DeviceService
{
- public string ServiceType { get; set; }
+ ///
+ /// Gets or sets the Service Type.
+ ///
+ public string ServiceType { get; set; } = string.Empty;
- public string ServiceId { get; set; }
+ ///
+ /// Gets or sets the Service Id.
+ ///
+ public string ServiceId { get; set; } = string.Empty;
- public string ScpdUrl { get; set; }
+ ///
+ /// Gets or sets the Scpd Url.
+ ///
+ public string ScpdUrl { get; set; } = string.Empty;
- public string ControlUrl { get; set; }
+ ///
+ /// Gets or sets the Control Url.
+ ///
+ public string ControlUrl { get; set; } = string.Empty;
- public string EventSubUrl { get; set; }
+ ///
+ /// Gets or sets the EventSubUrl.
+ ///
+ public string EventSubUrl { get; set; } = string.Empty;
- public override string ToString()
- {
- return string.Format("{0}", ServiceId);
- }
+ ///
+ public override string ToString() => ServiceId;
}
}
diff --git a/Emby.Dlna/Common/ServiceAction.cs b/Emby.Dlna/Common/ServiceAction.cs
index 5e030d396a..02b81a0aa7 100644
--- a/Emby.Dlna/Common/ServiceAction.cs
+++ b/Emby.Dlna/Common/ServiceAction.cs
@@ -2,20 +2,30 @@ using System.Collections.Generic;
namespace Emby.Dlna.Common
{
+ ///
+ /// Defines the .
+ ///
public class ServiceAction
{
- public string Name { get; set; }
-
- public List ArgumentList { get; set; }
-
- public override string ToString()
- {
- return Name;
- }
-
+ ///
+ /// Initializes a new instance of the class.
+ ///
public ServiceAction()
{
ArgumentList = new List();
}
+
+ ///
+ /// Gets or sets the name of the action.
+ ///
+ public string Name { get; set; } = string.Empty;
+
+ ///
+ /// Gets the ArgumentList.
+ ///
+ public List ArgumentList { get; }
+
+ ///
+ public override string ToString() => Name;
}
}
diff --git a/Emby.Dlna/Common/StateVariable.cs b/Emby.Dlna/Common/StateVariable.cs
index 4ca84bf51c..fd733e0853 100644
--- a/Emby.Dlna/Common/StateVariable.cs
+++ b/Emby.Dlna/Common/StateVariable.cs
@@ -1,25 +1,34 @@
using System;
+using System.Collections.Generic;
namespace Emby.Dlna.Common
{
+ ///
+ /// Defines the .
+ ///
public class StateVariable
{
- public string Name { get; set; }
+ ///
+ /// Gets or sets the name of the state variable.
+ ///
+ public string Name { get; set; } = string.Empty;
- public string DataType { get; set; }
+ ///
+ /// Gets or sets the data type of the state variable.
+ ///
+ public string DataType { get; set; } = string.Empty;
+ ///
+ /// Gets or sets a value indicating whether it sends events.
+ ///
public bool SendsEvents { get; set; }
- public string[] AllowedValues { get; set; }
+ ///
+ /// Gets or sets the allowed values range.
+ ///
+ public IReadOnlyList AllowedValues { get; set; } = Array.Empty();
- public override string ToString()
- {
- return Name;
- }
-
- public StateVariable()
- {
- AllowedValues = Array.Empty();
- }
+ ///
+ public override string ToString() => Name;
}
}
diff --git a/Emby.Dlna/Configuration/DlnaOptions.cs b/Emby.Dlna/Configuration/DlnaOptions.cs
index 0ebb490a1f..91fac4bef5 100644
--- a/Emby.Dlna/Configuration/DlnaOptions.cs
+++ b/Emby.Dlna/Configuration/DlnaOptions.cs
@@ -1,23 +1,92 @@
+#pragma warning disable CS1591
namespace Emby.Dlna.Configuration
{
+ ///
+ /// The DlnaOptions class contains the user definable parameters for the dlna subsystems.
+ ///
public class DlnaOptions
{
- public bool EnablePlayTo { get; set; }
- public bool EnableServer { get; set; }
- public bool EnableDebugLog { get; set; }
- public bool BlastAliveMessages { get; set; }
- public int ClientDiscoveryIntervalSeconds { get; set; }
- public int BlastAliveMessageIntervalSeconds { get; set; }
- public string DefaultUserId { get; set; }
-
+ ///
+ /// Initializes a new instance of the class.
+ ///
public DlnaOptions()
{
EnablePlayTo = true;
EnableServer = true;
BlastAliveMessages = true;
+ SendOnlyMatchedHost = true;
ClientDiscoveryIntervalSeconds = 60;
- BlastAliveMessageIntervalSeconds = 1800;
+ AliveMessageIntervalSeconds = 1800;
}
+
+ ///
+ /// Gets or sets a value indicating whether gets or sets a value to indicate the status of the dlna playTo subsystem.
+ ///
+ public bool EnablePlayTo { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether gets or sets a value to indicate the status of the dlna server subsystem.
+ ///
+ public bool EnableServer { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether detailed dlna server logs are sent to the console/log.
+ /// If the setting "Emby.Dlna": "Debug" msut be set in logging.default.json for this property to work.
+ ///
+ public bool EnableDebugLog { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether whether detailed playTo debug logs are sent to the console/log.
+ /// If the setting "Emby.Dlna.PlayTo": "Debug" msut be set in logging.default.json for this property to work.
+ ///
+ public bool EnablePlayToTracing { get; set; }
+
+ ///
+ /// Gets or sets the ssdp client discovery interval time (in seconds).
+ /// This is the time after which the server will send a ssdp search request.
+ ///
+ public int ClientDiscoveryIntervalSeconds { get; set; }
+
+ ///
+ /// Gets or sets the frequency at which ssdp alive notifications are transmitted.
+ ///
+ public int AliveMessageIntervalSeconds { get; set; }
+
+ ///
+ /// Gets or sets the frequency at which ssdp alive notifications are transmitted. MIGRATING - TO BE REMOVED ONCE WEB HAS BEEN ALTERED.
+ ///
+ public int BlastAliveMessageIntervalSeconds
+ {
+ get
+ {
+ return AliveMessageIntervalSeconds;
+ }
+
+ set
+ {
+ AliveMessageIntervalSeconds = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the default user account that the dlna server uses.
+ ///
+ public string? DefaultUserId { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether playTo device profiles should be created.
+ ///
+ public bool AutoCreatePlayToProfiles { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to blast alive messages.
+ ///
+ public bool BlastAliveMessages { get; set; } = true;
+
+ ///
+ /// gets or sets a value indicating whether to send only matched host.
+ ///
+ public bool SendOnlyMatchedHost { get; set; } = true;
}
}
diff --git a/Emby.Dlna/ConfigurationExtension.cs b/Emby.Dlna/ConfigurationExtension.cs
index 82d726e018..3ca43052a4 100644
--- a/Emby.Dlna/ConfigurationExtension.cs
+++ b/Emby.Dlna/ConfigurationExtension.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+#pragma warning disable CS1591
+
using Emby.Dlna.Configuration;
using MediaBrowser.Common.Configuration;
@@ -11,19 +12,4 @@ namespace Emby.Dlna
return manager.GetConfiguration("dlna");
}
}
-
- public class DlnaConfigurationFactory : IConfigurationFactory
- {
- public IEnumerable GetConfigurations()
- {
- return new ConfigurationStore[]
- {
- new ConfigurationStore
- {
- Key = "dlna",
- ConfigurationType = typeof (DlnaOptions)
- }
- };
- }
- }
}
diff --git a/Emby.Dlna/ConnectionManager/ConnectionManager.cs b/Emby.Dlna/ConnectionManager/ConnectionManager.cs
deleted file mode 100644
index cc427f2a15..0000000000
--- a/Emby.Dlna/ConnectionManager/ConnectionManager.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.Collections.Generic;
-using Emby.Dlna.Service;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Model.Xml;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Dlna.ConnectionManager
-{
- public class ConnectionManager : BaseService, IConnectionManager
- {
- private readonly IDlnaManager _dlna;
- private readonly ILogger _logger;
- private readonly IServerConfigurationManager _config;
- protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
-
- public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
- : base(logger, httpClient)
- {
- _dlna = dlna;
- _config = config;
- _logger = logger;
- XmlReaderSettingsFactory = xmlReaderSettingsFactory;
- }
-
- public string GetServiceXml(IDictionary headers)
- {
- return new ConnectionManagerXmlBuilder().GetXml();
- }
-
- public ControlResponse ProcessControlRequest(ControlRequest request)
- {
- var profile = _dlna.GetProfile(request.Headers) ??
- _dlna.GetDefaultProfile();
-
- return new ControlHandler(_config, _logger, XmlReaderSettingsFactory, profile).ProcessControlRequest(request);
- }
- }
-}
diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs
new file mode 100644
index 0000000000..916044a0cc
--- /dev/null
+++ b/Emby.Dlna/ConnectionManager/ConnectionManagerService.cs
@@ -0,0 +1,53 @@
+#pragma warning disable CS1591
+
+using System.Net.Http;
+using System.Threading.Tasks;
+using Emby.Dlna.Service;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dlna;
+using Microsoft.Extensions.Logging;
+
+namespace Emby.Dlna.ConnectionManager
+{
+ ///
+ /// Defines the .
+ ///
+ public class ConnectionManagerService : BaseService, IConnectionManager
+ {
+ private readonly IDlnaManager _dlna;
+ private readonly IServerConfigurationManager _config;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance..
+ /// The for use with the instance..
+ public ConnectionManagerService(
+ IDlnaManager dlna,
+ IServerConfigurationManager config,
+ ILogger logger,
+ IHttpClientFactory httpClientFactory)
+ : base(logger, httpClientFactory)
+ {
+ _dlna = dlna;
+ _config = config;
+ }
+
+ ///
+ public string GetServiceXml()
+ {
+ return ConnectionManagerXmlBuilder.GetXml();
+ }
+
+ ///
+ public Task ProcessControlRequestAsync(ControlRequest request)
+ {
+ var profile = _dlna.GetProfile(request.Headers) ??
+ _dlna.GetDefaultProfile();
+
+ return new ControlHandler(_config, Logger, profile).ProcessControlRequestAsync(request);
+ }
+ }
+}
diff --git a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
index f5873455a6..c484dac542 100644
--- a/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
+++ b/Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
@@ -1,48 +1,62 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
namespace Emby.Dlna.ConnectionManager
{
- public class ConnectionManagerXmlBuilder
+ ///
+ /// Defines the .
+ ///
+ public static class ConnectionManagerXmlBuilder
{
- public string GetXml()
+ ///
+ /// Gets the ConnectionManager:1 service template.
+ /// See http://upnp.org/specs/av/UPnP-av-ConnectionManager-v1-Service.pdf.
+ ///
+ /// An XML description of this service.
+ public static string GetXml()
{
- return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(), GetStateVariables());
+ return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables());
}
+ ///
+ /// Get the list of state variables for this invocation.
+ ///
+ /// The .
private static IEnumerable GetStateVariables()
{
- var list = new List();
-
- list.Add(new StateVariable
+ var list = new List
{
- Name = "SourceProtocolInfo",
- DataType = "string",
- SendsEvents = true
- });
+ new StateVariable
+ {
+ Name = "SourceProtocolInfo",
+ DataType = "string",
+ SendsEvents = true
+ },
- list.Add(new StateVariable
- {
- Name = "SinkProtocolInfo",
- DataType = "string",
- SendsEvents = true
- });
+ new StateVariable
+ {
+ Name = "SinkProtocolInfo",
+ DataType = "string",
+ SendsEvents = true
+ },
- list.Add(new StateVariable
- {
- Name = "CurrentConnectionIDs",
- DataType = "string",
- SendsEvents = true
- });
+ new StateVariable
+ {
+ Name = "CurrentConnectionIDs",
+ DataType = "string",
+ SendsEvents = true
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_ConnectionStatus",
- DataType = "string",
- SendsEvents = false,
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_ConnectionStatus",
+ DataType = "string",
+ SendsEvents = false,
- AllowedValues = new string[]
+ AllowedValues = new[]
{
"OK",
"ContentFormatMismatch",
@@ -50,55 +64,56 @@ namespace Emby.Dlna.ConnectionManager
"UnreliableChannel",
"Unknown"
}
- });
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_ConnectionManager",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_ConnectionManager",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_Direction",
- DataType = "string",
- SendsEvents = false,
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_Direction",
+ DataType = "string",
+ SendsEvents = false,
- AllowedValues = new string[]
+ AllowedValues = new[]
{
"Output",
"Input"
}
- });
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_ProtocolInfo",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_ProtocolInfo",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_ConnectionID",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_ConnectionID",
+ DataType = "ui4",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_AVTransportID",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_AVTransportID",
+ DataType = "ui4",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_RcsID",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_RcsID",
+ DataType = "ui4",
+ SendsEvents = false
+ }
+ };
return list;
}
diff --git a/Emby.Dlna/ConnectionManager/ControlHandler.cs b/Emby.Dlna/ConnectionManager/ControlHandler.cs
index 16211c61f4..1a1790ee6a 100644
--- a/Emby.Dlna/ConnectionManager/ControlHandler.cs
+++ b/Emby.Dlna/ConnectionManager/ControlHandler.cs
@@ -1,40 +1,55 @@
+#pragma warning disable CS1591
+
using System;
using System.Collections.Generic;
+using System.Xml;
using Emby.Dlna.Service;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ConnectionManager
{
+ ///
+ /// Defines the .
+ ///
public class ControlHandler : BaseControlHandler
{
private readonly DeviceProfile _profile;
- protected override IEnumerable> GetResult(string methodName, IDictionary methodParams)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance.
+ public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
+ : base(config, logger)
+ {
+ _profile = profile;
+ }
+
+ ///
+ protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter)
{
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
{
- return HandleGetProtocolInfo();
+ HandleGetProtocolInfo(xmlWriter);
+ return;
}
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
- private IEnumerable> HandleGetProtocolInfo()
+ ///
+ /// Builds the response to the GetProtocolInfo request.
+ ///
+ /// The .
+ private void HandleGetProtocolInfo(XmlWriter xmlWriter)
{
- return new Dictionary(StringComparer.OrdinalIgnoreCase)
- {
- { "Source", _profile.ProtocolInfo },
- { "Sink", "" }
- };
- }
-
- public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory, DeviceProfile profile) : base(config, logger, xmlReaderSettingsFactory)
- {
- _profile = profile;
+ xmlWriter.WriteElementString("Source", _profile.ProtocolInfo);
+ xmlWriter.WriteElementString("Sink", string.Empty);
}
}
}
diff --git a/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs b/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
index b7727b5580..542c7bfb4b 100644
--- a/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
+++ b/Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
@@ -1,11 +1,20 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Common;
namespace Emby.Dlna.ConnectionManager
{
- public class ServiceActionListBuilder
+ ///
+ /// Defines the .
+ ///
+ public static class ServiceActionListBuilder
{
- public IEnumerable GetActions()
+ ///
+ /// Returns an enumerable of the ConnectionManagar:1 DLNA actions.
+ ///
+ /// An .
+ public static IEnumerable GetActions()
{
var list = new List
{
@@ -19,6 +28,10 @@ namespace Emby.Dlna.ConnectionManager
return list;
}
+ ///
+ /// Returns the action details for "PrepareForConnection".
+ ///
+ /// The .
private static ServiceAction PrepareForConnection()
{
var action = new ServiceAction
@@ -78,6 +91,10 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
+ ///
+ /// Returns the action details for "GetCurrentConnectionInfo".
+ ///
+ /// The .
private static ServiceAction GetCurrentConnectionInfo()
{
var action = new ServiceAction
@@ -144,7 +161,11 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
- private ServiceAction GetProtocolInfo()
+ ///
+ /// Returns the action details for "GetProtocolInfo".
+ ///
+ /// The .
+ private static ServiceAction GetProtocolInfo()
{
var action = new ServiceAction
{
@@ -168,7 +189,11 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
- private ServiceAction GetCurrentConnectionIDs()
+ ///
+ /// Returns the action details for "GetCurrentConnectionIDs".
+ ///
+ /// The .
+ private static ServiceAction GetCurrentConnectionIDs()
{
var action = new ServiceAction
{
@@ -185,7 +210,11 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
- private ServiceAction ConnectionComplete()
+ ///
+ /// Returns the action details for "ConnectionComplete".
+ ///
+ /// The .
+ private static ServiceAction ConnectionComplete()
{
var action = new ServiceAction
{
diff --git a/Emby.Dlna/ContentDirectory/ContentDirectory.cs b/Emby.Dlna/ContentDirectory/ContentDirectory.cs
deleted file mode 100644
index b0fec90e69..0000000000
--- a/Emby.Dlna/ContentDirectory/ContentDirectory.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Emby.Dlna.Service;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Drawing;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.TV;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Globalization;
-using MediaBrowser.Model.Xml;
-using Microsoft.Extensions.Logging;
-
-namespace Emby.Dlna.ContentDirectory
-{
- public class ContentDirectory : BaseService, IContentDirectory
- {
- private readonly ILibraryManager _libraryManager;
- private readonly IImageProcessor _imageProcessor;
- private readonly IUserDataManager _userDataManager;
- private readonly IDlnaManager _dlna;
- private readonly IServerConfigurationManager _config;
- private readonly IUserManager _userManager;
- private readonly ILocalizationManager _localization;
- private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IUserViewManager _userViewManager;
- private readonly IMediaEncoder _mediaEncoder;
- protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
- private readonly ITVSeriesManager _tvSeriesManager;
-
- public ContentDirectory(IDlnaManager dlna,
- IUserDataManager userDataManager,
- IImageProcessor imageProcessor,
- ILibraryManager libraryManager,
- IServerConfigurationManager config,
- IUserManager userManager,
- ILogger logger,
- IHttpClient httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager)
- : base(logger, httpClient)
- {
- _dlna = dlna;
- _userDataManager = userDataManager;
- _imageProcessor = imageProcessor;
- _libraryManager = libraryManager;
- _config = config;
- _userManager = userManager;
- _localization = localization;
- _mediaSourceManager = mediaSourceManager;
- _userViewManager = userViewManager;
- _mediaEncoder = mediaEncoder;
- XmlReaderSettingsFactory = xmlReaderSettingsFactory;
- _tvSeriesManager = tvSeriesManager;
- }
-
- private int SystemUpdateId
- {
- get
- {
- var now = DateTime.UtcNow;
-
- return now.Year + now.DayOfYear + now.Hour;
- }
- }
-
- public string GetServiceXml(IDictionary headers)
- {
- return new ContentDirectoryXmlBuilder().GetXml();
- }
-
- public ControlResponse ProcessControlRequest(ControlRequest request)
- {
- var profile = _dlna.GetProfile(request.Headers) ??
- _dlna.GetDefaultProfile();
-
- var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
-
- var user = GetUser(profile);
-
- return new ControlHandler(
- Logger,
- _libraryManager,
- profile,
- serverAddress,
- null,
- _imageProcessor,
- _userDataManager,
- user,
- SystemUpdateId,
- _config,
- _localization,
- _mediaSourceManager,
- _userViewManager,
- _mediaEncoder,
- XmlReaderSettingsFactory,
- _tvSeriesManager)
- .ProcessControlRequest(request);
- }
-
- private User GetUser(DeviceProfile profile)
- {
- if (!string.IsNullOrEmpty(profile.UserId))
- {
- var user = _userManager.GetUserById(profile.UserId);
-
- if (user != null)
- {
- return user;
- }
- }
-
- var userId = _config.GetDlnaConfiguration().DefaultUserId;
-
- if (!string.IsNullOrEmpty(userId))
- {
- var user = _userManager.GetUserById(userId);
-
- if (user != null)
- {
- return user;
- }
- }
-
- foreach (var user in _userManager.Users)
- {
- if (user.Policy.IsAdministrator)
- {
- return user;
- }
- }
-
- foreach (var user in _userManager.Users)
- {
- return user;
- }
-
- return null;
- }
- }
-}
diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
new file mode 100644
index 0000000000..9020dea994
--- /dev/null
+++ b/Emby.Dlna/ContentDirectory/ContentDirectoryService.cs
@@ -0,0 +1,176 @@
+#pragma warning disable CS1591
+
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Emby.Dlna.Service;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.TV;
+using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Globalization;
+using Microsoft.Extensions.Logging;
+
+namespace Emby.Dlna.ContentDirectory
+{
+ ///
+ /// Defines the .
+ ///
+ public class ContentDirectoryService : BaseService, IContentDirectory
+ {
+ private readonly ILibraryManager _libraryManager;
+ private readonly IImageProcessor _imageProcessor;
+ private readonly IUserDataManager _userDataManager;
+ private readonly IDlnaManager _dlna;
+ private readonly IServerConfigurationManager _config;
+ private readonly IUserManager _userManager;
+ private readonly ILocalizationManager _localization;
+ private readonly IMediaSourceManager _mediaSourceManager;
+ private readonly IUserViewManager _userViewManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly ITVSeriesManager _tvSeriesManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ /// The to use in the instance.
+ public ContentDirectoryService(
+ IDlnaManager dlna,
+ IUserDataManager userDataManager,
+ IImageProcessor imageProcessor,
+ ILibraryManager libraryManager,
+ IServerConfigurationManager config,
+ IUserManager userManager,
+ ILogger logger,
+ IHttpClientFactory httpClient,
+ ILocalizationManager localization,
+ IMediaSourceManager mediaSourceManager,
+ IUserViewManager userViewManager,
+ IMediaEncoder mediaEncoder,
+ ITVSeriesManager tvSeriesManager)
+ : base(logger, httpClient)
+ {
+ _dlna = dlna;
+ _userDataManager = userDataManager;
+ _imageProcessor = imageProcessor;
+ _libraryManager = libraryManager;
+ _config = config;
+ _userManager = userManager;
+ _localization = localization;
+ _mediaSourceManager = mediaSourceManager;
+ _userViewManager = userViewManager;
+ _mediaEncoder = mediaEncoder;
+ _tvSeriesManager = tvSeriesManager;
+ }
+
+ ///
+ /// Gets the system id. (A unique id which changes on when our definition changes.)
+ ///
+ private static int SystemUpdateId
+ {
+ get
+ {
+ var now = DateTime.UtcNow;
+
+ return now.Year + now.DayOfYear + now.Hour;
+ }
+ }
+
+ ///
+ public string GetServiceXml()
+ {
+ return ContentDirectoryXmlBuilder.GetXml();
+ }
+
+ ///
+ public Task ProcessControlRequestAsync(ControlRequest request)
+ {
+ if (request == null)
+ {
+ throw new ArgumentNullException(nameof(request));
+ }
+
+ var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile();
+
+ var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
+
+ var user = GetUser(profile);
+
+ return new ControlHandler(
+ Logger,
+ _libraryManager,
+ profile,
+ serverAddress,
+ null,
+ _imageProcessor,
+ _userDataManager,
+ user,
+ SystemUpdateId,
+ _config,
+ _localization,
+ _mediaSourceManager,
+ _userViewManager,
+ _mediaEncoder,
+ _tvSeriesManager)
+ .ProcessControlRequestAsync(request);
+ }
+
+ ///
+ /// Get the user stored in the device profile.
+ ///
+ /// The .
+ /// The .
+ private User? GetUser(DeviceProfile profile)
+ {
+ if (!string.IsNullOrEmpty(profile.UserId))
+ {
+ var user = _userManager.GetUserById(Guid.Parse(profile.UserId));
+
+ if (user != null)
+ {
+ return user;
+ }
+ }
+
+ var userId = _config.GetDlnaConfiguration().DefaultUserId;
+
+ if (!string.IsNullOrEmpty(userId))
+ {
+ var user = _userManager.GetUserById(Guid.Parse(userId));
+
+ if (user != null)
+ {
+ return user;
+ }
+ }
+
+ foreach (var user in _userManager.Users)
+ {
+ if (user.HasPermission(PermissionKind.IsAdministrator))
+ {
+ return user;
+ }
+ }
+
+ return _userManager.Users.FirstOrDefault();
+ }
+ }
+}
diff --git a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs
index 15fdb36c4c..3edaabb70e 100644
--- a/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs
+++ b/Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs
@@ -1,145 +1,159 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
namespace Emby.Dlna.ContentDirectory
{
- public class ContentDirectoryXmlBuilder
+ ///
+ /// Defines the .
+ ///
+ public static class ContentDirectoryXmlBuilder
{
- public string GetXml()
+ ///
+ /// Gets the ContentDirectory:1 service template.
+ /// See http://upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf.
+ ///
+ /// An XML description of this service.
+ public static string GetXml()
{
- return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(),
- GetStateVariables());
+ return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables());
}
+ ///
+ /// Get the list of state variables for this invocation.
+ ///
+ /// The .
private static IEnumerable GetStateVariables()
{
- var list = new List();
-
- list.Add(new StateVariable
+ var list = new List
{
- Name = "A_ARG_TYPE_Filter",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_Filter",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_SortCriteria",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_SortCriteria",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_Index",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_Index",
+ DataType = "ui4",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_Count",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_Count",
+ DataType = "ui4",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_UpdateID",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_UpdateID",
+ DataType = "ui4",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "SearchCapabilities",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "SearchCapabilities",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "SortCapabilities",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "SortCapabilities",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "SystemUpdateID",
- DataType = "ui4",
- SendsEvents = true
- });
+ new StateVariable
+ {
+ Name = "SystemUpdateID",
+ DataType = "ui4",
+ SendsEvents = true
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_SearchCriteria",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_SearchCriteria",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_Result",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_Result",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_ObjectID",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_ObjectID",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_BrowseFlag",
- DataType = "string",
- SendsEvents = false,
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_BrowseFlag",
+ DataType = "string",
+ SendsEvents = false,
- AllowedValues = new string[]
+ AllowedValues = new[]
{
"BrowseMetadata",
"BrowseDirectChildren"
}
- });
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_BrowseLetter",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_BrowseLetter",
+ DataType = "string",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_CategoryType",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_CategoryType",
+ DataType = "ui4",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_RID",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_RID",
+ DataType = "ui4",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_PosSec",
- DataType = "ui4",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_PosSec",
+ DataType = "ui4",
+ SendsEvents = false
+ },
- list.Add(new StateVariable
- {
- Name = "A_ARG_TYPE_Featurelist",
- DataType = "string",
- SendsEvents = false
- });
+ new StateVariable
+ {
+ Name = "A_ARG_TYPE_Featurelist",
+ DataType = "string",
+ SendsEvents = false
+ }
+ };
return list;
}
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 1150afdbab..ac336e5dcc 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -1,3 +1,5 @@
+#nullable disable
+
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -8,6 +10,8 @@ using System.Threading;
using System.Xml;
using Emby.Dlna.Didl;
using Emby.Dlna.Service;
+using Jellyfin.Data.Entities;
+using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Drawing;
@@ -15,7 +19,6 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
@@ -25,13 +28,26 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Xml;
using Microsoft.Extensions.Logging;
+using Book = MediaBrowser.Controller.Entities.Book;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
+using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
+using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace Emby.Dlna.ContentDirectory
{
+ ///
+ /// Defines the .
+ ///
public class ControlHandler : BaseControlHandler
{
+ private const string NsDc = "http://purl.org/dc/elements/1.1/";
+ private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
+ private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/";
+ private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
+
private readonly ILibraryManager _libraryManager;
private readonly IUserDataManager _userDataManager;
private readonly IServerConfigurationManager _config;
@@ -39,20 +55,47 @@ namespace Emby.Dlna.ContentDirectory
private readonly IUserViewManager _userViewManager;
private readonly ITVSeriesManager _tvSeriesManager;
- private const string NS_DC = "http://purl.org/dc/elements/1.1/";
- private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
- private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
- private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
-
private readonly int _systemUpdateId;
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly DidlBuilder _didlBuilder;
private readonly DeviceProfile _profile;
- public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager)
- : base(config, logger, xmlReaderSettingsFactory)
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The server address to use in this instance> for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The system id for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance.
+ /// The for use with the instance.
+ public ControlHandler(
+ ILogger logger,
+ ILibraryManager libraryManager,
+ DeviceProfile profile,
+ string serverAddress,
+ string accessToken,
+ IImageProcessor imageProcessor,
+ IUserDataManager userDataManager,
+ User user,
+ int systemUpdateId,
+ IServerConfigurationManager config,
+ ILocalizationManager localization,
+ IMediaSourceManager mediaSourceManager,
+ IUserViewManager userViewManager,
+ IMediaEncoder mediaEncoder,
+ ITVSeriesManager tvSeriesManager)
+ : base(config, logger)
{
_libraryManager = libraryManager;
_userDataManager = userDataManager;
@@ -63,149 +106,210 @@ namespace Emby.Dlna.ContentDirectory
_profile = profile;
_config = config;
- _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, mediaEncoder);
+ _didlBuilder = new DidlBuilder(
+ profile,
+ user,
+ imageProcessor,
+ serverAddress,
+ accessToken,
+ userDataManager,
+ localization,
+ mediaSourceManager,
+ Logger,
+ mediaEncoder,
+ libraryManager);
}
- protected override IEnumerable> GetResult(string methodName, IDictionary methodParams)
+ ///
+ protected override void WriteResult(string methodName, IReadOnlyDictionary methodParams, XmlWriter xmlWriter)
{
- var deviceId = "test";
+ if (xmlWriter == null)
+ {
+ throw new ArgumentNullException(nameof(xmlWriter));
+ }
- var user = _user;
+ if (methodParams == null)
+ {
+ throw new ArgumentNullException(nameof(methodParams));
+ }
+
+ const string DeviceId = "test";
if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
- return HandleGetSearchCapabilities();
+ {
+ HandleGetSearchCapabilities(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
- return HandleGetSortCapabilities();
+ {
+ HandleGetSortCapabilities(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "GetSortExtensionCapabilities", StringComparison.OrdinalIgnoreCase))
- return HandleGetSortExtensionCapabilities();
+ {
+ HandleGetSortExtensionCapabilities(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
- return HandleGetSystemUpdateID();
+ {
+ HandleGetSystemUpdateID(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase))
- return HandleBrowse(methodParams, user, deviceId);
+ {
+ HandleBrowse(xmlWriter, methodParams, DeviceId);
+ return;
+ }
if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
- return HandleXGetFeatureList();
+ {
+ HandleXGetFeatureList(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "GetFeatureList", StringComparison.OrdinalIgnoreCase))
- return HandleGetFeatureList();
+ {
+ HandleGetFeatureList(xmlWriter);
+ return;
+ }
if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
- return HandleXSetBookmark(methodParams, user);
+ {
+ HandleXSetBookmark(methodParams);
+ return;
+ }
if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase))
- return HandleSearch(methodParams, user, deviceId);
+ {
+ HandleSearch(xmlWriter, methodParams, DeviceId);
+ return;
+ }
if (string.Equals(methodName, "X_BrowseByLetter", StringComparison.OrdinalIgnoreCase))
- return HandleX_BrowseByLetter(methodParams, user, deviceId);
+ {
+ HandleXBrowseByLetter(xmlWriter, methodParams, DeviceId);
+ return;
+ }
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
- private IEnumerable> HandleXSetBookmark(IDictionary sparams, User user)
+ ///
+ /// Adds a "XSetBookmark" element to the xml document.
+ ///
+ /// The method parameters.
+ private void HandleXSetBookmark(IReadOnlyDictionary sparams)
{
var id = sparams["ObjectID"];
- var serverItem = GetItemFromObjectId(id, user);
+ var serverItem = GetItemFromObjectId(id);
var item = serverItem.Item;
- var newbookmark = int.Parse(sparams["PosSecond"], _usCulture);
+ var newbookmark = int.Parse(sparams["PosSecond"], CultureInfo.InvariantCulture);
- var userdata = _userDataManager.GetUserData(user, item);
+ var userdata = _userDataManager.GetUserData(_user, item);
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
- _userDataManager.SaveUserData(user, item, userdata, UserDataSaveReason.TogglePlayed,
+ _userDataManager.SaveUserData(
+ _user,
+ item,
+ userdata,
+ UserDataSaveReason.TogglePlayed,
CancellationToken.None);
-
- return new Dictionary(StringComparer.OrdinalIgnoreCase);
}
- private IEnumerable> HandleGetSearchCapabilities()
+ ///
+ /// Adds the "SearchCaps" element to the xml document.
+ ///
+ /// The .
+ private static void HandleGetSearchCapabilities(XmlWriter xmlWriter)
{
- return new Dictionary(StringComparer.OrdinalIgnoreCase)
- {
- { "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" }
- };
+ xmlWriter.WriteElementString(
+ "SearchCaps",
+ "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords");
}
- private IEnumerable> HandleGetSortCapabilities()
+ ///
+ /// Adds the "SortCaps" element to the xml document.
+ ///
+ /// The .
+ private static void HandleGetSortCapabilities(XmlWriter xmlWriter)
{
- return new Dictionary(StringComparer.OrdinalIgnoreCase)
- {
- { "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" }
- };
+ xmlWriter.WriteElementString(
+ "SortCaps",
+ "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
}
- private IEnumerable> HandleGetSortExtensionCapabilities()
+ ///
+ /// Adds the "SortExtensionCaps" element to the xml document.
+ ///
+ /// The .
+ private static void HandleGetSortExtensionCapabilities(XmlWriter xmlWriter)
{
- return new Dictionary(StringComparer.OrdinalIgnoreCase)
- {
- { "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" }
- };
+ xmlWriter.WriteElementString(
+ "SortExtensionCaps",
+ "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
}
- private IEnumerable> HandleGetSystemUpdateID()
+ ///
+ /// Adds the "Id" element to the xml document.
+ ///
+ /// The .
+ private void HandleGetSystemUpdateID(XmlWriter xmlWriter)
{
- var headers = new Dictionary(StringComparer.OrdinalIgnoreCase);
- headers.Add("Id", _systemUpdateId.ToString(_usCulture));
- return headers;
+ xmlWriter.WriteElementString("Id", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
}
- private IEnumerable> HandleGetFeatureList()
+ ///
+ /// Adds the "FeatureList" element to the xml document.
+ ///
+ /// The .
+ private static void HandleGetFeatureList(XmlWriter xmlWriter)
{
- return new Dictionary(StringComparer.OrdinalIgnoreCase)
- {
- { "FeatureList", GetFeatureListXml() }
- };
+ xmlWriter.WriteElementString("FeatureList", WriteFeatureListXml());
}
- private IEnumerable> HandleXGetFeatureList()
+ ///
+ /// Adds the "FeatureList" element to the xml document.
+ ///
+ /// The .
+ private static void HandleXGetFeatureList(XmlWriter xmlWriter)
+ => HandleGetFeatureList(xmlWriter);
+
+ ///
+ /// Builds a static feature list.
+ ///
+ /// The xml feature list.
+ private static string WriteFeatureListXml()
{
- return new Dictionary(StringComparer.OrdinalIgnoreCase)
- {
- { "FeatureList", GetFeatureListXml() }
- };
+ return ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + "";
}
- private string GetFeatureListXml()
- {
- var builder = new StringBuilder();
-
- builder.Append("");
- builder.Append("");
-
- builder.Append("");
- builder.Append("");
- builder.Append("");
- builder.Append("");
- builder.Append("");
-
- builder.Append("");
-
- return builder.ToString();
- }
-
- public string GetValueOrDefault(IDictionary sparams, string key, string defaultValue)
- {
- if (sparams.TryGetValue(key, out string val))
- {
- return val;
- }
-
- return defaultValue;
- }
-
- private IEnumerable> HandleBrowse(IDictionary sparams, User user, string deviceId)
+ ///
+ /// Builds the "Browse" xml response.
+ ///
+ /// The .
+ /// The method parameters.
+ /// The device Id to use.
+ private void HandleBrowse(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId)
{
var id = sparams["ObjectID"];
var flag = sparams["BrowseFlag"];
- var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
- var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
+ var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
+ var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty));
var provided = 0;
@@ -224,104 +328,108 @@ namespace Emby.Dlna.ContentDirectory
start = startVal;
}
- var settings = new XmlWriterSettings
- {
- Encoding = Encoding.UTF8,
- CloseOutput = false,
- OmitXmlDeclaration = true,
- ConformanceLevel = ConformanceLevel.Fragment
- };
-
- StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
-
int totalCount;
- var dlnaOptions = _config.GetDlnaConfiguration();
-
- using (var writer = XmlWriter.Create(builder, settings))
+ using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
{
- //writer.WriteStartDocument();
-
- writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
-
- writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
- writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
- writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
- //didl.SetAttribute("xmlns:sec", NS_SEC);
-
- DidlBuilder.WriteXmlRootAttributes(_profile, writer);
-
- var serverItem = GetItemFromObjectId(id, user);
- var item = serverItem.Item;
-
- if (string.Equals(flag, "BrowseMetadata"))
+ var settings = new XmlWriterSettings()
{
- totalCount = 1;
+ Encoding = Encoding.UTF8,
+ CloseOutput = false,
+ OmitXmlDeclaration = true,
+ ConformanceLevel = ConformanceLevel.Fragment
+ };
- if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
- {
- var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
-
- _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
- }
- else
- {
- _didlBuilder.WriteItemElement(dlnaOptions, writer, item, user, null, null, deviceId, filter);
- }
-
- provided++;
- }
- else
+ using (var writer = XmlWriter.Create(builder, settings))
{
- var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
- totalCount = childrenResult.TotalRecordCount;
+ writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
- provided = childrenResult.Items.Length;
+ writer.WriteAttributeString("xmlns", "dc", null, NsDc);
+ writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
+ writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
- foreach (var i in childrenResult.Items)
+ DidlBuilder.WriteXmlRootAttributes(_profile, writer);
+
+ var serverItem = GetItemFromObjectId(id);
+ var item = serverItem.Item;
+
+ if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal))
{
- var childItem = i.Item;
- var displayStubType = i.StubType;
+ totalCount = 1;
- if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
+ if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
{
- var childCount = (GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0))
- .TotalRecordCount;
+ var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
- _didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
+ _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
}
else
{
- _didlBuilder.WriteItemElement(dlnaOptions, writer, childItem, user, item, serverItem.StubType, deviceId, filter);
+ _didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter);
+ }
+
+ provided++;
+ }
+ else
+ {
+ var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
+ totalCount = childrenResult.TotalRecordCount;
+
+ provided = childrenResult.Items.Count;
+
+ foreach (var i in childrenResult.Items)
+ {
+ var childItem = i.Item;
+ var displayStubType = i.StubType;
+
+ if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
+ {
+ var childCount = GetUserItems(childItem, displayStubType, _user, sortCriteria, null, 0)
+ .TotalRecordCount;
+
+ _didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
+ }
+ else
+ {
+ _didlBuilder.WriteItemElement(writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
+ }
}
}
+
+ writer.WriteFullEndElement();
}
- writer.WriteFullEndElement();
- //writer.WriteEndDocument();
+
+ xmlWriter.WriteElementString("Result", builder.ToString());
}
- var resXML = builder.ToString();
-
- return new[]
- {
- new KeyValuePair("Result", resXML),
- new KeyValuePair("NumberReturned", provided.ToString(_usCulture)),
- new KeyValuePair("TotalMatches", totalCount.ToString(_usCulture)),
- new KeyValuePair("UpdateID", _systemUpdateId.ToString(_usCulture))
- };
+ xmlWriter.WriteElementString("NumberReturned", provided.ToString(CultureInfo.InvariantCulture));
+ xmlWriter.WriteElementString("TotalMatches", totalCount.ToString(CultureInfo.InvariantCulture));
+ xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
}
- private IEnumerable> HandleX_BrowseByLetter(IDictionary sparams, User user, string deviceId)
+ ///
+ /// Builds the response to the "X_BrowseByLetter request.
+ ///
+ /// The .
+ /// The method parameters.
+ /// The device id.
+ private void HandleXBrowseByLetter(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId)
{
// TODO: Implement this method
- return HandleSearch(sparams, user, deviceId);
+ HandleSearch(xmlWriter, sparams, deviceId);
}
- private IEnumerable> HandleSearch(IDictionary sparams, User user, string deviceId)
+ ///
+ /// Builds a response to the "Search" request.
+ ///
+ /// The xmlWriter.
+ /// The method parameters.
+ /// The deviceId.
+ private void HandleSearch(XmlWriter xmlWriter, IReadOnlyDictionary sparams, string deviceId)
{
- var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", ""));
- var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
- var filter = new Filter(GetValueOrDefault(sparams, "Filter", "*"));
+ var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", string.Empty));
+ var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty));
+ var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
// sort example: dc:title, dc:date
@@ -340,109 +448,106 @@ namespace Emby.Dlna.ContentDirectory
start = startVal;
}
- var settings = new XmlWriterSettings
+ QueryResult childrenResult;
+
+ using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
{
- Encoding = Encoding.UTF8,
- CloseOutput = false,
- OmitXmlDeclaration = true,
- ConformanceLevel = ConformanceLevel.Fragment
- };
-
- StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
- int totalCount = 0;
- int provided = 0;
-
- using (var writer = XmlWriter.Create(builder, settings))
- {
- //writer.WriteStartDocument();
-
- writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
-
- writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
- writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
- writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
- //didl.SetAttribute("xmlns:sec", NS_SEC);
-
- DidlBuilder.WriteXmlRootAttributes(_profile, writer);
-
- var serverItem = GetItemFromObjectId(sparams["ContainerID"], user);
-
- var item = serverItem.Item;
-
- var childrenResult = (GetChildrenSorted(item, user, searchCriteria, sortCriteria, start, requestedCount));
-
- totalCount = childrenResult.TotalRecordCount;
-
- provided = childrenResult.Items.Length;
-
- var dlnaOptions = _config.GetDlnaConfiguration();
-
- foreach (var i in childrenResult.Items)
+ var settings = new XmlWriterSettings()
{
- if (i.IsDisplayedAsFolder)
- {
- var childCount = (GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0))
- .TotalRecordCount;
+ Encoding = Encoding.UTF8,
+ CloseOutput = false,
+ OmitXmlDeclaration = true,
+ ConformanceLevel = ConformanceLevel.Fragment
+ };
- _didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter);
- }
- else
+ using (var writer = XmlWriter.Create(builder, settings))
+ {
+ writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
+
+ writer.WriteAttributeString("xmlns", "dc", null, NsDc);
+ writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
+ writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
+
+ DidlBuilder.WriteXmlRootAttributes(_profile, writer);
+
+ var serverItem = GetItemFromObjectId(sparams["ContainerID"]);
+
+ var item = serverItem.Item;
+
+ childrenResult = GetChildrenSorted(item, _user, searchCriteria, sortCriteria, start, requestedCount);
+
+ var dlnaOptions = _config.GetDlnaConfiguration();
+
+ foreach (var i in childrenResult.Items)
{
- _didlBuilder.WriteItemElement(dlnaOptions, writer, i, user, item, serverItem.StubType, deviceId, filter);
+ if (i.IsDisplayedAsFolder)
+ {
+ var childCount = GetChildrenSorted(i, _user, searchCriteria, sortCriteria, null, 0)
+ .TotalRecordCount;
+
+ _didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter);
+ }
+ else
+ {
+ _didlBuilder.WriteItemElement(writer, i, _user, item, serverItem.StubType, deviceId, filter);
+ }
}
+
+ writer.WriteFullEndElement();
}
- writer.WriteFullEndElement();
- //writer.WriteEndDocument();
+ xmlWriter.WriteElementString("Result", builder.ToString());
}
- var resXML = builder.ToString();
-
- return new List>
- {
- new KeyValuePair("Result", resXML),
- new KeyValuePair("NumberReturned", provided.ToString(_usCulture)),
- new KeyValuePair("TotalMatches", totalCount.ToString(_usCulture)),
- new KeyValuePair("UpdateID", _systemUpdateId.ToString(_usCulture))
- };
+ xmlWriter.WriteElementString("NumberReturned", childrenResult.Items.Count.ToString(CultureInfo.InvariantCulture));
+ xmlWriter.WriteElementString("TotalMatches", childrenResult.TotalRecordCount.ToString(CultureInfo.InvariantCulture));
+ xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
}
- private QueryResult GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
+ ///
+ /// Returns the child items meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// The .
+ private static QueryResult GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
{
var folder = (Folder)item;
- var sortOrders = new List();
- if (!folder.IsPreSorted)
- {
- sortOrders.Add(ItemSortBy.SortName);
- }
+ var sortOrders = folder.IsPreSorted
+ ? Array.Empty<(string, SortOrder)>()
+ : new[] { (ItemSortBy.SortName, sort.SortOrder) };
- var mediaTypes = new List();
+ string[] mediaTypes = Array.Empty();
bool? isFolder = null;
if (search.SearchType == SearchType.Audio)
{
- mediaTypes.Add(MediaType.Audio);
+ mediaTypes = new[] { MediaType.Audio };
isFolder = false;
}
else if (search.SearchType == SearchType.Video)
{
- mediaTypes.Add(MediaType.Video);
+ mediaTypes = new[] { MediaType.Video };
isFolder = false;
}
else if (search.SearchType == SearchType.Image)
{
- mediaTypes.Add(MediaType.Photo);
+ mediaTypes = new[] { MediaType.Photo };
isFolder = false;
}
else if (search.SearchType == SearchType.Playlist)
{
- //items = items.OfType();
+ // items = items.OfType();
isFolder = true;
}
else if (search.SearchType == SearchType.MusicAlbum)
{
- //items = items.OfType();
+ // items = items.OfType();
isFolder = true;
}
@@ -450,22 +555,36 @@ namespace Emby.Dlna.ContentDirectory
{
Limit = limit,
StartIndex = startIndex,
- OrderBy = sortOrders.Select(i => new ValueTuple(i, sort.SortOrder)).ToArray(),
+ OrderBy = sortOrders,
User = user,
Recursive = true,
IsMissing = false,
- ExcludeItemTypes = new[] { typeof(Book).Name },
+ ExcludeItemTypes = new[] { nameof(Book) },
IsFolder = isFolder,
- MediaTypes = mediaTypes.ToArray(),
+ MediaTypes = mediaTypes,
DtoOptions = GetDtoOptions()
});
}
- private DtoOptions GetDtoOptions()
+ ///
+ /// Returns a new DtoOptions object.
+ ///
+ /// The .
+ private static DtoOptions GetDtoOptions()
{
return new DtoOptions(true);
}
+ ///
+ /// Returns the User items meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// The .
private QueryResult GetUserItems(BaseItem item, StubType? stubType, User user, SortCriteria sort, int? startIndex, int? limit)
{
if (item is MusicGenre)
@@ -500,11 +619,11 @@ namespace Emby.Dlna.ContentDirectory
}
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
- return GetFolders(item, user, stubType, sort, startIndex, limit);
+ return GetFolders(user, startIndex, limit);
}
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
{
- return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit);
+ return GetLiveTvChannels(user, sort, startIndex, limit);
}
}
@@ -523,7 +642,7 @@ namespace Emby.Dlna.ContentDirectory
Limit = limit,
StartIndex = startIndex,
IsVirtualItem = false,
- ExcludeItemTypes = new[] { typeof(Book).Name },
+ ExcludeItemTypes = new[] { nameof(Book) },
IsPlaceHolder = false,
DtoOptions = GetDtoOptions()
};
@@ -535,14 +654,22 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(queryResult);
}
- private QueryResult GetLiveTvChannels(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
+ ///
+ /// Returns the Live Tv Channels meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// The .
+ private QueryResult GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit)
{
var query = new InternalItemsQuery(user)
{
StartIndex = startIndex,
Limit = limit,
};
- query.IncludeItemTypes = new[] { typeof(LiveTvChannel).Name };
+ query.IncludeItemTypes = new[] { nameof(LiveTvChannel) };
SetSorting(query, sort, false);
@@ -551,6 +678,16 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
+ ///
+ /// Returns the music folders meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// The .
private QueryResult GetMusicFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
{
var query = new InternalItemsQuery(user)
@@ -567,7 +704,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.Playlists)
{
- return GetMusicPlaylists(item, user, query);
+ return GetMusicPlaylists(user, query);
}
if (stubType.HasValue && stubType.Value == StubType.Albums)
@@ -610,65 +747,76 @@ namespace Emby.Dlna.ContentDirectory
return GetMusicGenres(item, user, query);
}
- var list = new List();
-
- list.Add(new ServerItem(item)
+ var list = new List
{
- StubType = StubType.Latest
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.Latest
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Playlists
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.Playlists
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Albums
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.Albums
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.AlbumArtists
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.AlbumArtists
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Artists
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.Artists
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Songs
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.Songs
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Genres
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.Genres
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.FavoriteArtists
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.FavoriteArtists
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.FavoriteAlbums
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.FavoriteAlbums
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.FavoriteSongs
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.FavoriteSongs
+ }
+ };
return new QueryResult
{
- Items = list.ToArray(),
+ Items = list,
TotalRecordCount = list.Count
};
}
+ ///
+ /// Returns the movie folders meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// The .
private QueryResult GetMovieFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
{
var query = new InternalItemsQuery(user)
@@ -695,7 +843,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.Collections)
{
- return GetMovieCollections(item, user, query);
+ return GetMovieCollections(user, query);
}
if (stubType.HasValue && stubType.Value == StubType.Favorites)
@@ -708,46 +856,49 @@ namespace Emby.Dlna.ContentDirectory
return GetGenres(item, user, query);
}
- var list = new List();
-
- list.Add(new ServerItem(item)
+ var array = new[]
{
- StubType = StubType.ContinueWatching
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Latest
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Movies
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Collections
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Favorites
- });
-
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Genres
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.ContinueWatching
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Latest
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Movies
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Collections
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Favorites
+ },
+ new ServerItem(item)
+ {
+ StubType = StubType.Genres
+ }
+ };
return new QueryResult
{
- Items = list.ToArray(),
- TotalRecordCount = list.Count
+ Items = array,
+ TotalRecordCount = array.Length
};
}
- private QueryResult GetFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
+ ///
+ /// Returns the folders meeting the criteria.
+ ///
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// The .
+ private QueryResult GetFolders(User user, int? startIndex, int? limit)
{
var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true)
.OrderBy(i => i.SortName)
@@ -757,13 +908,26 @@ namespace Emby.Dlna.ContentDirectory
})
.ToArray();
- return new QueryResult
- {
- Items = folders,
- TotalRecordCount = folders.Length
- };
+ return ApplyPaging(
+ new QueryResult
+ {
+ Items = folders,
+ TotalRecordCount = folders.Length
+ },
+ startIndex,
+ limit);
}
+ ///
+ /// Returns the TV folders meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// The .
private QueryResult GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
{
var query = new InternalItemsQuery(user)
@@ -780,7 +944,7 @@ namespace Emby.Dlna.ContentDirectory
if (stubType.HasValue && stubType.Value == StubType.NextUp)
{
- return GetNextUp(item, user, query);
+ return GetNextUp(item, query);
}
if (stubType.HasValue && stubType.Value == StubType.Latest)
@@ -808,60 +972,68 @@ namespace Emby.Dlna.ContentDirectory
return GetGenres(item, user, query);
}
- var list = new List();
-
- list.Add(new ServerItem(item)
+ var list = new List
{
- StubType = StubType.ContinueWatching
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.ContinueWatching
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.NextUp
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.NextUp
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Latest
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.Latest
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Series
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.Series
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.FavoriteSeries
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.FavoriteSeries
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.FavoriteEpisodes
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.FavoriteEpisodes
+ },
- list.Add(new ServerItem(item)
- {
- StubType = StubType.Genres
- });
+ new ServerItem(item)
+ {
+ StubType = StubType.Genres
+ }
+ };
return new QueryResult
{
- Items = list.ToArray(),
+ Items = list,
TotalRecordCount = list.Count
};
}
+ ///
+ /// Returns the Movies that are part watched that meet the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetMovieContinueWatching(BaseItem parent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
- query.OrderBy = new ValueTuple[]
+ query.OrderBy = new[]
{
- new ValueTuple (ItemSortBy.DatePlayed, SortOrder.Descending),
- new ValueTuple (ItemSortBy.SortName, SortOrder.Ascending)
+ (ItemSortBy.DatePlayed, SortOrder.Descending),
+ (ItemSortBy.SortName, SortOrder.Ascending)
};
query.IsResumable = true;
@@ -872,136 +1044,213 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
+ ///
+ /// Returns the series meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetSeries(BaseItem parent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
- query.IncludeItemTypes = new[] { typeof(Series).Name };
+ query.IncludeItemTypes = new[] { nameof(Series) };
var result = _libraryManager.GetItemsResult(query);
return ToResult(result);
}
+ ///
+ /// Returns the Movie folders meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetMovieMovies(BaseItem parent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
- query.IncludeItemTypes = new[] { typeof(Movie).Name };
+ query.IncludeItemTypes = new[] { nameof(Movie) };
var result = _libraryManager.GetItemsResult(query);
return ToResult(result);
}
- private QueryResult GetMovieCollections(BaseItem parent, User user, InternalItemsQuery query)
+ ///
+ /// Returns the Movie collections meeting the criteria.
+ ///
+ /// The see cref="User"/>.
+ /// The see cref="InternalItemsQuery"/>.
+ /// The .
+ private QueryResult GetMovieCollections(User user, InternalItemsQuery query)
{
query.Recursive = true;
- //query.Parent = parent;
+ // query.Parent = parent;
query.SetUser(user);
- query.IncludeItemTypes = new[] { typeof(BoxSet).Name };
+ query.IncludeItemTypes = new[] { nameof(BoxSet) };
var result = _libraryManager.GetItemsResult(query);
return ToResult(result);
}
+ ///
+ /// Returns the Music albums meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetMusicAlbums(BaseItem parent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
- query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
+ query.IncludeItemTypes = new[] { nameof(MusicAlbum) };
var result = _libraryManager.GetItemsResult(query);
return ToResult(result);
}
+ ///
+ /// Returns the Music songs meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetMusicSongs(BaseItem parent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
- query.IncludeItemTypes = new[] { typeof(Audio).Name };
+ query.IncludeItemTypes = new[] { nameof(Audio) };
var result = _libraryManager.GetItemsResult(query);
return ToResult(result);
}
+ ///
+ /// Returns the songs tagged as favourite that meet the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetFavoriteSongs(BaseItem parent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
- query.IncludeItemTypes = new[] { typeof(Audio).Name };
+ query.IncludeItemTypes = new[] { nameof(Audio) };
var result = _libraryManager.GetItemsResult(query);
return ToResult(result);
}
+ ///
+ /// Returns the series tagged as favourite that meet the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetFavoriteSeries(BaseItem parent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
- query.IncludeItemTypes = new[] { typeof(Series).Name };
+ query.IncludeItemTypes = new[] { nameof(Series) };
var result = _libraryManager.GetItemsResult(query);
return ToResult(result);
}
+ ///
+ /// Returns the episodes tagged as favourite that meet the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetFavoriteEpisodes(BaseItem parent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
- query.IncludeItemTypes = new[] { typeof(Episode).Name };
+ query.IncludeItemTypes = new[] { nameof(Episode) };
var result = _libraryManager.GetItemsResult(query);
return ToResult(result);
}
+ ///
+ /// Returns the movies tagged as favourite that meet the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetMovieFavorites(BaseItem parent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
- query.IncludeItemTypes = new[] { typeof(Movie).Name };
+ query.IncludeItemTypes = new[] { nameof(Movie) };
var result = _libraryManager.GetItemsResult(query);
return ToResult(result);
}
+ ///
+ /// /// Returns the albums tagged as favourite that meet the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetFavoriteAlbums(BaseItem parent, User user, InternalItemsQuery query)
{
query.Recursive = true;
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
- query.IncludeItemTypes = new[] { typeof(MusicAlbum).Name };
+ query.IncludeItemTypes = new[] { nameof(MusicAlbum) };
var result = _libraryManager.GetItemsResult(query);
return ToResult(result);
}
+ ///
+ /// Returns the genres meeting the criteria.
+ /// The GetGenres.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetGenres(BaseItem parent, User user, InternalItemsQuery query)
{
var genresResult = _libraryManager.GetGenres(new InternalItemsQuery(user)
@@ -1020,6 +1269,13 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
+ ///
+ /// Returns the music genres meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetMusicGenres(BaseItem parent, User user, InternalItemsQuery query)
{
var genresResult = _libraryManager.GetMusicGenres(new InternalItemsQuery(user)
@@ -1038,6 +1294,13 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
+ ///
+ /// Returns the music albums by artist that meet the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetMusicAlbumArtists(BaseItem parent, User user, InternalItemsQuery query)
{
var artists = _libraryManager.GetAlbumArtists(new InternalItemsQuery(user)
@@ -1056,6 +1319,13 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
+ ///
+ /// Returns the music artists meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetMusicArtists(BaseItem parent, User user, InternalItemsQuery query)
{
var artists = _libraryManager.GetArtists(new InternalItemsQuery(user)
@@ -1074,6 +1344,13 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
+ ///
+ /// Returns the artists tagged as favourite that meet the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetFavoriteArtists(BaseItem parent, User user, InternalItemsQuery query)
{
var artists = _libraryManager.GetArtists(new InternalItemsQuery(user)
@@ -1093,10 +1370,16 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
- private QueryResult GetMusicPlaylists(BaseItem parent, User user, InternalItemsQuery query)
+ ///
+ /// Returns the music playlists meeting the criteria.
+ ///
+ /// The user.
+ /// The query.
+ /// The .
+ private QueryResult GetMusicPlaylists(User user, InternalItemsQuery query)
{
query.Parent = null;
- query.IncludeItemTypes = new[] { typeof(Playlist).Name };
+ query.IncludeItemTypes = new[] { nameof(Playlist) };
query.SetUser(user);
query.Recursive = true;
@@ -1105,72 +1388,114 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
+ ///
+ /// Returns the latest music meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query)
{
- query.OrderBy = new ValueTuple[] { };
+ query.OrderBy = Array.Empty<(string, SortOrder)>();
- var items = _userViewManager.GetLatestItems(new LatestItemsQuery
- {
- UserId = user.Id,
- Limit = 50,
- IncludeItemTypes = new[] { typeof(Audio).Name },
- ParentId = parent == null ? Guid.Empty : parent.Id,
- GroupItems = true
-
- }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
+ var items = _userViewManager.GetLatestItems(
+ new LatestItemsQuery
+ {
+ UserId = user.Id,
+ Limit = 50,
+ IncludeItemTypes = new[] { nameof(Audio) },
+ ParentId = parent?.Id ?? Guid.Empty,
+ GroupItems = true
+ },
+ query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items);
}
- private QueryResult GetNextUp(BaseItem parent, User user, InternalItemsQuery query)
+ ///
+ /// Returns the next up item meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ private QueryResult GetNextUp(BaseItem parent, InternalItemsQuery query)
{
- query.OrderBy = new ValueTuple[] { };
+ query.OrderBy = Array.Empty<(string, SortOrder)>();
- var result = _tvSeriesManager.GetNextUp(new NextUpQuery
- {
- Limit = query.Limit,
- StartIndex = query.StartIndex,
- UserId = query.User.Id
-
- }, new[] { parent }, query.DtoOptions);
+ var result = _tvSeriesManager.GetNextUp(
+ new NextUpQuery
+ {
+ Limit = query.Limit,
+ StartIndex = query.StartIndex,
+ UserId = query.User.Id
+ },
+ new[] { parent },
+ query.DtoOptions);
return ToResult(result);
}
+ ///
+ /// Returns the latest tv meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetTvLatest(BaseItem parent, User user, InternalItemsQuery query)
{
- query.OrderBy = new ValueTuple[] { };
+ query.OrderBy = Array.Empty<(string, SortOrder)>();
- var items = _userViewManager.GetLatestItems(new LatestItemsQuery
- {
- UserId = user.Id,
- Limit = 50,
- IncludeItemTypes = new[] { typeof(Episode).Name },
- ParentId = parent == null ? Guid.Empty : parent.Id,
- GroupItems = false
-
- }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
+ var items = _userViewManager.GetLatestItems(
+ new LatestItemsQuery
+ {
+ UserId = user.Id,
+ Limit = 50,
+ IncludeItemTypes = new[] { nameof(Episode) },
+ ParentId = parent == null ? Guid.Empty : parent.Id,
+ GroupItems = false
+ },
+ query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items);
}
+ ///
+ /// Returns the latest movies meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
private QueryResult GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query)
{
- query.OrderBy = new ValueTuple[] { };
+ query.OrderBy = Array.Empty<(string, SortOrder)>();
- var items = _userViewManager.GetLatestItems(new LatestItemsQuery
- {
- UserId = user.Id,
- Limit = 50,
- IncludeItemTypes = new[] { typeof(Movie).Name },
- ParentId = parent == null ? Guid.Empty : parent.Id,
- GroupItems = true
-
- }, query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
+ var items = _userViewManager.GetLatestItems(
+ new LatestItemsQuery
+ {
+ UserId = user.Id,
+ Limit = 50,
+ IncludeItemTypes = new[] { nameof(Movie) },
+ ParentId = parent?.Id ?? Guid.Empty,
+ GroupItems = true
+ },
+ query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
return ToResult(items);
}
+ ///
+ /// Returns music artist items that meet the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// The .
private QueryResult GetMusicArtistItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit)
{
var query = new InternalItemsQuery(user)
@@ -1178,7 +1503,7 @@ namespace Emby.Dlna.ContentDirectory
Recursive = true,
ParentId = parentId,
ArtistIds = new[] { item.Id },
- IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
+ IncludeItemTypes = new[] { nameof(MusicAlbum) },
Limit = limit,
StartIndex = startIndex,
DtoOptions = GetDtoOptions()
@@ -1191,6 +1516,16 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
+ ///
+ /// Returns the genre items meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// The .
private QueryResult GetGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit)
{
var query = new InternalItemsQuery(user)
@@ -1198,7 +1533,11 @@ namespace Emby.Dlna.ContentDirectory
Recursive = true,
ParentId = parentId,
GenreIds = new[] { item.Id },
- IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name },
+ IncludeItemTypes = new[]
+ {
+ nameof(Movie),
+ nameof(Series)
+ },
Limit = limit,
StartIndex = startIndex,
DtoOptions = GetDtoOptions()
@@ -1211,6 +1550,16 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
+ ///
+ /// Returns the music genre items meeting the criteria.
+ ///
+ /// The .
+ /// The .
+ /// The .
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// The .
private QueryResult GetMusicGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit)
{
var query = new InternalItemsQuery(user)
@@ -1218,7 +1567,7 @@ namespace Emby.Dlna.ContentDirectory
Recursive = true,
ParentId = parentId,
GenreIds = new[] { item.Id },
- IncludeItemTypes = new[] { typeof(MusicAlbum).Name },
+ IncludeItemTypes = new[] { nameof(MusicAlbum) },
Limit = limit,
StartIndex = startIndex,
DtoOptions = GetDtoOptions()
@@ -1231,7 +1580,12 @@ namespace Emby.Dlna.ContentDirectory
return ToResult(result);
}
- private QueryResult ToResult(BaseItem[] result)
+ ///
+ /// Converts a array into a .
+ ///
+ /// An array of .
+ /// A .
+ private static QueryResult ToResult(BaseItem[] result)
{
var serverItems = result
.Select(i => new ServerItem(i))
@@ -1244,7 +1598,12 @@ namespace Emby.Dlna.ContentDirectory
};
}
- private QueryResult ToResult(QueryResult result)
+ ///
+ /// Converts a to a .
+ ///
+ /// A .
+ /// The .
+ private static QueryResult ToResult(QueryResult result)
{
var serverItems = result
.Items
@@ -1258,42 +1617,65 @@ namespace Emby.Dlna.ContentDirectory
};
}
- private void SetSorting(InternalItemsQuery query, SortCriteria sort, bool isPreSorted)
+ ///
+ /// Sets the sorting method on a query.
+ ///
+ /// The .
+ /// The .
+ /// True if pre-sorted.
+ private static void SetSorting(InternalItemsQuery query, SortCriteria sort, bool isPreSorted)
{
- var sortOrders = new List();
- if (!isPreSorted)
+ if (isPreSorted)
{
- sortOrders.Add(ItemSortBy.SortName);
+ query.OrderBy = Array.Empty<(string, SortOrder)>();
+ }
+ else
+ {
+ query.OrderBy = new[] { (ItemSortBy.SortName, sort.SortOrder) };
}
-
- query.OrderBy = sortOrders.Select(i => new ValueTuple(i, sort.SortOrder)).ToArray();
}
- private QueryResult ApplyPaging(QueryResult result, int? startIndex, int? limit)
+ ///
+ /// Apply paging to a query.
+ ///
+ /// The .
+ /// The start index.
+ /// The maximum number to return.
+ /// A .
+ private static QueryResult ApplyPaging(QueryResult result, int? startIndex, int? limit)
{
result.Items = result.Items.Skip(startIndex ?? 0).Take(limit ?? int.MaxValue).ToArray();
return result;
}
- private ServerItem GetItemFromObjectId(string id, User user)
+ ///
+ /// Retrieves the ServerItem id.
+ ///
+ /// The id.
+ /// The .
+ private ServerItem GetItemFromObjectId(string id)
{
return DidlBuilder.IsIdRoot(id)
-
? new ServerItem(_libraryManager.GetUserRootFolder())
- : ParseItemId(id, user);
+ : ParseItemId(id);
}
- private ServerItem ParseItemId(string id, User user)
+ ///
+ /// Parses the item id into a .
+ ///
+ /// The .
+ /// The corresponding .
+ private ServerItem ParseItemId(string id)
{
StubType? stubType = null;
// After using PlayTo, MediaMonkey sends a request to the server trying to get item info
- const string paramsSrch = "Params=";
- var paramsIndex = id.IndexOf(paramsSrch, StringComparison.OrdinalIgnoreCase);
+ const string ParamsSrch = "Params=";
+ var paramsIndex = id.IndexOf(ParamsSrch, StringComparison.OrdinalIgnoreCase);
if (paramsIndex != -1)
{
- id = id.Substring(paramsIndex + paramsSrch.Length);
+ id = id.Substring(paramsIndex + ParamsSrch.Length);
var parts = id.Split(';');
id = parts[23];
@@ -1304,8 +1686,8 @@ namespace Emby.Dlna.ContentDirectory
{
if (id.StartsWith(name + "_", StringComparison.OrdinalIgnoreCase))
{
- stubType = (StubType)Enum.Parse(typeof(StubType), name, true);
- id = id.Split(new[] { '_' }, 2)[1];
+ stubType = Enum.Parse(name, true);
+ id = id.Split('_', 2)[1];
break;
}
@@ -1321,48 +1703,9 @@ namespace Emby.Dlna.ContentDirectory
};
}
- _logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
+ Logger.LogError("Error parsing item Id: {Id}. Returning user root folder.", id);
return new ServerItem(_libraryManager.GetUserRootFolder());
}
}
-
- internal class ServerItem
- {
- public BaseItem Item { get; set; }
- public StubType? StubType { get; set; }
-
- public ServerItem(BaseItem item)
- {
- Item = item;
-
- if (item is IItemByName && !(item is Folder))
- {
- StubType = Dlna.ContentDirectory.StubType.Folder;
- }
- }
- }
-
- public enum StubType
- {
- Folder = 0,
- Latest = 2,
- Playlists = 3,
- Albums = 4,
- AlbumArtists = 5,
- Artists = 6,
- Songs = 7,
- Genres = 8,
- FavoriteSongs = 9,
- FavoriteArtists = 10,
- FavoriteAlbums = 11,
- ContinueWatching = 12,
- Movies = 13,
- Collections = 14,
- Favorites = 15,
- NextUp = 16,
- Series = 17,
- FavoriteSeries = 18,
- FavoriteEpisodes = 19
- }
}
diff --git a/Emby.Dlna/ContentDirectory/ServerItem.cs b/Emby.Dlna/ContentDirectory/ServerItem.cs
new file mode 100644
index 0000000000..ff30e6e4af
--- /dev/null
+++ b/Emby.Dlna/ContentDirectory/ServerItem.cs
@@ -0,0 +1,36 @@
+#pragma warning disable CS1591
+
+using MediaBrowser.Controller.Entities;
+
+namespace Emby.Dlna.ContentDirectory
+{
+ ///
+ /// Defines the .
+ ///
+ internal class ServerItem
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The .
+ public ServerItem(BaseItem item)
+ {
+ Item = item;
+
+ if (item is IItemByName && item is not Folder)
+ {
+ StubType = Dlna.ContentDirectory.StubType.Folder;
+ }
+ }
+
+ ///
+ /// Gets or sets the underlying base item.
+ ///
+ public BaseItem Item { get; set; }
+
+ ///
+ /// Gets or sets the DLNA item type.
+ ///
+ public StubType? StubType { get; set; }
+ }
+}
diff --git a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs
index e999314fa6..7e3db46519 100644
--- a/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs
+++ b/Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs
@@ -3,9 +3,16 @@ using Emby.Dlna.Common;
namespace Emby.Dlna.ContentDirectory
{
- public class ServiceActionListBuilder
+ ///
+ /// Defines the .
+ ///
+ public static class ServiceActionListBuilder
{
- public IEnumerable GetActions()
+ ///
+ /// Returns a list of services that this instance provides.
+ ///
+ /// An .
+ public static IEnumerable GetActions()
{
return new[]
{
@@ -20,6 +27,10 @@ namespace Emby.Dlna.ContentDirectory
};
}
+ ///
+ /// Returns the action details for "GetSystemUpdateID".
+ ///
+ /// The .
private static ServiceAction GetGetSystemUpdateIDAction()
{
var action = new ServiceAction
@@ -37,6 +48,10 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
+ ///
+ /// Returns the action details for "GetSearchCapabilities".
+ ///
+ /// The .
private static ServiceAction GetSearchCapabilitiesAction()
{
var action = new ServiceAction
@@ -54,6 +69,10 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
+ ///
+ /// Returns the action details for "GetSortCapabilities".
+ ///
+ /// The .
private static ServiceAction GetSortCapabilitiesAction()
{
var action = new ServiceAction
@@ -71,6 +90,10 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
+ ///
+ /// Returns the action details for "X_GetFeatureList".
+ ///
+ /// The .
private static ServiceAction GetX_GetFeatureListAction()
{
var action = new ServiceAction
@@ -88,6 +111,10 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
+ ///
+ /// Returns the action details for "Search".
+ ///
+ /// The .
private static ServiceAction GetSearchAction()
{
var action = new ServiceAction
@@ -168,7 +195,11 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
- private ServiceAction GetBrowseAction()
+ ///
+ /// Returns the action details for "Browse".
+ ///
+ /// The .
+ private static ServiceAction GetBrowseAction()
{
var action = new ServiceAction
{
@@ -248,7 +279,11 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
- private ServiceAction GetBrowseByLetterAction()
+ ///
+ /// Returns the action details for "X_BrowseByLetter".
+ ///
+ /// The .
+ private static ServiceAction GetBrowseByLetterAction()
{
var action = new ServiceAction
{
@@ -335,7 +370,11 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
- private ServiceAction GetXSetBookmarkAction()
+ ///
+ /// Returns the action details for "X_SetBookmark".
+ ///
+ /// The .
+ private static ServiceAction GetXSetBookmarkAction()
{
var action = new ServiceAction
{
diff --git a/Emby.Dlna/ContentDirectory/StubType.cs b/Emby.Dlna/ContentDirectory/StubType.cs
new file mode 100644
index 0000000000..187dc1d75a
--- /dev/null
+++ b/Emby.Dlna/ContentDirectory/StubType.cs
@@ -0,0 +1,30 @@
+#pragma warning disable CS1591
+
+namespace Emby.Dlna.ContentDirectory
+{
+ ///
+ /// Defines the DLNA item types.
+ ///
+ public enum StubType
+ {
+ Folder = 0,
+ Latest = 2,
+ Playlists = 3,
+ Albums = 4,
+ AlbumArtists = 5,
+ Artists = 6,
+ Songs = 7,
+ Genres = 8,
+ FavoriteSongs = 9,
+ FavoriteArtists = 10,
+ FavoriteAlbums = 11,
+ ContinueWatching = 12,
+ Movies = 13,
+ Collections = 14,
+ Favorites = 15,
+ NextUp = 16,
+ Series = 17,
+ FavoriteSeries = 18,
+ FavoriteEpisodes = 19
+ }
+}
diff --git a/Emby.Dlna/ControlRequest.cs b/Emby.Dlna/ControlRequest.cs
index afd9a0b874..8ee6325e9e 100644
--- a/Emby.Dlna/ControlRequest.cs
+++ b/Emby.Dlna/ControlRequest.cs
@@ -1,21 +1,25 @@
-using System.Collections.Generic;
+#nullable disable
+
+#pragma warning disable CS1591
+
using System.IO;
+using Microsoft.AspNetCore.Http;
namespace Emby.Dlna
{
public class ControlRequest
{
- public IDictionary Headers { get; set; }
+ public ControlRequest(IHeaderDictionary headers)
+ {
+ Headers = headers;
+ }
+
+ public IHeaderDictionary Headers { get; }
public Stream InputXml { get; set; }
public string TargetServerUuId { get; set; }
public string RequestedUrl { get; set; }
-
- public ControlRequest()
- {
- Headers = new Dictionary();
- }
}
}
diff --git a/Emby.Dlna/ControlResponse.cs b/Emby.Dlna/ControlResponse.cs
index d2b79fc585..8b09588424 100644
--- a/Emby.Dlna/ControlResponse.cs
+++ b/Emby.Dlna/ControlResponse.cs
@@ -1,18 +1,28 @@
+#pragma warning disable CS1591
+
using System.Collections.Generic;
namespace Emby.Dlna
{
public class ControlResponse
{
- public IDictionary Headers { get; set; }
+ public ControlResponse(string xml, bool isSuccessful)
+ {
+ Headers = new Dictionary();
+ Xml = xml;
+ IsSuccessful = isSuccessful;
+ }
+
+ public IDictionary Headers { get; }
public string Xml { get; set; }
public bool IsSuccessful { get; set; }
- public ControlResponse()
+ ///
+ public override string ToString()
{
- Headers = new Dictionary();
+ return Xml;
}
}
}
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index 605f4f37b2..0a84f30c4c 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -1,38 +1,45 @@
+#nullable disable
+
+#pragma warning disable CS1591
+
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
-using Emby.Dlna.Configuration;
using Emby.Dlna.ContentDirectory;
+using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
+using Episode = MediaBrowser.Controller.Entities.TV.Episode;
+using Genre = MediaBrowser.Controller.Entities.Genre;
+using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
+using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
+using Season = MediaBrowser.Controller.Entities.TV.Season;
+using Series = MediaBrowser.Controller.Entities.TV.Series;
+using XmlAttribute = MediaBrowser.Model.Dlna.XmlAttribute;
namespace Emby.Dlna.Didl
{
public class DidlBuilder
{
- private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
- private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
- private const string NS_DC = "http://purl.org/dc/elements/1.1/";
- private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
- private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
+ private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
+ private const string NsDc = "http://purl.org/dc/elements/1.1/";
+ private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
+ private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/";
private readonly DeviceProfile _profile;
private readonly IImageProcessor _imageProcessor;
@@ -44,6 +51,7 @@ namespace Emby.Dlna.Didl
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger;
private readonly IMediaEncoder _mediaEncoder;
+ private readonly ILibraryManager _libraryManager;
public DidlBuilder(
DeviceProfile profile,
@@ -55,7 +63,8 @@ namespace Emby.Dlna.Didl
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
ILogger logger,
- IMediaEncoder mediaEncoder)
+ IMediaEncoder mediaEncoder,
+ ILibraryManager libraryManager)
{
_profile = profile;
_user = user;
@@ -67,6 +76,7 @@ namespace Emby.Dlna.Didl
_mediaSourceManager = mediaSourceManager;
_logger = logger;
_mediaEncoder = mediaEncoder;
+ _libraryManager = libraryManager;
}
public static string NormalizeDlnaMediaUrl(string url)
@@ -74,7 +84,7 @@ namespace Emby.Dlna.Didl
return url + "&dlnaheaders=true";
}
- public string GetItemDidl(DlnaOptions options, BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
+ public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
{
var settings = new XmlWriterSettings
{
@@ -86,23 +96,24 @@ namespace Emby.Dlna.Didl
using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
{
+ // If this using are changed to single lines, then write.Flush needs to be appended before the return.
using (var writer = XmlWriter.Create(builder, settings))
{
- //writer.WriteStartDocument();
+ // writer.WriteStartDocument();
- writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
+ writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
- writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
- writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
- writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
- //didl.SetAttribute("xmlns:sec", NS_SEC);
+ writer.WriteAttributeString("xmlns", "dc", null, NsDc);
+ writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
+ writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
+ // didl.SetAttribute("xmlns:sec", NS_SEC);
WriteXmlRootAttributes(_profile, writer);
- WriteItemElement(options, writer, item, user, context, null, deviceId, filter, streamInfo);
+ WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
writer.WriteFullEndElement();
- //writer.WriteEndDocument();
+ // writer.WriteEndDocument();
}
return builder.ToString();
@@ -113,7 +124,7 @@ namespace Emby.Dlna.Didl
{
foreach (var att in profile.XmlRootAttributes)
{
- var parts = att.Name.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
+ var parts = att.Name.Split(':', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 2)
{
writer.WriteAttributeString(parts[0], parts[1], null, att.Value);
@@ -126,7 +137,6 @@ namespace Emby.Dlna.Didl
}
public void WriteItemElement(
- DlnaOptions options,
XmlWriter writer,
BaseItem item,
User user,
@@ -138,7 +148,7 @@ namespace Emby.Dlna.Didl
{
var clientId = GetClientId(item, null);
- writer.WriteStartElement(string.Empty, "item", NS_DIDL);
+ writer.WriteStartElement(string.Empty, "item", NsDidl);
writer.WriteAttributeString("restricted", "1");
writer.WriteAttributeString("id", clientId);
@@ -158,43 +168,28 @@ namespace Emby.Dlna.Didl
AddGeneralProperties(item, null, context, writer, filter);
- AddSamsungBookmarkInfo(item, user, writer);
+ AddSamsungBookmarkInfo(item, user, writer, streamInfo);
// refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
- var hasMediaSources = item as IHasMediaSources;
-
- if (hasMediaSources != null)
+ if (item is IHasMediaSources)
{
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
- AddAudioResource(options, writer, item, deviceId, filter, streamInfo);
+ AddAudioResource(writer, item, deviceId, filter, streamInfo);
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
- AddVideoResource(options, writer, item, deviceId, filter, streamInfo);
+ AddVideoResource(writer, item, deviceId, filter, streamInfo);
}
}
- AddCover(item, context, null, writer);
+ AddCover(item, null, writer);
writer.WriteFullEndElement();
}
- private string GetMimeType(string input)
- {
- var mime = MimeTypes.GetMimeType(input);
-
- // TODO: Instead of being hard-coded here, this should probably be moved into all of the existing profiles
- if (string.Equals(mime, "video/mp2t", StringComparison.OrdinalIgnoreCase))
- {
- mime = "video/mpeg";
- }
-
- return mime;
- }
-
- private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
+ private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
if (streamInfo == null)
{
@@ -213,7 +208,9 @@ namespace Emby.Dlna.Didl
var targetWidth = streamInfo.TargetWidth;
var targetHeight = streamInfo.TargetHeight;
- var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
+ var contentFeatureList = ContentFeatureBuilder.BuildVideoHeader(
+ _profile,
+ streamInfo.Container,
streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioCodec.FirstOrDefault(),
targetWidth,
@@ -238,7 +235,7 @@ namespace Emby.Dlna.Didl
foreach (var contentFeature in contentFeatureList)
{
- AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
+ AddVideoResource(writer, filter, contentFeature, streamInfo);
}
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
@@ -285,7 +282,7 @@ namespace Emby.Dlna.Didl
}
else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase))
{
- writer.WriteStartElement(string.Empty, "res", NS_DIDL);
+ writer.WriteStartElement(string.Empty, "res", NsDidl);
writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*");
@@ -294,8 +291,11 @@ namespace Emby.Dlna.Didl
}
else
{
- writer.WriteStartElement(string.Empty, "res", NS_DIDL);
- var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant());
+ writer.WriteStartElement(string.Empty, "res", NsDidl);
+ var protocolInfo = string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:text/{0}:*",
+ info.Format.ToLowerInvariant());
writer.WriteAttributeString("protocolInfo", protocolInfo);
writer.WriteString(info.Url);
@@ -305,9 +305,9 @@ namespace Emby.Dlna.Didl
return true;
}
- private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
+ private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo)
{
- writer.WriteStartElement(string.Empty, "res", NS_DIDL);
+ writer.WriteStartElement(string.Empty, "res", NsDidl);
var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken));
@@ -315,7 +315,7 @@ namespace Emby.Dlna.Didl
if (mediaSource.RunTimeTicks.HasValue)
{
- writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
+ writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture));
}
if (filter.Contains("res@size"))
@@ -326,7 +326,7 @@ namespace Emby.Dlna.Didl
if (size.HasValue)
{
- writer.WriteAttributeString("size", size.Value.ToString(_usCulture));
+ writer.WriteAttributeString("size", size.Value.ToString(CultureInfo.InvariantCulture));
}
}
}
@@ -340,28 +340,35 @@ namespace Emby.Dlna.Didl
if (targetChannels.HasValue)
{
- writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
+ writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(CultureInfo.InvariantCulture));
}
if (filter.Contains("res@resolution"))
{
if (targetWidth.HasValue && targetHeight.HasValue)
{
- writer.WriteAttributeString("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
+ writer.WriteAttributeString(
+ "resolution",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}x{1}",
+ targetWidth.Value,
+ targetHeight.Value));
}
}
if (targetSampleRate.HasValue)
{
- writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
+ writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(CultureInfo.InvariantCulture));
}
if (totalBitrate.HasValue)
{
- writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(_usCulture));
+ writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(CultureInfo.InvariantCulture));
}
- var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
+ var mediaProfile = _profile.GetVideoMediaProfile(
+ streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioBitrate,
@@ -381,17 +388,19 @@ namespace Emby.Dlna.Didl
streamInfo.TargetVideoCodecTag,
streamInfo.IsTargetAVC);
- var filename = url.Substring(0, url.IndexOf('?'));
+ var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
- ? GetMimeType(filename)
+ ? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
- writer.WriteAttributeString("protocolInfo", string.Format(
- "http-get:*:{0}:{1}",
- mimeType,
- contentFeatures
- ));
+ writer.WriteAttributeString(
+ "protocolInfo",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:{0}:{1}",
+ mimeType,
+ contentFeatures));
writer.WriteString(url);
@@ -404,56 +413,123 @@ namespace Emby.Dlna.Didl
{
switch (itemStubType.Value)
{
- case StubType.Latest: return _localization.GetLocalizedString("Latest");
- case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
- case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
- case StubType.Albums: return _localization.GetLocalizedString("Albums");
- case StubType.Artists: return _localization.GetLocalizedString("Artists");
- case StubType.Songs: return _localization.GetLocalizedString("Songs");
- case StubType.Genres: return _localization.GetLocalizedString("Genres");
- case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
- case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
- case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
+ case StubType.Latest: return _localization.GetLocalizedString("Latest");
+ case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
+ case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
+ case StubType.Albums: return _localization.GetLocalizedString("Albums");
+ case StubType.Artists: return _localization.GetLocalizedString("Artists");
+ case StubType.Songs: return _localization.GetLocalizedString("Songs");
+ case StubType.Genres: return _localization.GetLocalizedString("Genres");
+ case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
+ case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
+ case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
- case StubType.Movies: return _localization.GetLocalizedString("Movies");
- case StubType.Collections: return _localization.GetLocalizedString("Collections");
- case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
- case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
- case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
+ case StubType.Movies: return _localization.GetLocalizedString("Movies");
+ case StubType.Collections: return _localization.GetLocalizedString("Collections");
+ case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
+ case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
+ case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
- case StubType.Series: return _localization.GetLocalizedString("Shows");
- default: break;
+ case StubType.Series: return _localization.GetLocalizedString("Shows");
}
}
- if (item is Episode episode && context is Season season)
- {
- // This is a special embedded within a season
- if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
- && season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
- {
- return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
- }
-
- if (item.IndexNumber.HasValue)
- {
- var number = item.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
-
- if (episode.IndexNumberEnd.HasValue)
- {
- number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
- }
-
- return number + " - " + item.Name;
- }
- }
-
- return item.Name;
+ return item is Episode episode
+ ? GetEpisodeDisplayName(episode, context)
+ : item.Name;
}
- private void AddAudioResource(DlnaOptions options, XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
+ ///
+ /// Gets episode display name appropriate for the given context.
+ ///
+ ///
+ /// If context is a season, this will return a string containing just episode number and name.
+ /// Otherwise the result will include series nams and season number.
+ ///
+ /// The episode.
+ /// Current context.
+ /// Formatted name of the episode.
+ private string GetEpisodeDisplayName(Episode episode, BaseItem context)
{
- writer.WriteStartElement(string.Empty, "res", NS_DIDL);
+ string[] components;
+
+ if (context is Season season)
+ {
+ // This is a special embedded within a season
+ if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0
+ && season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("ValueSpecialEpisodeName"),
+ episode.Name);
+ }
+
+ // inside a season use simple format (ex. '12 - Episode Name')
+ var epNumberName = GetEpisodeIndexFullName(episode);
+ components = new[] { epNumberName, episode.Name };
+ }
+ else
+ {
+ // outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name')
+ var epNumberName = GetEpisodeNumberDisplayName(episode);
+ components = new[] { episode.SeriesName, epNumberName, episode.Name };
+ }
+
+ return string.Join(" - ", components.Where(NotNullOrWhiteSpace));
+ }
+
+ ///
+ /// Gets complete episode number.
+ ///
+ /// The episode.
+ /// For single episodes returns just the number. For double episodes - current and ending numbers.
+ private string GetEpisodeIndexFullName(Episode episode)
+ {
+ var name = string.Empty;
+ if (episode.IndexNumber.HasValue)
+ {
+ name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+
+ if (episode.IndexNumberEnd.HasValue)
+ {
+ name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
+ }
+ }
+
+ return name;
+ }
+
+ ///
+ /// Gets episode number formatted as 'S##E##'.
+ ///
+ /// The episode.
+ /// Formatted episode number.
+ private string GetEpisodeNumberDisplayName(Episode episode)
+ {
+ var name = string.Empty;
+ var seasonNumber = episode.Season?.IndexNumber;
+
+ if (seasonNumber.HasValue)
+ {
+ name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+ }
+
+ var indexName = GetEpisodeIndexFullName(episode);
+
+ if (!string.IsNullOrWhiteSpace(indexName))
+ {
+ name += "E" + indexName;
+ }
+
+ return name;
+ }
+
+ private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
+
+ private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
+ {
+ writer.WriteStartElement(string.Empty, "res", NsDidl);
if (streamInfo == null)
{
@@ -474,7 +550,7 @@ namespace Emby.Dlna.Didl
if (mediaSource.RunTimeTicks.HasValue)
{
- writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", _usCulture));
+ writer.WriteAttributeString("duration", TimeSpan.FromTicks(mediaSource.RunTimeTicks.Value).ToString("c", CultureInfo.InvariantCulture));
}
if (filter.Contains("res@size"))
@@ -485,7 +561,7 @@ namespace Emby.Dlna.Didl
if (size.HasValue)
{
- writer.WriteAttributeString("size", size.Value.ToString(_usCulture));
+ writer.WriteAttributeString("size", size.Value.ToString(CultureInfo.InvariantCulture));
}
}
}
@@ -497,33 +573,36 @@ namespace Emby.Dlna.Didl
if (targetChannels.HasValue)
{
- writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(_usCulture));
+ writer.WriteAttributeString("nrAudioChannels", targetChannels.Value.ToString(CultureInfo.InvariantCulture));
}
if (targetSampleRate.HasValue)
{
- writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(_usCulture));
+ writer.WriteAttributeString("sampleFrequency", targetSampleRate.Value.ToString(CultureInfo.InvariantCulture));
}
if (targetAudioBitrate.HasValue)
{
- writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
+ writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(CultureInfo.InvariantCulture));
}
- var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
+ var mediaProfile = _profile.GetAudioMediaProfile(
+ streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
targetChannels,
targetAudioBitrate,
targetSampleRate,
targetAudioBitDepth);
- var filename = url.Substring(0, url.IndexOf('?'));
+ var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
- ? GetMimeType(filename)
+ ? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
- var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
+ var contentFeatures = ContentFeatureBuilder.BuildAudioHeader(
+ _profile,
+ streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
targetAudioBitrate,
targetSampleRate,
@@ -533,11 +612,13 @@ namespace Emby.Dlna.Didl
streamInfo.RunTimeTicks ?? 0,
streamInfo.TranscodeSeekInfo);
- writer.WriteAttributeString("protocolInfo", string.Format(
- "http-get:*:{0}:{1}",
- mimeType,
- contentFeatures
- ));
+ writer.WriteAttributeString(
+ "protocolInfo",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:{0}:{1}",
+ mimeType,
+ contentFeatures));
writer.WriteString(url);
@@ -545,29 +626,22 @@ namespace Emby.Dlna.Didl
}
public static bool IsIdRoot(string id)
- {
- if (string.IsNullOrWhiteSpace(id)
+ => string.IsNullOrWhiteSpace(id)
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
// Samsung sometimes uses 1 as root
- || string.Equals(id, "1", StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- return false;
- }
+ || string.Equals(id, "1", StringComparison.OrdinalIgnoreCase);
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
{
- writer.WriteStartElement(string.Empty, "container", NS_DIDL);
+ writer.WriteStartElement(string.Empty, "container", NsDidl);
writer.WriteAttributeString("restricted", "1");
writer.WriteAttributeString("searchable", "1");
- writer.WriteAttributeString("childCount", childCount.ToString(_usCulture));
+ writer.WriteAttributeString("childCount", childCount.ToString(CultureInfo.InvariantCulture));
var clientId = GetClientId(folder, stubType);
- if (string.Equals(requestedId, "0"))
+ if (string.Equals(requestedId, "0", StringComparison.Ordinal))
{
writer.WriteAttributeString("id", "0");
writer.WriteAttributeString("parentID", "-1");
@@ -596,19 +670,19 @@ namespace Emby.Dlna.Didl
AddGeneralProperties(folder, stubType, context, writer, filter);
- AddCover(folder, context, stubType, writer);
+ AddCover(folder, stubType, writer);
writer.WriteFullEndElement();
}
- private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer)
+ private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer, StreamInfo streamInfo)
{
if (!item.SupportsPositionTicksResume || item is Folder)
{
return;
}
- MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null;
+ XmlAttribute secAttribute = null;
foreach (var attribute in _profile.XmlRootAttributes)
{
if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase))
@@ -625,24 +699,28 @@ namespace Emby.Dlna.Didl
}
var userdata = _userDataManager.GetUserData(user, item);
+ var playbackPositionTicks = (streamInfo != null && streamInfo.StartPositionTicks > 0) ? streamInfo.StartPositionTicks : userdata.PlaybackPositionTicks;
- if (userdata.PlaybackPositionTicks > 0)
+ if (playbackPositionTicks > 0)
{
- var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture));
+ var elementValue = string.Format(
+ CultureInfo.InvariantCulture,
+ "BM={0}",
+ Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds));
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
}
}
///
- /// Adds fields used by both items and folders
+ /// Adds fields used by both items and folders.
///
private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
{
// Don't filter on dc:title because not all devices will include it in the filter
// MediaMonkey for example won't display content without a title
- //if (filter.Contains("dc:title"))
+ // if (filter.Contains("dc:title"))
{
- AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
+ AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NsDc);
}
WriteObjectClass(writer, item, itemStubType);
@@ -651,7 +729,7 @@ namespace Emby.Dlna.Didl
{
if (item.PremiereDate.HasValue)
{
- AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
+ AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NsDc);
}
}
@@ -659,16 +737,16 @@ namespace Emby.Dlna.Didl
{
foreach (var genre in item.Genres)
{
- AddValue(writer, "upnp", "genre", genre, NS_UPNP);
+ AddValue(writer, "upnp", "genre", genre, NsUpnp);
}
}
foreach (var studio in item.Studios)
{
- AddValue(writer, "upnp", "publisher", studio, NS_UPNP);
+ AddValue(writer, "upnp", "publisher", studio, NsUpnp);
}
- if (!(item is Folder))
+ if (item is not Folder)
{
if (filter.Contains("dc:description"))
{
@@ -676,27 +754,29 @@ namespace Emby.Dlna.Didl
if (!string.IsNullOrWhiteSpace(desc))
{
- AddValue(writer, "dc", "description", desc, NS_DC);
+ AddValue(writer, "dc", "description", desc, NsDc);
}
}
- //if (filter.Contains("upnp:longDescription"))
- //{
+
+ // if (filter.Contains("upnp:longDescription"))
+ // {
// if (!string.IsNullOrWhiteSpace(item.Overview))
// {
- // AddValue(writer, "upnp", "longDescription", item.Overview, NS_UPNP);
+ // AddValue(writer, "upnp", "longDescription", item.Overview, NsUpnp);
// }
- //}
+ // }
}
if (!string.IsNullOrEmpty(item.OfficialRating))
{
if (filter.Contains("dc:rating"))
{
- AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
+ AddValue(writer, "dc", "rating", item.OfficialRating, NsDc);
}
+
if (filter.Contains("upnp:rating"))
{
- AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
+ AddValue(writer, "upnp", "rating", item.OfficialRating, NsUpnp);
}
}
@@ -708,7 +788,7 @@ namespace Emby.Dlna.Didl
// More types here
// http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs
- writer.WriteStartElement("upnp", "class", NS_UPNP);
+ writer.WriteStartElement("upnp", "class", NsUpnp);
if (item.IsDisplayedAsFolder || stubType.HasValue)
{
@@ -781,52 +861,50 @@ namespace Emby.Dlna.Didl
private void AddPeople(BaseItem item, XmlWriter writer)
{
- //var types = new[]
- //{
- // PersonType.Director,
- // PersonType.Writer,
- // PersonType.Producer,
- // PersonType.Composer,
- // "Creator"
- //};
+ if (!item.SupportsPeople)
+ {
+ return;
+ }
- //var people = _libraryManager.GetPeople(item);
+ var types = new[]
+ {
+ PersonType.Director,
+ PersonType.Writer,
+ PersonType.Producer,
+ PersonType.Composer,
+ "creator"
+ };
- //var index = 0;
+ // Seeing some LG models locking up due content with large lists of people
+ // The actual issue might just be due to processing a more metadata than it can handle
+ var people = _libraryManager.GetPeople(
+ new InternalPeopleQuery
+ {
+ ItemId = item.Id,
+ Limit = 6
+ });
- //// Seeing some LG models locking up due content with large lists of people
- //// The actual issue might just be due to processing a more metadata than it can handle
- //var limit = 6;
+ foreach (var actor in people)
+ {
+ var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
+ ?? PersonType.Actor;
- //foreach (var actor in people)
- //{
- // var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
- // ?? PersonType.Actor;
-
- // AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
-
- // index++;
-
- // if (index >= limit)
- // {
- // break;
- // }
- //}
+ AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NsUpnp);
+ }
}
private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
{
AddCommonFields(item, itemStubType, context, writer, filter);
- var hasArtists = item as IHasArtist;
var hasAlbumArtists = item as IHasAlbumArtist;
- if (hasArtists != null)
+ if (item is IHasArtist hasArtists)
{
foreach (var artist in hasArtists.Artists)
{
- AddValue(writer, "upnp", "artist", artist, NS_UPNP);
- AddValue(writer, "dc", "creator", artist, NS_DC);
+ AddValue(writer, "upnp", "artist", artist, NsUpnp);
+ AddValue(writer, "dc", "creator", artist, NsDc);
// If it doesn't support album artists (musicvideo), then tag as both
if (hasAlbumArtists == null)
@@ -846,16 +924,16 @@ namespace Emby.Dlna.Didl
if (!string.IsNullOrWhiteSpace(item.Album))
{
- AddValue(writer, "upnp", "album", item.Album, NS_UPNP);
+ AddValue(writer, "upnp", "album", item.Album, NsUpnp);
}
if (item.IndexNumber.HasValue)
{
- AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
+ AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), NsUpnp);
if (item is Episode)
{
- AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
+ AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(CultureInfo.InvariantCulture), NsUpnp);
}
}
}
@@ -864,7 +942,7 @@ namespace Emby.Dlna.Didl
{
try
{
- writer.WriteStartElement("upnp", "artist", NS_UPNP);
+ writer.WriteStartElement("upnp", "artist", NsUpnp);
writer.WriteAttributeString("role", "AlbumArtist");
writer.WriteString(name);
@@ -873,7 +951,7 @@ namespace Emby.Dlna.Didl
}
catch (XmlException ex)
{
- _logger.LogError(ex, "Error adding xml value: {value}", name);
+ _logger.LogError(ex, "Error adding xml value: {Value}", name);
}
}
@@ -885,11 +963,11 @@ namespace Emby.Dlna.Didl
}
catch (XmlException ex)
{
- _logger.LogError(ex, "Error adding xml value: {value}", value);
+ _logger.LogError(ex, "Error adding xml value: {Value}", value);
}
}
- private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
+ private void AddCover(BaseItem item, StubType? stubType, XmlWriter writer)
{
ImageDownloadInfo imageInfo = GetImageInfo(item);
@@ -898,16 +976,29 @@ namespace Emby.Dlna.Didl
return;
}
- var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
+ // TODO: Remove these default values
+ var albumArtUrlInfo = GetImageUrl(
+ imageInfo,
+ _profile.MaxAlbumArtWidth ?? 10000,
+ _profile.MaxAlbumArtHeight ?? 10000,
+ "jpg");
- writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
- writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
- writer.WriteString(albumartUrlInfo.Url);
+ writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
+ if (!string.IsNullOrEmpty(_profile.AlbumArtPn))
+ {
+ writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
+ }
+
+ writer.WriteString(albumArtUrlInfo.url);
writer.WriteFullEndElement();
- // TOOD: Remove these default values
- var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
- writer.WriteElementString("upnp", "icon", NS_UPNP, iconUrlInfo.Url);
+ // TODO: Remove these default values
+ var iconUrlInfo = GetImageUrl(
+ imageInfo,
+ _profile.MaxIconWidth ?? 48,
+ _profile.MaxIconHeight ?? 48,
+ "jpg");
+ writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
if (!_profile.EnableAlbumArtInDidl)
{
@@ -921,8 +1012,6 @@ namespace Emby.Dlna.Didl
}
}
- AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
-
if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
{
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
@@ -931,19 +1020,12 @@ namespace Emby.Dlna.Didl
AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG");
AddImageResElement(item, writer, 160, 160, "png", "PNG_TN");
}
+
+ AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
}
- private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
- {
- writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
- writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
- writer.WriteString(_serverAddress + "/Dlna/icons/people480.jpg");
- writer.WriteFullEndElement();
-
- writer.WriteElementString("upnp", "icon", NS_UPNP, _serverAddress + "/Dlna/icons/people48.jpg");
- }
-
- private void AddImageResElement(BaseItem item,
+ private void AddImageResElement(
+ BaseItem item,
XmlWriter writer,
int maxWidth,
int maxHeight,
@@ -959,25 +1041,28 @@ namespace Emby.Dlna.Didl
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format);
- writer.WriteStartElement(string.Empty, "res", NS_DIDL);
+ writer.WriteStartElement(string.Empty, "res", NsDidl);
// Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail
// rather than using a larger one when available
- var width = albumartUrlInfo.Width ?? maxWidth;
- var height = albumartUrlInfo.Height ?? maxHeight;
+ var width = albumartUrlInfo.width ?? maxWidth;
+ var height = albumartUrlInfo.height ?? maxHeight;
- var contentFeatures = new ContentFeatureBuilder(_profile)
- .BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
+ var contentFeatures = ContentFeatureBuilder.BuildImageHeader(_profile, format, width, height, imageInfo.IsDirectStream, org_Pn);
- writer.WriteAttributeString("protocolInfo", string.Format(
- "http-get:*:{0}:{1}",
- GetMimeType("file." + format),
- contentFeatures
- ));
+ writer.WriteAttributeString(
+ "protocolInfo",
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "http-get:*:{0}:{1}",
+ MimeTypes.GetMimeType("file." + format),
+ contentFeatures));
- writer.WriteAttributeString("resolution", string.Format("{0}x{1}", width, height));
+ writer.WriteAttributeString(
+ "resolution",
+ string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
- writer.WriteString(albumartUrlInfo.Url);
+ writer.WriteString(albumartUrlInfo.url);
writer.WriteFullEndElement();
}
@@ -988,10 +1073,12 @@ namespace Emby.Dlna.Didl
{
return GetImageInfo(item, ImageType.Primary);
}
+
if (item.HasImage(ImageType.Thumb))
{
return GetImageInfo(item, ImageType.Thumb);
}
+
if (item.HasImage(ImageType.Backdrop))
{
if (item is Channel)
@@ -1000,19 +1087,58 @@ namespace Emby.Dlna.Didl
}
}
- item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
-
- if (item != null)
+ // For audio tracks without art use album art if available.
+ if (item is Audio audioItem)
{
- if (item.HasImage(ImageType.Primary))
- {
- return GetImageInfo(item, ImageType.Primary);
- }
+ var album = audioItem.AlbumEntity;
+ return album != null && album.HasImage(ImageType.Primary)
+ ? GetImageInfo(album, ImageType.Primary)
+ : null;
+ }
+
+ // Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder.
+ if (item is MusicAlbum || item is Playlist)
+ {
+ return null;
+ }
+
+ // For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item.
+ var parentWithImage = GetFirstParentWithImageBelowUserRoot(item);
+ if (parentWithImage != null)
+ {
+ return GetImageInfo(parentWithImage, ImageType.Primary);
}
return null;
}
+ private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
+ {
+ if (item == null)
+ {
+ return null;
+ }
+
+ if (item.HasImage(ImageType.Primary))
+ {
+ return item;
+ }
+
+ var parent = item.GetParent();
+ if (parent is UserRootFolder)
+ {
+ return null;
+ }
+
+ // terminate in case we went past user root folder (unlikely?)
+ if (parent is Folder folder && folder.IsRoot)
+ {
+ return null;
+ }
+
+ return GetFirstParentWithImageBelowUserRoot(parent);
+ }
+
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
{
var imageInfo = item.GetImageInfo(type, 0);
@@ -1032,29 +1158,15 @@ namespace Emby.Dlna.Didl
if (width == 0 || height == 0)
{
- //_imageProcessor.GetImageSize(item, imageInfo);
width = null;
height = null;
}
-
else if (width == -1 || height == -1)
{
width = null;
height = null;
}
- //try
- //{
- // var size = _imageProcessor.GetImageSize(imageInfo);
-
- // width = size.Width;
- // height = size.Height;
- //}
- //catch
- //{
-
- //}
-
var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty)
.TrimStart('.')
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
@@ -1071,30 +1183,6 @@ namespace Emby.Dlna.Didl
};
}
- private class ImageDownloadInfo
- {
- internal Guid ItemId;
- internal string ImageTag;
- internal ImageType Type;
-
- internal int? Width;
- internal int? Height;
-
- internal bool IsDirectStream;
-
- internal string Format;
-
- internal ItemImageInfo ItemImageInfo;
- }
-
- private class ImageUrlInfo
- {
- internal string Url;
-
- internal int? Width;
- internal int? Height;
- }
-
public static string GetClientId(BaseItem item, StubType? stubType)
{
return GetClientId(item.Id, stubType);
@@ -1102,7 +1190,7 @@ namespace Emby.Dlna.Didl
public static string GetClientId(Guid idValue, StubType? stubType)
{
- var id = idValue.ToString("N");
+ var id = idValue.ToString("N", CultureInfo.InvariantCulture);
if (stubType.HasValue)
{
@@ -1112,11 +1200,13 @@ namespace Emby.Dlna.Didl
return id;
}
- private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
+ private (string url, int? width, int? height) GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
{
- var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
+ var url = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
_serverAddress,
- info.ItemId.ToString("N"),
+ info.ItemId.ToString("N", CultureInfo.InvariantCulture),
info.Type,
info.ImageTag,
format,
@@ -1130,8 +1220,7 @@ namespace Emby.Dlna.Didl
if (width.HasValue && height.HasValue)
{
- var newSize = DrawingUtils.Resize(
- new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
+ var newSize = DrawingUtils.Resize(new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
width = newSize.Width;
height = newSize.Height;
@@ -1148,12 +1237,26 @@ namespace Emby.Dlna.Didl
// just lie
info.IsDirectStream = true;
- return new ImageUrlInfo
- {
- Url = url,
- Width = width,
- Height = height
- };
+ return (url, width, height);
+ }
+
+ private class ImageDownloadInfo
+ {
+ internal Guid ItemId { get; set; }
+
+ internal string ImageTag { get; set; }
+
+ internal ImageType Type { get; set; }
+
+ internal int? Width { get; set; }
+
+ internal int? Height { get; set; }
+
+ internal bool IsDirectStream { get; set; }
+
+ internal string Format { get; set; }
+
+ internal ItemImageInfo ItemImageInfo { get; set; }
}
}
}
diff --git a/Emby.Dlna/Didl/Filter.cs b/Emby.Dlna/Didl/Filter.cs
index a0e67870e9..d703f043eb 100644
--- a/Emby.Dlna/Didl/Filter.cs
+++ b/Emby.Dlna/Didl/Filter.cs
@@ -1,5 +1,6 @@
+#pragma warning disable CS1591
+
using System;
-using MediaBrowser.Model.Extensions;
namespace Emby.Dlna.Didl
{
@@ -11,21 +12,18 @@ namespace Emby.Dlna.Didl
public Filter()
: this("*")
{
-
}
public Filter(string filter)
{
- _all = StringHelper.EqualsIgnoreCase(filter, "*");
+ _all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
- _fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
+ _fields = (filter ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries);
}
public bool Contains(string field)
{
- // Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back.
- return true;
- //return _all || ListHelper.ContainsIgnoreCase(_fields, field);
+ return _all || Array.Exists(_fields, x => x.Equals(field, StringComparison.OrdinalIgnoreCase));
}
}
}
diff --git a/Emby.Dlna/Didl/StringWriterWithEncoding.cs b/Emby.Dlna/Didl/StringWriterWithEncoding.cs
index c3c4bd393b..b66f53ece2 100644
--- a/Emby.Dlna/Didl/StringWriterWithEncoding.cs
+++ b/Emby.Dlna/Didl/StringWriterWithEncoding.cs
@@ -1,3 +1,6 @@
+#pragma warning disable CS1591
+#pragma warning disable CA1305
+
using System;
using System.IO;
using System.Text;
@@ -6,7 +9,7 @@ namespace Emby.Dlna.Didl
{
public class StringWriterWithEncoding : StringWriter
{
- private readonly Encoding _encoding;
+ private readonly Encoding? _encoding;
public StringWriterWithEncoding()
{
@@ -27,7 +30,6 @@ namespace Emby.Dlna.Didl
{
}
-
public StringWriterWithEncoding(Encoding encoding)
{
_encoding = encoding;
@@ -51,6 +53,6 @@ namespace Emby.Dlna.Didl
_encoding = encoding;
}
- public override Encoding Encoding => (null == _encoding) ? base.Encoding : _encoding;
+ public override Encoding Encoding => _encoding ?? base.Encoding;
}
}
diff --git a/Emby.Dlna/DlnaConfigurationFactory.cs b/Emby.Dlna/DlnaConfigurationFactory.cs
new file mode 100644
index 0000000000..6cc6b73a0c
--- /dev/null
+++ b/Emby.Dlna/DlnaConfigurationFactory.cs
@@ -0,0 +1,23 @@
+#pragma warning disable CS1591
+
+using System.Collections.Generic;
+using Emby.Dlna.Configuration;
+using MediaBrowser.Common.Configuration;
+
+namespace Emby.Dlna
+{
+ public class DlnaConfigurationFactory : IConfigurationFactory
+ {
+ public IEnumerable GetConfigurations()
+ {
+ return new[]
+ {
+ new ConfigurationStore
+ {
+ Key = "dlna",
+ ConfigurationType = typeof(DlnaOptions)
+ }
+ };
+ }
+ }
+}
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index f53d274516..f37d2d7d7b 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -1,12 +1,17 @@
+#pragma warning disable CS1591
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Text;
+using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Emby.Dlna.Profiles;
using Emby.Dlna.Server;
+using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller;
@@ -15,9 +20,10 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Reflection;
using MediaBrowser.Model.Serialization;
+using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
namespace Emby.Dlna
{
@@ -26,10 +32,10 @@ namespace Emby.Dlna
private readonly IApplicationPaths _appPaths;
private readonly IXmlSerializer _xmlSerializer;
private readonly IFileSystem _fileSystem;
- private readonly ILogger _logger;
- private readonly IJsonSerializer _jsonSerializer;
+ private readonly ILogger _logger;
private readonly IServerApplicationHost _appHost;
- private readonly IAssemblyInfo _assemblyInfo;
+ private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
+ private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
private readonly Dictionary> _profiles = new Dictionary>(StringComparer.Ordinal);
@@ -38,24 +44,24 @@ namespace Emby.Dlna
IFileSystem fileSystem,
IApplicationPaths appPaths,
ILoggerFactory loggerFactory,
- IJsonSerializer jsonSerializer,
- IServerApplicationHost appHost,
- IAssemblyInfo assemblyInfo)
+ IServerApplicationHost appHost)
{
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
_appPaths = appPaths;
- _logger = loggerFactory.CreateLogger("Dlna");
- _jsonSerializer = jsonSerializer;
+ _logger = loggerFactory.CreateLogger();
_appHost = appHost;
- _assemblyInfo = assemblyInfo;
}
+ private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
+
+ private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
+
public async Task InitProfilesAsync()
{
try
{
- await ExtractSystemProfilesAsync();
+ await ExtractSystemProfilesAsync().ConfigureAwait(false);
LoadProfiles();
}
catch (Exception ex)
@@ -85,15 +91,16 @@ namespace Emby.Dlna
.Select(i => i.Item2)
.ToList();
}
-
}
+ ///
public DeviceProfile GetDefaultProfile()
{
return new DefaultProfile();
}
- public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
+ ///
+ public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
{
if (deviceInfo == null)
{
@@ -103,13 +110,13 @@ namespace Emby.Dlna
var profile = GetProfiles()
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
- if (profile != null)
+ if (profile == null)
{
- _logger.LogDebug("Found matching device profile: {0}", profile.Name);
+ LogUnmatchedProfile(deviceInfo);
}
else
{
- LogUnmatchedProfile(deviceInfo);
+ _logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
}
return profile;
@@ -120,83 +127,57 @@ namespace Emby.Dlna
var builder = new StringBuilder();
builder.AppendLine("No matching device profile found. The default will need to be used.");
- builder.AppendLine(string.Format("DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty));
- builder.AppendLine(string.Format("FriendlyName:{0}", profile.FriendlyName ?? string.Empty));
- builder.AppendLine(string.Format("Manufacturer:{0}", profile.Manufacturer ?? string.Empty));
- builder.AppendLine(string.Format("ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty));
- builder.AppendLine(string.Format("ModelDescription:{0}", profile.ModelDescription ?? string.Empty));
- builder.AppendLine(string.Format("ModelName:{0}", profile.ModelName ?? string.Empty));
- builder.AppendLine(string.Format("ModelNumber:{0}", profile.ModelNumber ?? string.Empty));
- builder.AppendLine(string.Format("ModelUrl:{0}", profile.ModelUrl ?? string.Empty));
- builder.AppendLine(string.Format("SerialNumber:{0}", profile.SerialNumber ?? string.Empty));
+ builder.Append("FriendlyName: ").AppendLine(profile.FriendlyName);
+ builder.Append("Manufacturer: ").AppendLine(profile.Manufacturer);
+ builder.Append("ManufacturerUrl: ").AppendLine(profile.ManufacturerUrl);
+ builder.Append("ModelDescription: ").AppendLine(profile.ModelDescription);
+ builder.Append("ModelName: ").AppendLine(profile.ModelName);
+ builder.Append("ModelNumber: ").AppendLine(profile.ModelNumber);
+ builder.Append("ModelUrl: ").AppendLine(profile.ModelUrl);
+ builder.Append("SerialNumber: ").AppendLine(profile.SerialNumber);
_logger.LogInformation(builder.ToString());
}
- private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
+ ///
+ /// Attempts to match a device with a profile.
+ /// Rules:
+ /// - If the profile field has no value, the field matches irregardless of its contents.
+ /// - the profile field can be an exact match, or a reg exp.
+ ///
+ /// The of the device.
+ /// The of the profile.
+ /// True if they match.
+ public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
{
- if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
- {
- if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
- return false;
- }
-
- if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
- {
- if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
- return false;
- }
-
- if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
- {
- if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
- return false;
- }
-
- if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
- {
- if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
- return false;
- }
-
- if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
- {
- if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
- return false;
- }
-
- if (!string.IsNullOrEmpty(profileInfo.ModelName))
- {
- if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
- return false;
- }
-
- if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
- {
- if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
- return false;
- }
-
- if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
- {
- if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
- return false;
- }
-
- if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
- {
- if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
- return false;
- }
-
- return true;
+ return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)
+ && IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)
+ && IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)
+ && IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)
+ && IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)
+ && IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)
+ && IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)
+ && IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber);
}
- private bool IsRegexMatch(string input, string pattern)
+ private bool IsRegexOrSubstringMatch(string input, string pattern)
{
+ if (string.IsNullOrEmpty(pattern))
+ {
+ // In profile identification: An empty pattern matches anything.
+ return true;
+ }
+
+ if (string.IsNullOrEmpty(input))
+ {
+ // The profile contains a value, and the device doesn't.
+ return false;
+ }
+
try
{
- return Regex.IsMatch(input, pattern);
+ return input.Equals(pattern, StringComparison.OrdinalIgnoreCase)
+ || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
}
catch (ArgumentException ex)
{
@@ -205,37 +186,33 @@ namespace Emby.Dlna
}
}
- public DeviceProfile GetProfile(IDictionary headers)
+ ///
+ public DeviceProfile? GetProfile(IHeaderDictionary headers)
{
if (headers == null)
{
throw new ArgumentNullException(nameof(headers));
}
- // Convert to case insensitive
- headers = new Dictionary(headers, StringComparer.OrdinalIgnoreCase);
-
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
-
- if (profile != null)
+ if (profile == null)
{
- _logger.LogDebug("Found matching device profile: {0}", profile.Name);
+ _logger.LogDebug("No matching device profile found. {@Headers}", headers);
}
else
{
- var headerString = string.Join(", ", headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
- _logger.LogDebug("No matching device profile found. {0}", headerString);
+ _logger.LogDebug("Found matching device profile: {0}", profile.Name);
}
return profile;
}
- private bool IsMatch(IDictionary headers, DeviceIdentification profileInfo)
+ private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo)
{
return profileInfo.Headers.Any(i => IsMatch(headers, i));
}
- private bool IsMatch(IDictionary headers, HttpHeaderInfo header)
+ private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header)
{
// Handle invalid user setup
if (string.IsNullOrEmpty(header.Name))
@@ -243,15 +220,15 @@ namespace Emby.Dlna
return false;
}
- if (headers.TryGetValue(header.Name, out string value))
+ if (headers.TryGetValue(header.Name, out StringValues value))
{
switch (header.Match)
{
case HeaderMatchType.Equals:
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
case HeaderMatchType.Substring:
- var isMatch = value.IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
- //_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
+ var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
+ // _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
return isMatch;
case HeaderMatchType.Regex:
return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
@@ -263,10 +240,6 @@ namespace Emby.Dlna
return false;
}
- private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
-
- private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
-
private IEnumerable GetProfiles(string path, DeviceProfileType type)
{
try
@@ -278,19 +251,19 @@ namespace Emby.Dlna
return xmlFies
.Select(i => ParseProfileFile(i, type))
.Where(i => i != null)
- .ToList();
+ .ToList()!; // We just filtered out all the nulls
}
catch (IOException)
{
- return new List();
+ return Array.Empty();
}
}
- private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
+ private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
{
lock (_profiles)
{
- if (_profiles.TryGetValue(path, out Tuple profileTuple))
+ if (_profiles.TryGetValue(path, out Tuple? profileTuple))
{
return profileTuple.Item2;
}
@@ -303,7 +276,7 @@ namespace Emby.Dlna
profile = ReserializeProfile(tempProfile);
- profile.Id = path.ToLowerInvariant().GetMD5().ToString("N");
+ profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
_profiles[path] = new Tuple(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
@@ -318,14 +291,20 @@ namespace Emby.Dlna
}
}
- public DeviceProfile GetProfile(string id)
+ ///
+ public DeviceProfile? GetProfile(string id)
{
if (string.IsNullOrEmpty(id))
{
throw new ArgumentNullException(nameof(id));
}
- var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
+ var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
+
+ if (info == null)
+ {
+ return null;
+ }
return ParseProfileFile(info.Path, info.Info.Type);
}
@@ -342,6 +321,7 @@ namespace Emby.Dlna
}
}
+ ///
public IEnumerable GetProfileInfos()
{
return GetProfileInfosInternal().Select(i => i.Info);
@@ -349,17 +329,14 @@ namespace Emby.Dlna
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
{
- return new InternalProfileInfo
- {
- Path = file.FullName,
-
- Info = new DeviceProfileInfo
+ return new InternalProfileInfo(
+ new DeviceProfileInfo
{
- Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N"),
+ Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
Name = _fileSystem.GetFileNameWithoutExtension(file),
Type = type
- }
- };
+ },
+ file.FullName);
}
private async Task ExtractSystemProfilesAsync()
@@ -368,25 +345,33 @@ namespace Emby.Dlna
var systemProfilesPath = SystemProfilesPath;
- foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType())
- .Where(i => i.StartsWith(namespaceName))
- .ToList())
+ foreach (var name in _assembly.GetManifestResourceNames())
{
- var filename = Path.GetFileName(name).Substring(namespaceName.Length);
-
- var path = Path.Combine(systemProfilesPath, filename);
-
- using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name))
+ if (!name.StartsWith(namespaceName, StringComparison.Ordinal))
{
+ continue;
+ }
+
+ var path = Path.Join(
+ systemProfilesPath,
+ Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
+
+ // The stream should exist as we just got its name from GetManifestResourceNames
+ using (var stream = _assembly.GetManifestResourceStream(name)!)
+ {
+ var length = stream.Length;
var fileInfo = _fileSystem.GetFileInfo(path);
- if (!fileInfo.Exists || fileInfo.Length != stream.Length)
+ if (!fileInfo.Exists || fileInfo.Length != length)
{
Directory.CreateDirectory(systemProfilesPath);
- using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
+ var fileOptions = AsyncFile.WriteOptions;
+ fileOptions.Mode = FileMode.CreateNew;
+ fileOptions.PreallocationSize = length;
+ using (var fileStream = new FileStream(path, fileOptions))
{
- await stream.CopyToAsync(fileStream);
+ await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
}
}
@@ -396,6 +381,7 @@ namespace Emby.Dlna
Directory.CreateDirectory(UserProfilesPath);
}
+ ///
public void DeleteProfile(string id)
{
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
@@ -413,6 +399,7 @@ namespace Emby.Dlna
}
}
+ ///
public void CreateProfile(DeviceProfile profile)
{
profile = ReserializeProfile(profile);
@@ -428,7 +415,8 @@ namespace Emby.Dlna
SaveProfile(profile, path, DeviceProfileType.User);
}
- public void UpdateProfile(DeviceProfile profile)
+ ///
+ public void UpdateProfile(string profileId, DeviceProfile profile)
{
profile = ReserializeProfile(profile);
@@ -436,12 +424,13 @@ namespace Emby.Dlna
{
throw new ArgumentException("Profile is missing Id");
}
+
if (string.IsNullOrEmpty(profile.Name))
{
throw new ArgumentException("Profile is missing Name");
}
- var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase));
+ var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profileId, StringComparison.OrdinalIgnoreCase));
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
var path = Path.Combine(UserProfilesPath, newFilename);
@@ -461,6 +450,7 @@ namespace Emby.Dlna
{
_profiles[path] = new Tuple(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
}
+
SerializeToXml(profile, path);
}
@@ -471,10 +461,10 @@ namespace Emby.Dlna
///
/// Recreates the object using serialization, to ensure it's not a subclass.
- /// If it's a subclass it may not serlialize properly to xml (different root element tag name)
+ /// If it's a subclass it may not serialize properly to xml (different root element tag name).
///
- ///
- ///
+ /// The device profile.
+ /// The re-serialized device profile.
private DeviceProfile ReserializeProfile(DeviceProfile profile)
{
if (profile.GetType() == typeof(DeviceProfile))
@@ -482,42 +472,56 @@ namespace Emby.Dlna
return profile;
}
- var json = _jsonSerializer.SerializeToString(profile);
+ var json = JsonSerializer.Serialize(profile, _jsonOptions);
- return _jsonSerializer.DeserializeFromString(json);
+ // Output can't be null if the input isn't null
+ return JsonSerializer.Deserialize(json, _jsonOptions)!;
}
- class InternalProfileInfo
+ /// | | | |