Merge branch 'jellyfin:master' into master

This commit is contained in:
WWWesten 2021-11-01 23:43:29 +05:00 committed by GitHub
commit 0a14279e2a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2639 changed files with 139347 additions and 128004 deletions

View file

@ -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)

View file

@ -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'

View file

@ -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

View file

@ -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'

64
.ci/azure-pipelines.yml Normal file
View file

@ -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

View file

@ -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)"

1
.copr/Makefile Symbolic link
View file

@ -0,0 +1 @@
../fedora/Makefile

View file

@ -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

View file

@ -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

4
.gitattributes vendored
View file

@ -1 +1,5 @@
* text=auto eol=lf
*.png binary
*.jpg binary
CONTRIBUTORS.md merge=union

4
.github/CODEOWNERS vendored Normal file
View file

@ -0,0 +1,4 @@
# Joshua must review all changes to deployment and build.sh
.ci/* @joshuaboniface
deployment/* @joshuaboniface
build.sh @joshuaboniface

View file

@ -10,6 +10,21 @@ assignees: ''
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
**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**
<!-- Steps to reproduce the behavior: -->
1. Go to '...'
@ -20,16 +35,17 @@ assignees: ''
**Expected behavior**
<!-- A clear and concise description of what you expected to happen. -->
**Logs**
**Server Logs**
<!-- Please paste any log errors. -->
**FFmpeg Logs**
<!-- Please paste any log errors. -->
**Browser Console Logs**
<!-- Please paste any log errors. -->
**Screenshots**
<!-- If applicable, add screenshots to help explain your problem. -->
**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**
<!-- Add any other context about the problem here. -->

View file

@ -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.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
**Describe the solution you'd like**
<!-- A clear and concise description of what you want to happen. -->
**Describe alternatives you've considered**
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->

View file

@ -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**
<!-- A clear and concise description of what you want to happen. -->
**PLEASE DO NOT OPEN FEATURE REQUEST ISSUES ON GITHUB**
**Additional context**
<!-- Add any other context or screenshots about the feature request here. -->
**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.

View file

@ -0,0 +1,34 @@
---
name: Media playback issue
about: Create a media playback issue report
title: ''
labels: mediaplayback
assignees: ''
---
**Media Info of the file**
<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
**Logs**
<!-- Please paste any log messages from during the playback issue. -->
**FFmpeg Logs**
<!-- Please paste any FFmpeg logs if remuxing or transcoding appears to be part of the issue. -->
**Stats for Nerds Screenshots**
<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
**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]

15
.github/dependabot.yml vendored Normal file
View file

@ -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

View file

@ -1,6 +1,6 @@
<!--
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.readthedocs.io/en/latest/developer-docs/contributing/ page.
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our documentation.
-->
**Changes**

29
.github/stale.yml vendored Normal file
View file

@ -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

76
.github/workflows/automation.yml vendored Normal file
View file

@ -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 }}

38
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -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

119
.github/workflows/commands.yml vendored Normal file
View file

@ -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

39
.gitignore vendored
View file

@ -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

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "MediaBrowser.WebDashboard/jellyfin-web"]
path = MediaBrowser.WebDashboard/jellyfin-web
url = https://github.com/jellyfin/jellyfin-web.git

3
.npmrc Normal file
View file

@ -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

14
.vscode/extensions.json vendored Normal file
View file

@ -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": [
]
}

34
.vscode/launch.json vendored
View file

@ -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}"
}
,]
}
],
"env": {
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
}
}

19
.vscode/tasks.json vendored
View file

@ -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"
}
]
}
],
"options": {
"env": {
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
}
}
}

View file

@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
</Project>

View file

@ -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;
/// <summary>
/// Setting this to false throws an IComparer error on some discs.
/// </summary>
public static bool KeepStreamOrder => true;
public static bool GenerateTextSummary => false;
public static string LastPath => string.Empty;
}
}

View file

@ -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<string, TSPlaylistFile> PlaylistFiles =
new Dictionary<string, TSPlaylistFile>();
public Dictionary<string, TSStreamClipFile> StreamClipFiles =
new Dictionary<string, TSStreamClipFile>();
public Dictionary<string, TSStreamFile> StreamFiles =
new Dictionary<string, TSStreamFile>();
public Dictionary<string, TSInterleavedFile> InterleavedFiles =
new Dictionary<string, TSInterleavedFile>();
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<FileSystemMetadata> GetFiles(string path, string extension)
{
return _fileSystem.GetFiles(path, new[] { extension }, false, false);
}
private IEnumerable<string> GetFilePaths(string path, string extension)
{
return _fileSystem.GetFilePaths(path, new[] { extension }, false, false);
}
public void Scan()
{
var errorStreamClipFiles = new List<TSStreamClipFile>();
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<TSPlaylistFile>();
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<TSStreamFile>();
foreach (var streamFile in streamFiles)
{
try
{
var playlists = new List<TSPlaylistFile>();
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;
}
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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)]

View file

@ -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.

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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
}
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}
}
}

View file

@ -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();
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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<TSDescriptor> 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<TSDescriptor>();
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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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<double> Chapters = new List<double>();
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;
}
}
}

View file

@ -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<ushort, TSStream> Streams =
new Dictionary<ushort, TSStream>();
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();
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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)

14
Directory.Build.props Normal file
View file

@ -0,0 +1,14 @@
<Project>
<!-- Sets defaults for all projects in the repo -->
<PropertyGroup>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
</PropertyGroup>
</Project>

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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));
}
}
}

View file

@ -1,16 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{713F42B5-878E-499D-A878-E4C652B1D5E8}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<AnalysisMode>AllDisabledByDefault</AnalysisMode>
<Nullable>disable</Nullable>
</PropertyGroup>
</Project>

View file

@ -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
{
}
}

View file

@ -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)

View file

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.IO;
namespace DvdLib.Ifo

View file

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System.IO;
namespace DvdLib.Ifo

View file

@ -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)

View file

@ -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<ushort, string> VTSPaths = new Dictionary<ushort, string>();
private readonly IFileSystem _fileSystem;
public Dvd(string path, IFileSystem fileSystem)
public Dvd(string path)
{
_fileSystem = fileSystem;
Titles = new List<Title>();
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<ushort>();
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<FileSystemMetadata> allFiles)
private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> 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);
}
}
}
}

View file

@ -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)

View file

@ -1,17 +0,0 @@
using System.Collections.Generic;
namespace DvdLib.Ifo
{
public class ProgramChainCommandTable
{
public readonly ushort LastByteAddress;
public readonly List<VirtualMachineCommand> PreCommands;
public readonly List<VirtualMachineCommand> PostCommands;
public readonly List<VirtualMachineCommand> CellCommands;
}
public class VirtualMachineCommand
{
public readonly byte[] Command;
}
}

View file

@ -1,10 +1,12 @@
#pragma warning disable CS1591
using System.Collections.Generic;
namespace DvdLib.Ifo
{
public class Program
{
public readonly List<Cell> Cells;
public IReadOnlyList<Cell> Cells { get; }
public Program(List<Cell> cells)
{

View file

@ -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<Cell> 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);

View file

@ -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<ProgramChain> ProgramChains;
public readonly List<Chapter> 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);
}

View file

@ -1,3 +1,5 @@
#pragma warning disable CS1591
using System;
namespace DvdLib.Ifo

View file

@ -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()
{
}
}
}

View file

@ -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")]

View file

@ -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<Stream>(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<string> 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);
}
}
}

View file

@ -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<DeviceProfileInfo[]>
{
}
[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<DeviceProfile>
{
}
[Route("/Dlna/Profiles/{Id}", "GET", Summary = "Gets a single profile")]
public class GetProfile : IReturn<DeviceProfile>
{
[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);
}
}
}

View file

@ -1,12 +1,23 @@
namespace Emby.Dlna.Common
{
/// <summary>
/// DLNA Query parameter type, used when querying DLNA devices via SOAP.
/// </summary>
public class Argument
{
public string Name { get; set; }
/// <summary>
/// Gets or sets name of the DLNA argument.
/// </summary>
public string Name { get; set; } = string.Empty;
public string Direction { get; set; }
/// <summary>
/// Gets or sets the direction of the parameter.
/// </summary>
public string Direction { get; set; } = string.Empty;
public string RelatedStateVariable { get; set; }
/// <summary>
/// Gets or sets the related DLNA state variable for this argument.
/// </summary>
public string RelatedStateVariable { get; set; } = string.Empty;
}
}

View file

@ -1,21 +1,41 @@
using System.Globalization;
namespace Emby.Dlna.Common
{
/// <summary>
/// Defines the <see cref="DeviceIcon" />.
/// </summary>
public class DeviceIcon
{
public string Url { get; set; }
/// <summary>
/// Gets or sets the Url.
/// </summary>
public string Url { get; set; } = string.Empty;
public string MimeType { get; set; }
/// <summary>
/// Gets or sets the MimeType.
/// </summary>
public string MimeType { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the Width.
/// </summary>
public int Width { get; set; }
/// <summary>
/// Gets or sets the Height.
/// </summary>
public int Height { get; set; }
public string Depth { get; set; }
/// <summary>
/// Gets or sets the Depth.
/// </summary>
public string Depth { get; set; } = string.Empty;
/// <inheritdoc />
public override string ToString()
{
return string.Format("{0}x{1}", Height, Width);
return string.Format(CultureInfo.InvariantCulture, "{0}x{1}", Height, Width);
}
}
}

View file

@ -1,21 +1,36 @@
namespace Emby.Dlna.Common
{
/// <summary>
/// Defines the <see cref="DeviceService" />.
/// </summary>
public class DeviceService
{
public string ServiceType { get; set; }
/// <summary>
/// Gets or sets the Service Type.
/// </summary>
public string ServiceType { get; set; } = string.Empty;
public string ServiceId { get; set; }
/// <summary>
/// Gets or sets the Service Id.
/// </summary>
public string ServiceId { get; set; } = string.Empty;
public string ScpdUrl { get; set; }
/// <summary>
/// Gets or sets the Scpd Url.
/// </summary>
public string ScpdUrl { get; set; } = string.Empty;
public string ControlUrl { get; set; }
/// <summary>
/// Gets or sets the Control Url.
/// </summary>
public string ControlUrl { get; set; } = string.Empty;
public string EventSubUrl { get; set; }
/// <summary>
/// Gets or sets the EventSubUrl.
/// </summary>
public string EventSubUrl { get; set; } = string.Empty;
public override string ToString()
{
return string.Format("{0}", ServiceId);
}
/// <inheritdoc />
public override string ToString() => ServiceId;
}
}

View file

@ -2,20 +2,30 @@ using System.Collections.Generic;
namespace Emby.Dlna.Common
{
/// <summary>
/// Defines the <see cref="ServiceAction" />.
/// </summary>
public class ServiceAction
{
public string Name { get; set; }
public List<Argument> ArgumentList { get; set; }
public override string ToString()
{
return Name;
}
/// <summary>
/// Initializes a new instance of the <see cref="ServiceAction"/> class.
/// </summary>
public ServiceAction()
{
ArgumentList = new List<Argument>();
}
/// <summary>
/// Gets or sets the name of the action.
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// Gets the ArgumentList.
/// </summary>
public List<Argument> ArgumentList { get; }
/// <inheritdoc />
public override string ToString() => Name;
}
}

View file

@ -1,25 +1,34 @@
using System;
using System.Collections.Generic;
namespace Emby.Dlna.Common
{
/// <summary>
/// Defines the <see cref="StateVariable" />.
/// </summary>
public class StateVariable
{
public string Name { get; set; }
/// <summary>
/// Gets or sets the name of the state variable.
/// </summary>
public string Name { get; set; } = string.Empty;
public string DataType { get; set; }
/// <summary>
/// Gets or sets the data type of the state variable.
/// </summary>
public string DataType { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether it sends events.
/// </summary>
public bool SendsEvents { get; set; }
public string[] AllowedValues { get; set; }
/// <summary>
/// Gets or sets the allowed values range.
/// </summary>
public IReadOnlyList<string> AllowedValues { get; set; } = Array.Empty<string>();
public override string ToString()
{
return Name;
}
public StateVariable()
{
AllowedValues = Array.Empty<string>();
}
/// <inheritdoc />
public override string ToString() => Name;
}
}

View file

@ -1,23 +1,92 @@
#pragma warning disable CS1591
namespace Emby.Dlna.Configuration
{
/// <summary>
/// The DlnaOptions class contains the user definable parameters for the dlna subsystems.
/// </summary>
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; }
/// <summary>
/// Initializes a new instance of the <see cref="DlnaOptions"/> class.
/// </summary>
public DlnaOptions()
{
EnablePlayTo = true;
EnableServer = true;
BlastAliveMessages = true;
SendOnlyMatchedHost = true;
ClientDiscoveryIntervalSeconds = 60;
BlastAliveMessageIntervalSeconds = 1800;
AliveMessageIntervalSeconds = 1800;
}
/// <summary>
/// Gets or sets a value indicating whether gets or sets a value to indicate the status of the dlna playTo subsystem.
/// </summary>
public bool EnablePlayTo { get; set; }
/// <summary>
/// Gets or sets a value indicating whether gets or sets a value to indicate the status of the dlna server subsystem.
/// </summary>
public bool EnableServer { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool EnableDebugLog { get; set; }
/// <summary>
/// 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.
/// </summary>
public bool EnablePlayToTracing { get; set; }
/// <summary>
/// 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.
/// </summary>
public int ClientDiscoveryIntervalSeconds { get; set; }
/// <summary>
/// Gets or sets the frequency at which ssdp alive notifications are transmitted.
/// </summary>
public int AliveMessageIntervalSeconds { get; set; }
/// <summary>
/// Gets or sets the frequency at which ssdp alive notifications are transmitted. MIGRATING - TO BE REMOVED ONCE WEB HAS BEEN ALTERED.
/// </summary>
public int BlastAliveMessageIntervalSeconds
{
get
{
return AliveMessageIntervalSeconds;
}
set
{
AliveMessageIntervalSeconds = value;
}
}
/// <summary>
/// Gets or sets the default user account that the dlna server uses.
/// </summary>
public string? DefaultUserId { get; set; }
/// <summary>
/// Gets or sets a value indicating whether playTo device profiles should be created.
/// </summary>
public bool AutoCreatePlayToProfiles { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to blast alive messages.
/// </summary>
public bool BlastAliveMessages { get; set; } = true;
/// <summary>
/// gets or sets a value indicating whether to send only matched host.
/// </summary>
public bool SendOnlyMatchedHost { get; set; } = true;
}
}

View file

@ -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<DlnaOptions>("dlna");
}
}
public class DlnaConfigurationFactory : IConfigurationFactory
{
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new ConfigurationStore[]
{
new ConfigurationStore
{
Key = "dlna",
ConfigurationType = typeof (DlnaOptions)
}
};
}
}
}

View file

@ -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<string, string> 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);
}
}
}

View file

@ -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
{
/// <summary>
/// Defines the <see cref="ConnectionManagerService" />.
/// </summary>
public class ConnectionManagerService : BaseService, IConnectionManager
{
private readonly IDlnaManager _dlna;
private readonly IServerConfigurationManager _config;
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionManagerService"/> class.
/// </summary>
/// <param name="dlna">The <see cref="IDlnaManager"/> for use with the <see cref="ConnectionManagerService"/> instance.</param>
/// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="ConnectionManagerService"/> instance.</param>
/// <param name="logger">The <see cref="ILogger{ConnectionManagerService}"/> for use with the <see cref="ConnectionManagerService"/> instance..</param>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> for use with the <see cref="ConnectionManagerService"/> instance..</param>
public ConnectionManagerService(
IDlnaManager dlna,
IServerConfigurationManager config,
ILogger<ConnectionManagerService> logger,
IHttpClientFactory httpClientFactory)
: base(logger, httpClientFactory)
{
_dlna = dlna;
_config = config;
}
/// <inheritdoc />
public string GetServiceXml()
{
return ConnectionManagerXmlBuilder.GetXml();
}
/// <inheritdoc />
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
{
var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile();
return new ControlHandler(_config, Logger, profile).ProcessControlRequestAsync(request);
}
}
}

View file

@ -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
/// <summary>
/// Defines the <see cref="ConnectionManagerXmlBuilder" />.
/// </summary>
public static class ConnectionManagerXmlBuilder
{
public string GetXml()
/// <summary>
/// Gets the ConnectionManager:1 service template.
/// See http://upnp.org/specs/av/UPnP-av-ConnectionManager-v1-Service.pdf.
/// </summary>
/// <returns>An XML description of this service.</returns>
public static string GetXml()
{
return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(), GetStateVariables());
return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables());
}
/// <summary>
/// Get the list of state variables for this invocation.
/// </summary>
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
private static IEnumerable<StateVariable> GetStateVariables()
{
var list = new List<StateVariable>();
list.Add(new StateVariable
var list = new List<StateVariable>
{
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;
}

View file

@ -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
{
/// <summary>
/// Defines the <see cref="ControlHandler" />.
/// </summary>
public class ControlHandler : BaseControlHandler
{
private readonly DeviceProfile _profile;
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
/// <summary>
/// Initializes a new instance of the <see cref="ControlHandler"/> class.
/// </summary>
/// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
/// <param name="logger">The <see cref="ILogger"/> for use with the <see cref="ControlHandler"/> instance.</param>
/// <param name="profile">The <see cref="DeviceProfile"/> for use with the <see cref="ControlHandler"/> instance.</param>
public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
: base(config, logger)
{
_profile = profile;
}
/// <inheritdoc />
protected override void WriteResult(string methodName, IReadOnlyDictionary<string, string> 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<KeyValuePair<string, string>> HandleGetProtocolInfo()
/// <summary>
/// Builds the response to the GetProtocolInfo request.
/// </summary>
/// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
private void HandleGetProtocolInfo(XmlWriter xmlWriter)
{
return new Dictionary<string, string>(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);
}
}
}

View file

@ -1,11 +1,20 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Dlna.Common;
namespace Emby.Dlna.ConnectionManager
{
public class ServiceActionListBuilder
/// <summary>
/// Defines the <see cref="ServiceActionListBuilder" />.
/// </summary>
public static class ServiceActionListBuilder
{
public IEnumerable<ServiceAction> GetActions()
/// <summary>
/// Returns an enumerable of the ConnectionManagar:1 DLNA actions.
/// </summary>
/// <returns>An <see cref="IEnumerable{ServiceAction}"/>.</returns>
public static IEnumerable<ServiceAction> GetActions()
{
var list = new List<ServiceAction>
{
@ -19,6 +28,10 @@ namespace Emby.Dlna.ConnectionManager
return list;
}
/// <summary>
/// Returns the action details for "PrepareForConnection".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction PrepareForConnection()
{
var action = new ServiceAction
@ -78,6 +91,10 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
/// <summary>
/// Returns the action details for "GetCurrentConnectionInfo".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetCurrentConnectionInfo()
{
var action = new ServiceAction
@ -144,7 +161,11 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
private ServiceAction GetProtocolInfo()
/// <summary>
/// Returns the action details for "GetProtocolInfo".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetProtocolInfo()
{
var action = new ServiceAction
{
@ -168,7 +189,11 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
private ServiceAction GetCurrentConnectionIDs()
/// <summary>
/// Returns the action details for "GetCurrentConnectionIDs".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetCurrentConnectionIDs()
{
var action = new ServiceAction
{
@ -185,7 +210,11 @@ namespace Emby.Dlna.ConnectionManager
return action;
}
private ServiceAction ConnectionComplete()
/// <summary>
/// Returns the action details for "ConnectionComplete".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction ConnectionComplete()
{
var action = new ServiceAction
{

View file

@ -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<string, string> 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;
}
}
}

View file

@ -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
{
/// <summary>
/// Defines the <see cref="ContentDirectoryService" />.
/// </summary>
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;
/// <summary>
/// Initializes a new instance of the <see cref="ContentDirectoryService"/> class.
/// </summary>
/// <param name="dlna">The <see cref="IDlnaManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="userDataManager">The <see cref="IUserDataManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="imageProcessor">The <see cref="IImageProcessor"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="libraryManager">The <see cref="ILibraryManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="config">The <see cref="IServerConfigurationManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="userManager">The <see cref="IUserManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="logger">The <see cref="ILogger{ContentDirectoryService}"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="httpClient">The <see cref="IHttpClientFactory"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="localization">The <see cref="ILocalizationManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="mediaSourceManager">The <see cref="IMediaSourceManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="userViewManager">The <see cref="IUserViewManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="mediaEncoder">The <see cref="IMediaEncoder"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
/// <param name="tvSeriesManager">The <see cref="ITVSeriesManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
public ContentDirectoryService(
IDlnaManager dlna,
IUserDataManager userDataManager,
IImageProcessor imageProcessor,
ILibraryManager libraryManager,
IServerConfigurationManager config,
IUserManager userManager,
ILogger<ContentDirectoryService> 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;
}
/// <summary>
/// Gets the system id. (A unique id which changes on when our definition changes.)
/// </summary>
private static int SystemUpdateId
{
get
{
var now = DateTime.UtcNow;
return now.Year + now.DayOfYear + now.Hour;
}
}
/// <inheritdoc />
public string GetServiceXml()
{
return ContentDirectoryXmlBuilder.GetXml();
}
/// <inheritdoc />
public Task<ControlResponse> 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);
}
/// <summary>
/// Get the user stored in the device profile.
/// </summary>
/// <param name="profile">The <see cref="DeviceProfile"/>.</param>
/// <returns>The <see cref="User"/>.</returns>
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();
}
}
}

View file

@ -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
/// <summary>
/// Defines the <see cref="ContentDirectoryXmlBuilder" />.
/// </summary>
public static class ContentDirectoryXmlBuilder
{
public string GetXml()
/// <summary>
/// Gets the ContentDirectory:1 service template.
/// See http://upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf.
/// </summary>
/// <returns>An XML description of this service.</returns>
public static string GetXml()
{
return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(),
GetStateVariables());
return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables());
}
/// <summary>
/// Get the list of state variables for this invocation.
/// </summary>
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
private static IEnumerable<StateVariable> GetStateVariables()
{
var list = new List<StateVariable>();
list.Add(new StateVariable
var list = new List<StateVariable>
{
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;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,36 @@
#pragma warning disable CS1591
using MediaBrowser.Controller.Entities;
namespace Emby.Dlna.ContentDirectory
{
/// <summary>
/// Defines the <see cref="ServerItem" />.
/// </summary>
internal class ServerItem
{
/// <summary>
/// Initializes a new instance of the <see cref="ServerItem"/> class.
/// </summary>
/// <param name="item">The <see cref="BaseItem"/>.</param>
public ServerItem(BaseItem item)
{
Item = item;
if (item is IItemByName && item is not Folder)
{
StubType = Dlna.ContentDirectory.StubType.Folder;
}
}
/// <summary>
/// Gets or sets the underlying base item.
/// </summary>
public BaseItem Item { get; set; }
/// <summary>
/// Gets or sets the DLNA item type.
/// </summary>
public StubType? StubType { get; set; }
}
}

View file

@ -3,9 +3,16 @@ using Emby.Dlna.Common;
namespace Emby.Dlna.ContentDirectory
{
public class ServiceActionListBuilder
/// <summary>
/// Defines the <see cref="ServiceActionListBuilder" />.
/// </summary>
public static class ServiceActionListBuilder
{
public IEnumerable<ServiceAction> GetActions()
/// <summary>
/// Returns a list of services that this instance provides.
/// </summary>
/// <returns>An <see cref="IEnumerable{ServiceAction}"/>.</returns>
public static IEnumerable<ServiceAction> GetActions()
{
return new[]
{
@ -20,6 +27,10 @@ namespace Emby.Dlna.ContentDirectory
};
}
/// <summary>
/// Returns the action details for "GetSystemUpdateID".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetGetSystemUpdateIDAction()
{
var action = new ServiceAction
@ -37,6 +48,10 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
/// <summary>
/// Returns the action details for "GetSearchCapabilities".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetSearchCapabilitiesAction()
{
var action = new ServiceAction
@ -54,6 +69,10 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
/// <summary>
/// Returns the action details for "GetSortCapabilities".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetSortCapabilitiesAction()
{
var action = new ServiceAction
@ -71,6 +90,10 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
/// <summary>
/// Returns the action details for "X_GetFeatureList".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetX_GetFeatureListAction()
{
var action = new ServiceAction
@ -88,6 +111,10 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
/// <summary>
/// Returns the action details for "Search".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetSearchAction()
{
var action = new ServiceAction
@ -168,7 +195,11 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
private ServiceAction GetBrowseAction()
/// <summary>
/// Returns the action details for "Browse".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetBrowseAction()
{
var action = new ServiceAction
{
@ -248,7 +279,11 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
private ServiceAction GetBrowseByLetterAction()
/// <summary>
/// Returns the action details for "X_BrowseByLetter".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetBrowseByLetterAction()
{
var action = new ServiceAction
{
@ -335,7 +370,11 @@ namespace Emby.Dlna.ContentDirectory
return action;
}
private ServiceAction GetXSetBookmarkAction()
/// <summary>
/// Returns the action details for "X_SetBookmark".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetXSetBookmarkAction()
{
var action = new ServiceAction
{

View file

@ -0,0 +1,30 @@
#pragma warning disable CS1591
namespace Emby.Dlna.ContentDirectory
{
/// <summary>
/// Defines the DLNA item types.
/// </summary>
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
}
}

View file

@ -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<string, string> 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<string, string>();
}
}
}

View file

@ -1,18 +1,28 @@
#pragma warning disable CS1591
using System.Collections.Generic;
namespace Emby.Dlna
{
public class ControlResponse
{
public IDictionary<string, string> Headers { get; set; }
public ControlResponse(string xml, bool isSuccessful)
{
Headers = new Dictionary<string, string>();
Xml = xml;
IsSuccessful = isSuccessful;
}
public IDictionary<string, string> Headers { get; }
public string Xml { get; set; }
public bool IsSuccessful { get; set; }
public ControlResponse()
/// <inheritdoc />
public override string ToString()
{
Headers = new Dictionary<string, string>();
return Xml;
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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));
}
}
}

View file

@ -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;
}
}

View file

@ -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<ConfigurationStore> GetConfigurations()
{
return new[]
{
new ConfigurationStore
{
Key = "dlna",
ConfigurationType = typeof(DlnaOptions)
}
};
}
}
}

View file

@ -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<DlnaManager> _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<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(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<DlnaManager>();
_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();
}
}
/// <inheritdoc />
public DeviceProfile GetDefaultProfile()
{
return new DefaultProfile();
}
public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
/// <inheritdoc />
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)
/// <summary>
/// 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.
/// </summary>
/// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param>
/// <param name="profileInfo">The <see cref="DeviceIdentification"/> of the profile.</param>
/// <returns><b>True</b> if they match.</returns>
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<string, string> headers)
/// <inheritdoc />
public DeviceProfile? GetProfile(IHeaderDictionary headers)
{
if (headers == null)
{
throw new ArgumentNullException(nameof(headers));
}
// Convert to case insensitive
headers = new Dictionary<string, string>(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<string, string> headers, DeviceIdentification profileInfo)
private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo)
{
return profileInfo.Headers.Any(i => IsMatch(headers, i));
}
private bool IsMatch(IDictionary<string, string> 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<DeviceProfile> 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<DeviceProfile>();
return Array.Empty<DeviceProfile>();
}
}
private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
{
lock (_profiles)
{
if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile> profileTuple))
if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile>? 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<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
@ -318,14 +291,20 @@ namespace Emby.Dlna
}
}
public DeviceProfile GetProfile(string id)
/// <inheritdoc />
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
}
}
/// <inheritdoc />
public IEnumerable<DeviceProfileInfo> 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);
}
/// <inheritdoc />
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
}
}
/// <inheritdoc />
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)
/// <inheritdoc />
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<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
}
SerializeToXml(profile, path);
}
@ -471,10 +461,10 @@ namespace Emby.Dlna
/// <summary>
/// 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).
/// </summary>
/// <param name="profile"></param>
/// <returns></returns>
/// <param name="profile">The device profile.</param>
/// <returns>The re-serialized device profile.</returns>
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<DeviceProfile>(json);
// Output can't be null if the input isn't null
return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions)!;
}
class InternalProfileInfo
/// <inheritdoc />
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
{
internal DeviceProfileInfo Info { get; set; }
internal string Path { get; set; }
}
public string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId, string serverAddress)
{
var profile = GetProfile(headers) ??
GetDefaultProfile();
var profile = GetDefaultProfile();
var serverId = _appHost.SystemId;
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
}
public ImageStream GetIcon(string filename)
/// <inheritdoc />
public ImageStream? GetIcon(string filename)
{
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
? ImageFormat.Png
: ImageFormat.Jpg;
var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant();
return new ImageStream
var stream = _assembly.GetManifestResourceStream(resource);
if (stream == null)
{
Format = format,
Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource)
return null;
}
return new ImageStream(stream)
{
Format = format
};
}
private class InternalProfileInfo
{
internal InternalProfileInfo(DeviceProfileInfo info, string path)
{
Info = info;
Path = path;
}
internal DeviceProfileInfo Info { get; }
internal string Path { get; }
}
}
/*
class DlnaProfileEntryPoint : IServerEntryPoint
{
@ -539,7 +543,7 @@ namespace Emby.Dlna
private void DumpProfiles()
{
DeviceProfile[] list = new []
DeviceProfile[] list = new[]
{
new SamsungSmartTvProfile(),
new XboxOneProfile(),
@ -563,9 +567,9 @@ namespace Emby.Dlna
new Foobar2000Profile(),
new SharpSmartTvProfile(),
new MediaMonkeyProfile(),
//new Windows81Profile(),
//new WindowsMediaCenterProfile(),
//new WindowsPhoneProfile(),
// new Windows81Profile(),
// new WindowsMediaCenterProfile(),
// new WindowsPhoneProfile(),
new DirectTvProfile(),
new DishHopperJoeyProfile(),
new DefaultProfile(),

View file

@ -1,5 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
<ProjectGuid>{805844AB-E92F-45E6-9D99-4F6D48D129A5}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
@ -12,10 +17,18 @@
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Images\logo120.jpg" />
<EmbeddedResource Include="Images\logo120.png" />
@ -58,4 +71,8 @@
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.2*" />
</ItemGroup>
</Project>

View file

@ -1,17 +1,22 @@
#pragma warning disable CS1591
using System.Collections.Generic;
namespace Emby.Dlna
{
public class EventSubscriptionResponse
{
public string Content { get; set; }
public string ContentType { get; set; }
public Dictionary<string, string> Headers { get; set; }
public EventSubscriptionResponse()
public EventSubscriptionResponse(string content, string contentType)
{
Content = content;
ContentType = contentType;
Headers = new Dictionary<string, string>();
}
public string Content { get; set; }
public string ContentType { get; set; }
public Dictionary<string, string> Headers { get; }
}
}

Some files were not shown because too many files have changed in this diff Show more