Compare commits

..

14 Commits

Author SHA1 Message Date
Joshua Boniface
cf54a0e8be Fix missed backport change
This stupid file is back; looks like its location changed in .NET 3.0
versus .NET 2.2. Just revert it back to its original location.

Related to #1859
2019-11-03 14:50:03 -05:00
Joshua Boniface
e73cf46e14 Bump version to 10.4.1 2019-11-03 14:45:56 -05:00
dkanada
39c3b2f044 Merge pull request #1954 from LogicalPhallacy/LogicalPhallacy-patch-NSSM
Use mirror for NSSM

(cherry picked from commit ef623f5129)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-03 14:45:20 -05:00
Joshua M. Boniface
7a592a0f15 Merge pull request #1904 from JustAMan/hls-move-2
Switch ffmpeg to hls muxer (from segment) to fix premature stop on non-patched ffmpeg

(cherry picked from commit a460814182)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-03 14:45:07 -05:00
Joshua M. Boniface
1fad64cd59 Merge pull request #1903 from anthonylavado/nsis-update
Update NSIS Installer

(cherry picked from commit 9756bdb76e)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-03 14:44:57 -05:00
Joshua M. Boniface
86e5dc4607 Merge pull request #1859 from joshuaboniface/copr-fix
Fix COPR build and Fedora packaging

(cherry picked from commit 5d5fa55fe5)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-11-03 14:40:10 -05:00
Bond-009
e98e4766f7 Merge pull request #1933 from cvium/autoreload_log_config
Reload logging.json on changes

(cherry picked from commit da7ba822b0)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-24 09:38:42 -04:00
Joshua M. Boniface
6e59671cf6 Merge pull request #1898 from Bond-009/jsonfix
Fix Json serialization error

(cherry picked from commit 91600b1c81)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 15:17:39 -04:00
Bond-009
86a50367b2 Merge pull request #1909 from KerryRJ/FixDvdsFailingToPlay
Fix System.NullReferenceException when playing Dvds copied to HDD

(cherry picked from commit fdb0c3a1df)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:22:13 -04:00
Bond-009
0212c0b85f Merge pull request #1870 from JustAMan/fix-http-ex1
Fix exception when handling error, log errors better

(cherry picked from commit d8d2e52e3f)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:21:54 -04:00
Vasily
89d365122c Merge pull request #1866 from Bond-009/sqlslow
Change slow query time logging to debug

(cherry picked from commit cadfd5bf3f)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:21:41 -04:00
Vasily
9c0a8350d6 Merge pull request #1863 from joshuaboniface/fix-baseurl-issues
Fix inconsistent BaseUrl behavior

(cherry picked from commit 1176749f14)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:21:26 -04:00
Vasily
818d21718c Merge pull request #1862 from joshuaboniface/bump-version
Fix bump_version for submodule removal

(cherry picked from commit aa9d7d7f04)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:21:06 -04:00
Vasily
0b551c0cd4 Merge pull request #1861 from joshuaboniface/fix-centos-build
Use NVM to install nodejs v8 and yarn for CentOS

(cherry picked from commit 094852ce30)
Signed-off-by: Joshua Boniface <joshua@boniface.me>
2019-10-20 14:20:41 -04:00
2206 changed files with 82897 additions and 102067 deletions

View File

@@ -1,93 +0,0 @@
parameters:
- name: Packages
type: object
default: {}
- name: LinuxImage
type: string
default: "ubuntu-latest"
- name: DotNetSdkVersion
type: string
default: 5.0.100
jobs:
- job: CompatibilityCheck
displayName: Compatibility Check
dependsOn: Build
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
pool:
vmImage: "${{ parameters.LinuxImage }}"
strategy:
matrix:
${{ each Package in parameters.Packages }}:
${{ Package.key }}:
NugetPackageName: ${{ Package.value.NugetPackageName }}
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
maxParallel: 2
steps:
- checkout: none
- task: UseDotNet@2
displayName: "Update DotNet"
inputs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- task: DotNetCoreCLI@2
displayName: 'Install ABI CompatibilityChecker Tool'
inputs:
command: custom
custom: tool
arguments: 'update compatibilitychecker -g'
- task: DownloadPipelineArtifact@2
displayName: 'Download New Assembly Build Artifact'
inputs:
source: 'current'
artifact: "$(NugetPackageName)"
path: "$(System.ArtifactsDirectory)/new-artifacts"
runVersion: "latest"
- task: CopyFiles@2
displayName: 'Copy New Assembly Build Artifact'
inputs:
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/new-release
cleanTargetFolder: true
overWrite: true
flattenFolders: true
- task: DownloadPipelineArtifact@2
displayName: 'Download Reference Assembly Build Artifact'
enabled: false
inputs:
source: "specific"
artifact: "$(NugetPackageName)"
path: "$(System.ArtifactsDirectory)/current-artifacts"
project: "$(System.TeamProjectId)"
pipeline: "$(System.DefinitionId)"
runVersion: "latestFromBranch"
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
- task: CopyFiles@2
displayName: 'Copy Reference Assembly Build Artifact'
enabled: false
inputs:
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/current-release
cleanTargetFolder: true
overWrite: true
flattenFolders: true
- task: DotNetCoreCLI@2
displayName: 'Execute ABI Compatibility Check Tool'
enabled: false
inputs:
command: custom
custom: compat
arguments: 'current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
workingDirectory: $(System.ArtifactsDirectory)

View File

@@ -1,59 +0,0 @@
parameters:
- name: LinuxImage
type: string
default: "ubuntu-latest"
- name: GeneratorVersion
type: string
default: "5.0.0-beta2"
jobs:
- job: GenerateApiClients
displayName: 'Generate Api Clients'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
dependsOn: Test
pool:
vmImage: "${{ parameters.LinuxImage }}"
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download OpenAPI Spec Artifact'
inputs:
source: 'current'
artifact: "OpenAPI Spec"
path: "$(System.ArtifactsDirectory)/openapispec"
runVersion: "latest"
- task: CmdLine@2
displayName: 'Download OpenApi Generator'
inputs:
script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar"
## Authenticate with npm registry
- task: npmAuthenticate@0
inputs:
workingFile: ./.npmrc
customEndpoint: 'jellyfin-bot for NPM'
## Generate npm api client
- task: CmdLine@2
displayName: 'Build stable typescript axios client'
inputs:
script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)"
## Run npm install
- task: Npm@1
displayName: 'Install npm dependencies'
inputs:
command: install
workingDir: ./apiclient/generated/typescript/axios
## Publish npm packages
- task: Npm@1
displayName: 'Publish stable typescript axios client'
inputs:
command: custom
customCommand: publish --access public
publishRegistry: useExternalRegistry
publishEndpoint: 'jellyfin-bot for NPM'
workingDir: ./apiclient/generated/typescript/axios

View File

@@ -1,93 +0,0 @@
parameters:
LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
DotNetSdkVersion: 5.0.100
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 }}
- 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'

View File

@@ -1,252 +0,0 @@
jobs:
- job: BuildPackage
displayName: 'Build Packages'
strategy:
matrix:
CentOS.amd64:
BuildConfiguration: centos.amd64
Fedora.amd64:
BuildConfiguration: fedora.amd64
Debian.amd64:
BuildConfiguration: debian.amd64
Debian.arm64:
BuildConfiguration: debian.arm64
Debian.armhf:
BuildConfiguration: debian.armhf
Ubuntu.amd64:
BuildConfiguration: ubuntu.amd64
Ubuntu.arm64:
BuildConfiguration: ubuntu.arm64
Ubuntu.armhf:
BuildConfiguration: ubuntu.armhf
Linux.amd64:
BuildConfiguration: linux.amd64
Linux.amd64-musl:
BuildConfiguration: linux.amd64-musl
Linux.arm64:
BuildConfiguration: linux.arm64
Linux.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
condition: and(succeeded('BuildPackage'), succeeded('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'
dependsOn:
- BuildPackage
condition: succeeded('BuildPackage')
pool:
vmImage: 'ubuntu-latest'
steps:
- task: UseDotNet@2
displayName: 'Use .NET 5.0 sdk'
inputs:
packageType: 'sdk'
version: '5.0.x'
- task: DotNetCoreCLI@2
displayName: 'Build Stable Nuget packages'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
inputs:
command: 'pack'
packagesToPack: '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'
versioningScheme: 'off'
- task: DotNetCoreCLI@2
displayName: 'Build Unstable Nuget packages'
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
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;$(Build.ArtifactStagingDirectory)/**/*.snupkg'
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

@@ -1,98 +0,0 @@
parameters:
- name: ImageNames
type: object
default:
Linux: "ubuntu-latest"
Windows: "windows-latest"
macOS: "macos-latest"
- name: TestProjects
type: string
default: "tests/**/*Tests.csproj"
- name: DotNetSdkVersion
type: string
default: 5.0.100
jobs:
- job: Test
displayName: Test
strategy:
matrix:
${{ each imageName in parameters.ImageNames }}:
${{ imageName.key }}:
ImageName: ${{ imageName.value }}
pool:
vmImage: "$(ImageName)"
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: false
# This is required for the SonarCloud analyzer
- task: UseDotNet@2
displayName: "Install .NET SDK 5.x"
condition: eq(variables['ImageName'], 'ubuntu-latest')
inputs:
packageType: sdk
version: '5.x'
- task: UseDotNet@2
displayName: "Update DotNet"
inputs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- task: SonarCloudPrepare@1
displayName: 'Prepare analysis on SonarCloud'
condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
inputs:
SonarCloud: 'Sonarcloud for Jellyfin'
organization: 'jellyfin'
projectKey: 'jellyfin_jellyfin'
- task: DotNetCoreCLI@2
displayName: 'Run CLI Tests'
inputs:
command: "test"
projects: ${{ parameters.TestProjects }}
arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal'
publishTestResults: true
testRunTitle: $(Agent.JobName)
workingDirectory: "$(Build.SourcesDirectory)"
- task: SonarCloudAnalyze@1
displayName: 'Run Code Analysis'
condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
- task: SonarCloudPublish@1
displayName: 'Publish Quality Gate Result'
condition: eq(variables['ImageName'], 'ubuntu-latest')
enabled: false
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Run ReportGenerator'
inputs:
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
targetdir: "$(Agent.TempDirectory)/merged/"
reporttypes: "Cobertura"
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
- task: PublishCodeCoverageResults@1
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
displayName: 'Publish Code Coverage'
inputs:
codeCoverageTool: "cobertura"
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
pathToSources: $(Build.SourcesDirectory)
failIfCoverageEmpty: true
- task: PublishPipelineArtifact@1
displayName: 'Publish OpenAPI Artifact'
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
inputs:
targetPath: "tests/Jellyfin.Api.Tests/bin/Release/net5.0/openapi.json"
artifactName: 'OpenAPI Spec'

View File

@@ -1,50 +1,283 @@
name: $(Date:yyyyMMdd)$(Rev:.r)
variables:
- name: TestProjects
value: 'tests/**/*Tests.csproj'
- name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
- name: DotNetSdkVersion
value: 5.0.100
- name: TestProjects
value: 'tests/Jellyfin.Common.Tests/Jellyfin.Common.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)
- job: main_build
displayName: Main Build
pool:
vmImage: ubuntu-latest
strategy:
matrix:
release:
BuildConfiguration: Release
debug:
BuildConfiguration: Debug
maxParallel: 2
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: true
- ${{ 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'
- task: CmdLine@2
displayName: "Check out web"
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-abi.yml
parameters:
Packages:
- task: CmdLine@2
displayName: "Check out web (PR)"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
inputs:
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- task: NodeTool@0
displayName: 'Install Node.js'
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
versionSpec: '10.x'
- task: CmdLine@2
displayName: "Build Web UI"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: yarn install
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
- task: CopyFiles@2
displayName: Copy the web UI
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
contents: '**'
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: false # Optional
- task: DotNetCoreCLI@2
displayName: Publish
inputs:
command: publish
publishWebProjects: false
projects: '$(RestoreBuildProjects)'
arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
zipAfterPublish: false
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Naming'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
artifactName: 'Jellyfin.Naming'
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Controller'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
artifactName: 'Jellyfin.Controller'
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Model'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
artifactName: 'Jellyfin.Model'
- task: PublishPipelineArtifact@0
displayName: 'Publish Artifact Common'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
artifactName: 'Jellyfin.Common'
- job: main_test
displayName: Main Test
pool:
vmImage: windows-latest
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: false
- task: DotNetCoreCLI@2
displayName: Build
inputs:
command: build
publishWebProjects: false
projects: '$(TestProjects)'
arguments: '--configuration $(BuildConfiguration)'
zipAfterPublish: false
- task: VisualStudioTestPlatformInstaller@1
inputs:
packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
- task: VSTest@2
inputs:
testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
testAssemblyVer2: | # Required when testSelector == TestAssemblies
**\bin\$(BuildConfiguration)\**\*test*.dll
!**\obj\**
!**\xunit.runner.visualstudio.testadapter.dll
!**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
#testPlan: # Required when testSelector == TestPlan
#testSuite: # Required when testSelector == TestPlan
#testConfiguration: # Required when testSelector == TestPlan
#tcmTestRun: '$(test.RunId)' # Optional
searchFolder: '$(System.DefaultWorkingDirectory)'
#testFiltercriteria: # Optional
#runOnlyImpactedTests: False # Optional
#runAllTestsAfterXBuilds: '50' # Optional
#uiTests: false # Optional
#vstestLocationMethod: 'version' # Optional. Options: version, location
#vsTestVersion: 'latest' # Optional. Options: latest, 16.0, 15.0, 14.0, toolsInstaller
#vstestLocation: # Optional
#runSettingsFile: # Optional
#overrideTestrunParameters: # Optional
#pathtoCustomTestAdapters: # Optional
runInParallel: True # Optional
runTestsInIsolation: True # Optional
codeCoverageEnabled: True # Optional
#otherConsoleOptions: # Optional
#distributionBatchType: 'basedOnTestCases' # Optional. Options: basedOnTestCases, basedOnExecutionTime, basedOnAssembly
#batchingBasedOnAgentsOption: 'autoBatchSize' # Optional. Options: autoBatchSize, customBatchSize
#customBatchSizeValue: '10' # Required when distributionBatchType == BasedOnTestCases && BatchingBasedOnAgentsOption == CustomBatchSize
#batchingBasedOnExecutionTimeOption: 'autoBatchSize' # Optional. Options: autoBatchSize, customTimeBatchSize
#customRunTimePerBatchValue: '60' # Required when distributionBatchType == BasedOnExecutionTime && BatchingBasedOnExecutionTimeOption == CustomTimeBatchSize
#dontDistribute: False # Optional
#testRunTitle: # Optional
#platform: # Optional
configuration: 'Debug' # Optional
publishRunAttachments: true # Optional
#diagnosticsEnabled: false # Optional
#collectDumpOn: 'onAbortOnly' # Optional. Options: onAbortOnly, always, never
#rerunFailedTests: False # Optional
#rerunType: 'basedOnTestFailurePercentage' # Optional. Options: basedOnTestFailurePercentage, basedOnTestFailureCount
#rerunFailedThreshold: '30' # Optional
#rerunFailedTestCasesMaxLimit: '5' # Optional
#rerunMaxAttempts: '3' # Optional
# - task: PublishTestResults@2
# inputs:
# testResultsFormat: 'VSTest' # Options: JUnit, NUnit, VSTest, xUnit, cTest
# testResultsFiles: '**/*.trx'
# #searchFolder: '$(System.DefaultWorkingDirectory)' # Optional
# mergeTestResults: true # Optional
# #failTaskOnFailedTests: false # Optional
# #testRunTitle: # Optional
# #buildPlatform: # Optional
# #buildConfiguration: # Optional
# #publishRunAttachments: true # Optional
- job: main_build_win
displayName: Main Build Windows
pool:
vmImage: windows-latest
strategy:
matrix:
release:
BuildConfiguration: Release
maxParallel: 2
steps:
- checkout: self
clean: true
submodules: true
persistCredentials: true
- task: CmdLine@2
displayName: "Check out web"
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- task: CmdLine@2
displayName: "Check out web (PR)"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
inputs:
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
- task: NodeTool@0
displayName: 'Install Node.js'
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
versionSpec: '10.x'
- task: CmdLine@2
displayName: "Build Web UI"
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
script: yarn install
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
- task: CopyFiles@2
displayName: Copy the web UI
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
inputs:
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
contents: '**'
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: false # Optional
- task: CmdLine@2
displayName: Clone the UX repository
inputs:
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
- task: PowerShell@2
displayName: Build the NSIS Installer
inputs:
targetType: 'filePath' # Optional. Options: filePath, inline
filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
#script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
#failOnStderr: false # Optional
#ignoreLASTEXITCODE: false # Optional
#pwsh: false # Optional
workingDirectory: $(Build.SourcesDirectory) # Optional
- task: CopyFiles@2
displayName: Copy the NSIS Installer to the artifact directory
inputs:
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
contents: 'jellyfin*.exe'
targetFolder: $(System.ArtifactsDirectory)/setup
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: PublishPipelineArtifact@0
displayName: 'Publish Setup Artifact'
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
inputs:
targetPath: '$(build.artifactstagingdirectory)/setup'
artifactName: 'Jellyfin Server Setup'
- job: dotnet_compat
displayName: Compatibility Check
pool:
vmImage: ubuntu-latest
dependsOn: main_build
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber']) # Only execute if the pullrequest numer is defined. (So not for normal CI builds)
strategy:
matrix:
Naming:
NugetPackageName: Jellyfin.Naming
AssemblyFileName: Emby.Naming.dll
@@ -57,10 +290,82 @@ jobs:
Common:
NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll
LinuxImage: 'ubuntu-latest'
maxParallel: 2
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download the New Assembly Build Artifact
inputs:
source: 'current' # Options: current, specific
#preferTriggeringPipeline: false # Optional
#tags: # Optional
artifact: '$(NugetPackageName)' # Optional
#patterns: '**' # Optional
path: '$(System.ArtifactsDirectory)/new-artifacts'
#project: # Required when source == Specific
#pipeline: # Required when source == Specific
runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific
#runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
#runId: # Required when source == Specific && runVersion == Specific
- task: CopyFiles@2
displayName: Copy New Assembly to new-release folder
inputs:
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/new-release
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: DownloadPipelineArtifact@2
displayName: Download the Reference Assembly Build Artifact
inputs:
source: 'specific' # Options: current, specific
#preferTriggeringPipeline: false # Optional
#tags: # Optional
artifact: '$(NugetPackageName)' # Optional
#patterns: '**' # Optional
path: '$(System.ArtifactsDirectory)/current-artifacts'
project: '$(System.TeamProjectId)' # Required when source == Specific
pipeline: '$(System.DefinitionId)' # Required when source == Specific
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch
#runId: # Required when source == Specific && runVersion == Specific
- task: CopyFiles@2
displayName: Copy Reference Assembly to current-release folder
inputs:
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
contents: '**/*.dll'
targetFolder: $(System.ArtifactsDirectory)/current-release
cleanTargetFolder: true # Optional
overWrite: true # Optional
flattenFolders: true # Optional
- task: DownloadGitHubRelease@0
displayName: Download ABI compatibility check tool from GitHub
inputs:
connection: Jellyfin Release Download
userRepository: EraYaN/dotnet-compatibility
defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
#version: # Required when defaultVersionType != Latest
itemPattern: '**-ci.zip' # Optional
downloadPath: '$(System.ArtifactsDirectory)'
- task: ExtractFiles@1
displayName: Extract ABI compatibility check tool
inputs:
archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
destinationFolder: $(System.ArtifactsDirectory)/tools
cleanDestinationFolder: true
- task: CmdLine@2
displayName: Execute ABI compatibility check tool
inputs:
script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines'
workingDirectory: $(System.ArtifactsDirectory) # Optional
#failOnStderr: false # Optional
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-package.yml
- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags/v'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-api-client.yml

46
.ci/publish-nightly.yml Normal file
View File

@@ -0,0 +1,46 @@
name: Nightly-$(date:yyyyMMdd).$(rev:r)
variables:
- name: Version
value: '1.0.0'
trigger: none
pr: none
jobs:
- job: publish_artifacts_nightly
displayName: Publish Artifacts Nightly
pool:
vmImage: ubuntu-latest
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download the Windows Setup Artifact
inputs:
source: 'specific' # Options: current, specific
artifact: 'Jellyfin Server Setup' # Optional
path: '$(System.ArtifactsDirectory)/win-installer'
project: '$(System.TeamProjectId)' # Required when source == Specific
pipelineId: 1 # Required when source == Specific
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
- task: SSH@0
displayName: 'Create Drop directory'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_nightly_azure_upload'
- task: CopyFilesOverSSH@0
displayName: 'Copy the Windows Setup to the Repo'
inputs:
sshEndpoint: 'Jellyfin Build Server'
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
contents: 'jellyfin_*.exe'
targetFolder: '/srv/incoming/jellyfin_nightly_azure_upload/win-installer'
- task: SSH@0
displayName: 'Clean up SCP symlink'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'rm -f /srv/incoming/jellyfin_nightly_azure_upload'

48
.ci/publish-release.yml Normal file
View File

@@ -0,0 +1,48 @@
name: Release-$(Version)-$(date:yyyyMMdd).$(rev:r)
variables:
- name: Version
value: '1.0.0'
- name: UsedRunId
value: 0
trigger: none
pr: none
jobs:
- job: publish_artifacts_release
displayName: Publish Artifacts Release
pool:
vmImage: ubuntu-latest
steps:
- checkout: none
- task: DownloadPipelineArtifact@2
displayName: Download the Windows Setup Artifact
inputs:
source: 'specific' # Options: current, specific
artifact: 'Jellyfin Server Setup' # Optional
path: '$(System.ArtifactsDirectory)/win-installer'
project: '$(System.TeamProjectId)' # Required when source == Specific
pipelineId: 1 # Required when source == Specific
runVersion: 'specific' # Required when source == Specific. Options: latest, latestFromBranch, specific
runId: $(UsedRunId)
- task: SSH@0
displayName: 'Create Drop directory'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_release_azure_upload'
- task: CopyFilesOverSSH@0
displayName: 'Copy the Windows Setup to the Repo'
inputs:
sshEndpoint: 'Jellyfin Build Server'
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
contents: 'jellyfin_*.exe'
targetFolder: '/srv/incoming/jellyfin_release_azure_upload/win-installer'
- task: SSH@0
displayName: 'Clean up SCP symlink'
inputs:
sshEndpoint: 'Jellyfin Build Server'
commands: 'rm -f /srv/incoming/jellyfin_release_azure_upload'

View File

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

59
.copr/Makefile Normal file
View File

@@ -0,0 +1,59 @@
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
deployment/fedora-package-x64/pkg-src/jellyfin.spec)
deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
cd deployment/fedora-package-x64; \
SOURCE_DIR=../.. \
WORKDIR="$${PWD}"; \
package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
pkg_src_dir="$${WORKDIR}/pkg-src"; \
GNU_TAR=1; \
tar \
--transform "s,^\.,jellyfin-$(VERSION)," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./ || GNU_TAR=0; \
if [ $${GNU_TAR} -eq 0 ]; then \
package_temporary_dir="$$(mktemp -d)"; \
mkdir -p "$${package_temporary_dir}/jellyfin"; \
tar \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
--exclude='**/.vs' \
--exclude='**/.vscode' \
--exclude='deployment' \
--exclude='**/bin' \
--exclude='**/obj' \
--exclude='**/.nuget' \
--exclude='*.deb' \
--exclude='*.rpm' \
-czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
-C $${SOURCE_DIR} ./; \
mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
-C "$${package_temporary_dir}/jellyfin-$(VERSION); \
rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
-C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
rm -rf $${package_temporary_dir}; \
fi; \
rpmbuild -bs pkg-src/jellyfin.spec \
--define "_sourcedir $$PWD/pkg-src/" \
--define "_srcrpmdir $(outdir)"

View File

@@ -13,7 +13,7 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
end_of_line = lf
max_line_length = off
max_line_length = null
# YAML indentation
[*.{yml,yaml}]
@@ -22,7 +22,6 @@ indent_size = 2
# XML indentation
[*.{csproj,xml}]
indent_size = 2
###############################
# .NET Coding Conventions #
###############################
@@ -52,12 +51,11 @@ 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_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_prefer_inferred_tuple_names = true:suggestion
dotnet_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 #
###############################
@@ -69,7 +67,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
@@ -161,7 +159,6 @@ 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 #
###############################
@@ -192,3 +189,9 @@ 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

3
.github/CODEOWNERS vendored
View File

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

View File

@@ -10,19 +10,6 @@ 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 72, Chrome 80, Safari 13]
- Jellyfin Version: [e.g. 10.4.3, nightly 20191231]
- Playback: [Direct Play, Remux, Direct Stream, Transcode]
- 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 '...'
@@ -39,5 +26,11 @@ assignees: ''
**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]
- Reverse proxy: [e.g. no, nginx, apache, etc.]
**Additional context**
<!-- Add any other context about the problem here. -->

View File

@@ -1,13 +0,0 @@
---
name: Feature Request
about: Request a new feature
title: ''
labels: feature-request
assignees: ''
---
**PLEASE DO NOT OPEN FEATURE REQUEST ISSUES ON GITHUB**
**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

@@ -11,10 +11,7 @@ assignees: ''
<!-- 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. -->
<!-- Please paste any log message from during the playback issue, for example the ffmpeg command line can be very useful. -->
**Stats for Nerds Screenshots**
<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
@@ -32,3 +29,4 @@ assignees: ''
- 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]

View File

@@ -1,9 +0,0 @@
version: 2
updates:
- package-ecosystem: nuget
directory: "/"
schedule:
interval: weekly
time: '12:00'
open-pull-requests-limit: 10

13
.github/stale.yml vendored
View File

@@ -1,7 +1,7 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 120
daysUntilStale: 90
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 21
daysUntilClose: 14
# Issues with these labels will never be considered stale
exemptLabels:
- regression
@@ -11,15 +11,12 @@ exemptLabels:
- 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).
Issues go stale after 90d of inactivity. Mark the issue as fresh by adding a comment or commit. Stale issues close after an additional 14d of inactivity.
If this issue is safe to close now please do so.
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

View File

@@ -1,36 +0,0 @@
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: '5.0.100'
- 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

23
.gitignore vendored
View File

@@ -244,14 +244,14 @@ pip-log.txt
#########################
# Artifacts for debian-x64
debian/.debhelper/
debian/*.debhelper
debian/debhelper-build-stamp
debian/files
debian/jellyfin.substvars
debian/jellyfin/
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/
# Don't ignore the debian/bin folder
!debian/bin/
!deployment/debian-package-x64/pkg-src/bin/
deployment/**/dist/
deployment/**/pkg-dist/
@@ -268,12 +268,3 @@ doc/
# Deployment artifacts
dist
*.exe
# BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts
# Ignore web artifacts from native builds
web/
web-src.*
MediaBrowser.WebDashboard/jellyfin-web
apiclient/generated

3
.npmrc
View File

@@ -1,3 +0,0 @@
registry=https://registry.npmjs.org/
@jellyfin:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/
always-auth=true

View File

@@ -1,14 +0,0 @@
{
// 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,30 +1,19 @@
{
"version": "0.2.0",
"configurations": [
// 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": [
{
"name": ".NET Core Launch (console)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net5.0/jellyfin.dll",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin.dll",
"args": [],
"cwd": "${workspaceFolder}/Jellyfin.Server",
"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/net5.0/jellyfin.dll",
"args": ["--nowebclient"],
"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"
@@ -35,8 +24,5 @@
"request": "attach",
"processId": "${command:pickProcess}"
}
],
"env": {
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
}
}
,]
}

19
.vscode/tasks.json vendored
View File

@@ -10,21 +10,6 @@
"${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"
}
}
}
]
}

17
BDInfo/BDInfo.csproj Normal file
View File

@@ -0,0 +1,17 @@
<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>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>

33
BDInfo/BDInfoSettings.cs Normal file
View File

@@ -0,0 +1,33 @@
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;
}
}

449
BDInfo/BDROM.cs Normal file
View File

@@ -0,0 +1,449 @@
//============================================================================
// 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()
{
foreach (var streamClipFile in StreamClipFiles.Values)
{
try
{
streamClipFile.Scan();
}
catch (Exception ex)
{
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);
foreach (var playlistFile in PlaylistFiles.Values)
{
try
{
playlistFile.Scan(StreamFiles, StreamClipFiles);
}
catch (Exception ex)
{
if (PlaylistFileScanError != null)
{
if (PlaylistFileScanError(playlistFile, ex))
{
continue;
}
else
{
break;
}
}
else throw;
}
}
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)
{
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;
}
}
}
}
}

493
BDInfo/LanguageCodes.cs Normal file
View File

@@ -0,0 +1,493 @@
//============================================================================
// 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

@@ -0,0 +1,21 @@
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 Server")]
[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[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)]

5
BDInfo/ReadMe.txt Normal file
View File

@@ -0,0 +1,5 @@
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.

309
BDInfo/TSCodecAC3.cs Normal file
View File

@@ -0,0 +1,309 @@
//============================================================================
// 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;
}
}
}

148
BDInfo/TSCodecAVC.cs Normal file
View File

@@ -0,0 +1,148 @@
//============================================================================
// 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;
}
}
}

159
BDInfo/TSCodecDTS.cs Normal file
View File

@@ -0,0 +1,159 @@
//============================================================================
// 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;
}
}
}
}

246
BDInfo/TSCodecDTSHD.cs Normal file
View File

@@ -0,0 +1,246 @@
//============================================================================
// 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);
}
}
}
}

123
BDInfo/TSCodecLPCM.cs Normal file
View File

@@ -0,0 +1,123 @@
//============================================================================
// 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;
}
}
}

208
BDInfo/TSCodecMPEG2.cs Normal file
View File

@@ -0,0 +1,208 @@
//============================================================================
// 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
}
}
}
}
}

36
BDInfo/TSCodecMVC.cs Normal file
View File

@@ -0,0 +1,36 @@
//============================================================================
// 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;
}
}
}

186
BDInfo/TSCodecTrueHD.cs Normal file
View File

@@ -0,0 +1,186 @@
//============================================================================
// 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;
}
}
}

131
BDInfo/TSCodecVC1.cs Normal file
View File

@@ -0,0 +1,131 @@
//============================================================================
// 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

@@ -0,0 +1,37 @@
//============================================================================
// 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();
}
}
}

1282
BDInfo/TSPlaylistFile.cs Normal file

File diff suppressed because it is too large Load Diff

780
BDInfo/TSStream.cs Normal file
View File

@@ -0,0 +1,780 @@
//============================================================================
// 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;
}
}
}

130
BDInfo/TSStreamBuffer.cs Normal file
View File

@@ -0,0 +1,130 @@
//============================================================================
// 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;
}
}
}

107
BDInfo/TSStreamClip.cs Normal file
View File

@@ -0,0 +1,107 @@
//============================================================================
// 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;
}
}
}

244
BDInfo/TSStreamClipFile.cs Normal file
View File

@@ -0,0 +1,244 @@
//============================================================================
// 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();
}
}
}
}

1555
BDInfo/TSStreamFile.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,146 +1,35 @@
# Jellyfin Contributors
- [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)
- [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)
- [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)
- [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)
- [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)
- [grafixeyehero](https://github.com/grafixeyehero)
- [h1nk](https://github.com/h1nk)
- [hawken93](https://github.com/hawken93)
- [HelloWorld017](https://github.com/HelloWorld017)
- [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)
- [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)
- [JoshuaBoniface](https://github.com/joshuaboniface)
- [nvllsvm](https://github.com/nvllsvm)
- [nyanmisaka](https://github.com/nyanmisaka)
- [OancaAndrei](https://github.com/OancaAndrei)
- [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)
- [sorinyo2004](https://github.com/sorinyo2004)
- [JustAMan](https://github.com/JustAMan)
- [dcrdev](https://github.com/dcrdev)
- [EraYaN](https://github.com/EraYaN)
- [flemse](https://github.com/flemse)
- [bfayers](https://github.com/bfayers)
- [Bond_009](https://github.com/Bond-009)
- [AnthonyLavado](https://github.com/anthonylavado)
- [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)
- [LeoVerto](https://github.com/LeoVerto)
- [grafixeyehero](https://github.com/grafixeyehero)
- [cvium](https://github.com/cvium)
- [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)
- [TtheCreator](https://github.com/Tthecreator)
- [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)
- [fruhnow](https://github.com/fruhnow)
- [Lynxy](https://github.com/Lynxy)
- [fasheng](https://github.com/fasheng)
- [ploughpuff](https://github.com/ploughpuff)
- [pjeanjean](https://github.com/pjeanjean)
- [DrPandemic](https://github.com/drpandemic)
- [joern-h](https://github.com/joern-h)
- [Khinenw](https://github.com/HelloWorld017)
- [fhriley](https://github.com/fhriley)
- [nevado](https://github.com/nevado)
# Emby Contributors
@@ -204,4 +93,3 @@
- [tikuf](https://github.com/tikuf/)
- [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [olsh](https://github.com/olsh)

View File

@@ -1,81 +1,42 @@
ARG DOTNET_VERSION=5.0
ARG DOTNET_VERSION=2.2
ARG FFMPEG_VERSION=latest
FROM node: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 python \
ARG JELLYFIN_WEB_VERSION=v10.4.1
RUN apk add curl \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& yarn install \
&& yarn build \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-buster-slim as builder
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
WORKDIR /repo
COPY . .
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# 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"
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
FROM debian:buster-slim
# 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 jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}
COPY --from=ffmpeg / /
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
# https://github.com/intel/compute-runtime/releases
ARG GMMLIB_VERSION=20.3.2
ARG IGC_VERSION=1.0.5435
ARG NEO_VERSION=20.46.18421
ARG LEVEL_ZERO_VERSION=1.0.18421
# Install dependencies:
# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
# libfontconfig1: needed for Skia
# mesa-va-drivers: needed for VAAPI
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \
&& 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 \
libfontconfig1 mesa-va-drivers \
&& apt-get clean autoclean \
&& apt-get autoremove \
&& 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
&& chmod 777 /cache /config /media
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
--datadir /config \
--cachedir /cache \
--ffmpeg /usr/local/bin/ffmpeg

View File

@@ -1,77 +1,44 @@
# DESIGNED FOR BUILDING ON AMD64 ONLY
#####################################
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=5.0
ARG DOTNET_VERSION=3.0
FROM node: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 python \
ARG JELLYFIN_WEB_VERSION=v10.4.1
RUN apk add curl \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& yarn install \
&& yarn build \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
FROM mcr.microsoft.com/dotnet/core/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 Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:DebugSymbols=false;DebugType=none"
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-arm as qemu
FROM arm32v7/debian:buster-slim
# 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 mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
curl -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 curl gnupg -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& 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
&& chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
--datadir /config \
--cachedir /cache \
--ffmpeg /usr/bin/ffmpeg

View File

@@ -1,66 +1,44 @@
# DESIGNED FOR BUILDING ON AMD64 ONLY
#####################################
# Requires binfm_misc registration
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=5.0
ARG DOTNET_VERSION=3.0
FROM node: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 python \
ARG JELLYFIN_WEB_VERSION=v10.4.1
RUN apk add curl \
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
&& cd jellyfin-web-* \
&& yarn install \
&& yarn build \
&& mv dist /dist
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
FROM mcr.microsoft.com/dotnet/core/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 Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:DebugSymbols=false;DebugType=none"
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
FROM arm64v8/debian:buster-slim
# 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 mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
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 \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
&& 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
&& chmod 777 /cache /config /media
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
EXPOSE 8096
VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
"--ffmpeg", "/usr/bin/ffmpeg"]
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
--datadir /config \
--cachedir /cache \
--ffmpeg /usr/bin/ffmpeg

2565
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,4 @@
#pragma warning disable CS1591
using System.Buffers.Binary;
using System;
using System.IO;
namespace DvdLib
@@ -14,12 +12,19 @@ namespace DvdLib
public override ushort ReadUInt16()
{
return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
}
public override uint ReadUInt32()
{
return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
}
private byte[] ReadAndReverseBytes(int count)
{
byte[] val = base.ReadBytes(count);
Array.Reverse(val, 0, count);
return val;
}
}
}

View File

@@ -1,19 +1,17 @@
<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>net5.0</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.IO;
namespace DvdLib.Ifo
@@ -7,7 +5,6 @@ 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,5 +1,3 @@
#pragma warning disable CS1591
using System.IO;
namespace DvdLib.Ifo

View File

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

View File

@@ -1,13 +1,9 @@
#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,9 +1,8 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Model.IO;
namespace DvdLib.Ifo
{
@@ -14,10 +13,13 @@ namespace DvdLib.Ifo
private ushort _titleCount;
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
public Dvd(string path)
private readonly IFileSystem _fileSystem;
public Dvd(string path, IFileSystem fileSystem)
{
_fileSystem = fileSystem;
Titles = new List<Title>();
var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories);
var allFiles = _fileSystem.GetFiles(path, true).ToList();
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));
@@ -31,7 +33,7 @@ namespace DvdLib.Ifo
continue;
}
var nums = ifo.Name.Split('_', StringSplitOptions.RemoveEmptyEntries);
var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries);
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
{
ReadVTS(ifoNumber, ifo.FullName);
@@ -40,7 +42,7 @@ namespace DvdLib.Ifo
}
else
{
using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var vmgFs = _fileSystem.GetFileStream(vmgPath.FullName, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
{
using (var vmgRead = new BigEndianBinaryReader(vmgFs))
{
@@ -74,7 +76,7 @@ namespace DvdLib.Ifo
}
}
private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles)
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
{
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
@@ -93,7 +95,7 @@ namespace DvdLib.Ifo
{
VTSPaths[vtsNum] = vtsPath;
using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var vtsFs = _fileSystem.GetFileStream(vtsPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
{
using (var vtsRead = new BigEndianBinaryReader(vtsFs))
{
@@ -117,19 +119,12 @@ 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));
@@ -154,10 +149,7 @@ 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,5 +1,3 @@
#pragma warning disable CS1591
using System;
namespace DvdLib.Ifo
@@ -15,14 +13,8 @@ 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,12 +1,10 @@
#pragma warning disable CS1591
using System.Collections.Generic;
namespace DvdLib.Ifo
{
public class Program
{
public IReadOnlyList<Cell> Cells { get; }
public readonly List<Cell> Cells;
public Program(List<Cell> cells)
{

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -22,9 +20,7 @@ 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
@@ -35,11 +31,9 @@ namespace DvdLib.Ifo
private ushort _goupProgramNumber;
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;
@@ -75,15 +69,8 @@ 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,5 +1,3 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using System.IO;
@@ -8,11 +6,8 @@ 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;
@@ -20,7 +15,6 @@ 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;
@@ -59,10 +53,7 @@ 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,5 +1,3 @@
#pragma warning disable CS1591
using System;
namespace DvdLib.Ifo

View File

@@ -0,0 +1,306 @@
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, 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();
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
public object Get(GetMediaReceiverRegistrar request)
{
var xml = MediaReceiverRegistrar.GetServiceXml();
return _resultFactory.GetResult(Request, xml, XMLContentType);
}
public object Get(GetConnnectionManager request)
{
var xml = ConnectionManager.GetServiceXml();
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,
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

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,17 @@
#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
{
/// <summary>
/// Initializes a new instance of the <see cref="DlnaOptions"/> class.
/// </summary>
public bool EnablePlayTo { get; set; }
public bool EnableServer { get; set; }
public bool EnableDebugLog { get; set; }
public bool BlastAliveMessages { get; set; }
public bool SendOnlyMatchedHost { get; set; }
public int ClientDiscoveryIntervalSeconds { get; set; }
public int BlastAliveMessageIntervalSeconds { get; set; }
public string DefaultUserId { get; set; }
public DlnaOptions()
{
EnablePlayTo = true;
@@ -17,76 +19,7 @@ namespace Emby.Dlna.Configuration
BlastAliveMessages = true;
SendOnlyMatchedHost = true;
ClientDiscoveryIntervalSeconds = 60;
AliveMessageIntervalSeconds = 1800;
BlastAliveMessageIntervalSeconds = 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,6 +1,4 @@
#nullable enable
#pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Dlna.Configuration;
using MediaBrowser.Common.Configuration;
@@ -13,4 +11,19 @@ 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

@@ -0,0 +1,36 @@
using Emby.Dlna.Service;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
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;
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
: base(logger, httpClient)
{
_dlna = dlna;
_config = config;
_logger = logger;
}
public string GetServiceXml()
{
return new ConnectionManagerXmlBuilder().GetXml();
}
public ControlResponse ProcessControlRequest(ControlRequest request)
{
var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile();
return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request);
}
}
}

View File

@@ -1,53 +0,0 @@
#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,62 +1,48 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
namespace Emby.Dlna.ConnectionManager
{
/// <summary>
/// Defines the <see cref="ConnectionManagerXmlBuilder" />.
/// </summary>
public static class ConnectionManagerXmlBuilder
public class ConnectionManagerXmlBuilder
{
/// <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()
public string GetXml()
{
return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables());
return new ServiceXmlBuilder().GetXml(new 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>
var list = new List<StateVariable>();
list.Add(new StateVariable
{
new StateVariable
{
Name = "SourceProtocolInfo",
DataType = "string",
SendsEvents = true
},
Name = "SourceProtocolInfo",
DataType = "string",
SendsEvents = true
});
new StateVariable
{
Name = "SinkProtocolInfo",
DataType = "string",
SendsEvents = true
},
list.Add(new StateVariable
{
Name = "SinkProtocolInfo",
DataType = "string",
SendsEvents = true
});
new StateVariable
{
Name = "CurrentConnectionIDs",
DataType = "string",
SendsEvents = true
},
list.Add(new StateVariable
{
Name = "CurrentConnectionIDs",
DataType = "string",
SendsEvents = true
});
new StateVariable
{
Name = "A_ARG_TYPE_ConnectionStatus",
DataType = "string",
SendsEvents = false,
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_ConnectionStatus",
DataType = "string",
SendsEvents = false,
AllowedValues = new[]
AllowedValues = new string[]
{
"OK",
"ContentFormatMismatch",
@@ -64,56 +50,55 @@ namespace Emby.Dlna.ConnectionManager
"UnreliableChannel",
"Unknown"
}
},
});
new StateVariable
{
Name = "A_ARG_TYPE_ConnectionManager",
DataType = "string",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_ConnectionManager",
DataType = "string",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_Direction",
DataType = "string",
SendsEvents = false,
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_Direction",
DataType = "string",
SendsEvents = false,
AllowedValues = new[]
AllowedValues = new string[]
{
"Output",
"Input"
}
},
});
new StateVariable
{
Name = "A_ARG_TYPE_ProtocolInfo",
DataType = "string",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_ProtocolInfo",
DataType = "string",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_ConnectionID",
DataType = "ui4",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_ConnectionID",
DataType = "ui4",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_AVTransportID",
DataType = "ui4",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_AVTransportID",
DataType = "ui4",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_RcsID",
DataType = "ui4",
SendsEvents = false
}
};
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_RcsID",
DataType = "ui4",
SendsEvents = false
});
return list;
}

View File

@@ -1,8 +1,5 @@
#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;
@@ -11,45 +8,33 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ConnectionManager
{
/// <summary>
/// Defines the <see cref="ControlHandler" />.
/// </summary>
public class ControlHandler : BaseControlHandler
{
private readonly DeviceProfile _profile;
/// <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, IDictionary<string, string> methodParams, XmlWriter xmlWriter)
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
{
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
{
HandleGetProtocolInfo(xmlWriter);
return;
return HandleGetProtocolInfo();
}
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
}
/// <summary>
/// Builds the response to the GetProtocolInfo request.
/// </summary>
/// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
private void HandleGetProtocolInfo(XmlWriter xmlWriter)
private IEnumerable<KeyValuePair<string, string>> HandleGetProtocolInfo()
{
xmlWriter.WriteElementString("Source", _profile.ProtocolInfo);
xmlWriter.WriteElementString("Sink", string.Empty);
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "Source", _profile.ProtocolInfo },
{ "Sink", "" }
};
}
public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
: base(config, logger)
{
_profile = profile;
}
}
}

View File

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

View File

@@ -1,15 +1,11 @@
#pragma warning disable CS1591
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Collections.Generic;
using Emby.Dlna.Service;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
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;
@@ -19,10 +15,7 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.ContentDirectory
{
/// <summary>
/// Defines the <see cref="ContentDirectoryService" />.
/// </summary>
public class ContentDirectoryService : BaseService, IContentDirectory
public class ContentDirectory : BaseService, IContentDirectory
{
private readonly ILibraryManager _libraryManager;
private readonly IImageProcessor _imageProcessor;
@@ -36,31 +29,14 @@ namespace Emby.Dlna.ContentDirectory
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,
public ContentDirectory(IDlnaManager dlna,
IUserDataManager userDataManager,
IImageProcessor imageProcessor,
ILibraryManager libraryManager,
IServerConfigurationManager config,
IUserManager userManager,
ILogger<ContentDirectoryService> logger,
IHttpClientFactory httpClient,
ILogger logger,
IHttpClient httpClient,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
IUserViewManager userViewManager,
@@ -81,10 +57,7 @@ namespace Emby.Dlna.ContentDirectory
_tvSeriesManager = tvSeriesManager;
}
/// <summary>
/// Gets the system id. (A unique id which changes on when our definition changes.)
/// </summary>
private static int SystemUpdateId
private int SystemUpdateId
{
get
{
@@ -94,21 +67,15 @@ namespace Emby.Dlna.ContentDirectory
}
}
/// <inheritdoc />
public string GetServiceXml()
{
return ContentDirectoryXmlBuilder.GetXml();
return new ContentDirectoryXmlBuilder().GetXml();
}
/// <inheritdoc />
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
public ControlResponse ProcessControlRequest(ControlRequest request)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile();
var profile = _dlna.GetProfile(request.Headers) ??
_dlna.GetDefaultProfile();
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
@@ -130,19 +97,14 @@ namespace Emby.Dlna.ContentDirectory
_userViewManager,
_mediaEncoder,
_tvSeriesManager)
.ProcessControlRequestAsync(request);
.ProcessControlRequest(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));
var user = _userManager.GetUserById(profile.UserId);
if (user != null)
{
@@ -154,7 +116,7 @@ namespace Emby.Dlna.ContentDirectory
if (!string.IsNullOrEmpty(userId))
{
var user = _userManager.GetUserById(Guid.Parse(userId));
var user = _userManager.GetUserById(userId);
if (user != null)
{
@@ -164,13 +126,18 @@ namespace Emby.Dlna.ContentDirectory
foreach (var user in _userManager.Users)
{
if (user.HasPermission(PermissionKind.IsAdministrator))
if (user.Policy.IsAdministrator)
{
return user;
}
}
return _userManager.Users.FirstOrDefault();
foreach (var user in _userManager.Users)
{
return user;
}
return null;
}
}
}

View File

@@ -1,159 +1,145 @@
#pragma warning disable CS1591
using System.Collections.Generic;
using Emby.Dlna.Common;
using Emby.Dlna.Service;
namespace Emby.Dlna.ContentDirectory
{
/// <summary>
/// Defines the <see cref="ContentDirectoryXmlBuilder" />.
/// </summary>
public static class ContentDirectoryXmlBuilder
public class ContentDirectoryXmlBuilder
{
/// <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()
public string GetXml()
{
return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables());
return new ServiceXmlBuilder().GetXml(new 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>
var list = new List<StateVariable>();
list.Add(new StateVariable
{
new StateVariable
{
Name = "A_ARG_TYPE_Filter",
DataType = "string",
SendsEvents = false
},
Name = "A_ARG_TYPE_Filter",
DataType = "string",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_SortCriteria",
DataType = "string",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_SortCriteria",
DataType = "string",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_Index",
DataType = "ui4",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_Index",
DataType = "ui4",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_Count",
DataType = "ui4",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_Count",
DataType = "ui4",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_UpdateID",
DataType = "ui4",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_UpdateID",
DataType = "ui4",
SendsEvents = false
});
new StateVariable
{
Name = "SearchCapabilities",
DataType = "string",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "SearchCapabilities",
DataType = "string",
SendsEvents = false
});
new StateVariable
{
Name = "SortCapabilities",
DataType = "string",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "SortCapabilities",
DataType = "string",
SendsEvents = false
});
new StateVariable
{
Name = "SystemUpdateID",
DataType = "ui4",
SendsEvents = true
},
list.Add(new StateVariable
{
Name = "SystemUpdateID",
DataType = "ui4",
SendsEvents = true
});
new StateVariable
{
Name = "A_ARG_TYPE_SearchCriteria",
DataType = "string",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_SearchCriteria",
DataType = "string",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_Result",
DataType = "string",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_Result",
DataType = "string",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_ObjectID",
DataType = "string",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_ObjectID",
DataType = "string",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_BrowseFlag",
DataType = "string",
SendsEvents = false,
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_BrowseFlag",
DataType = "string",
SendsEvents = false,
AllowedValues = new[]
AllowedValues = new string[]
{
"BrowseMetadata",
"BrowseDirectChildren"
}
},
});
new StateVariable
{
Name = "A_ARG_TYPE_BrowseLetter",
DataType = "string",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_BrowseLetter",
DataType = "string",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_CategoryType",
DataType = "ui4",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_CategoryType",
DataType = "ui4",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_RID",
DataType = "ui4",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_RID",
DataType = "ui4",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_PosSec",
DataType = "ui4",
SendsEvents = false
},
list.Add(new StateVariable
{
Name = "A_ARG_TYPE_PosSec",
DataType = "ui4",
SendsEvents = false
});
new StateVariable
{
Name = "A_ARG_TYPE_Featurelist",
DataType = "string",
SendsEvents = false
}
};
list.Add(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

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

View File

@@ -1,31 +0,0 @@
#pragma warning disable CS1591
#pragma warning disable SA1602
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,5 +1,3 @@
#pragma warning disable CS1591
using System.IO;
using Microsoft.AspNetCore.Http;
@@ -7,17 +5,17 @@ namespace Emby.Dlna
{
public class ControlRequest
{
public ControlRequest(IHeaderDictionary headers)
{
Headers = headers;
}
public IHeaderDictionary Headers { get; }
public IHeaderDictionary Headers { get; set; }
public Stream InputXml { get; set; }
public string TargetServerUuId { get; set; }
public string RequestedUrl { get; set; }
public ControlRequest()
{
Headers = new HeaderDictionary();
}
}
}

View File

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

View File

@@ -1,46 +1,39 @@
#pragma warning disable CS1591
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Emby.Dlna.Configuration;
using Emby.Dlna.ContentDirectory;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using Genre = MediaBrowser.Controller.Entities.Genre;
using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
using XmlAttribute = MediaBrowser.Model.Dlna.XmlAttribute;
namespace Emby.Dlna.Didl
{
public class DidlBuilder
{
private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NsDc = "http://purl.org/dc/elements/1.1/";
private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/";
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
private const string NS_DC = "http://purl.org/dc/elements/1.1/";
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
private const string NS_DLNA = "urn:schemas-dlna-org:metadata-1-0/";
private readonly DeviceProfile _profile;
private readonly IImageProcessor _imageProcessor;
private readonly string _serverAddress;
@@ -51,7 +44,6 @@ namespace Emby.Dlna.Didl
private readonly IMediaSourceManager _mediaSourceManager;
private readonly ILogger _logger;
private readonly IMediaEncoder _mediaEncoder;
private readonly ILibraryManager _libraryManager;
public DidlBuilder(
DeviceProfile profile,
@@ -63,8 +55,7 @@ namespace Emby.Dlna.Didl
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
ILogger logger,
IMediaEncoder mediaEncoder,
ILibraryManager libraryManager)
IMediaEncoder mediaEncoder)
{
_profile = profile;
_user = user;
@@ -76,7 +67,6 @@ namespace Emby.Dlna.Didl
_mediaSourceManager = mediaSourceManager;
_logger = logger;
_mediaEncoder = mediaEncoder;
_libraryManager = libraryManager;
}
public static string NormalizeDlnaMediaUrl(string url)
@@ -84,7 +74,7 @@ namespace Emby.Dlna.Didl
return url + "&dlnaheaders=true";
}
public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
public string GetItemDidl(DlnaOptions options, BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
{
var settings = new XmlWriterSettings
{
@@ -98,21 +88,21 @@ namespace Emby.Dlna.Didl
{
using (var writer = XmlWriter.Create(builder, settings))
{
// writer.WriteStartDocument();
//writer.WriteStartDocument();
writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
writer.WriteAttributeString("xmlns", "dc", null, NsDc);
writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
// didl.SetAttribute("xmlns:sec", NS_SEC);
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
//didl.SetAttribute("xmlns:sec", NS_SEC);
WriteXmlRootAttributes(_profile, writer);
WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
WriteItemElement(options, writer, item, user, context, null, deviceId, filter, streamInfo);
writer.WriteFullEndElement();
// writer.WriteEndDocument();
//writer.WriteEndDocument();
}
return builder.ToString();
@@ -123,7 +113,7 @@ namespace Emby.Dlna.Didl
{
foreach (var att in profile.XmlRootAttributes)
{
var parts = att.Name.Split(':', StringSplitOptions.RemoveEmptyEntries);
var parts = att.Name.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 2)
{
writer.WriteAttributeString(parts[0], parts[1], null, att.Value);
@@ -136,6 +126,7 @@ namespace Emby.Dlna.Didl
}
public void WriteItemElement(
DlnaOptions options,
XmlWriter writer,
BaseItem item,
User user,
@@ -147,7 +138,7 @@ namespace Emby.Dlna.Didl
{
var clientId = GetClientId(item, null);
writer.WriteStartElement(string.Empty, "item", NsDidl);
writer.WriteStartElement(string.Empty, "item", NS_DIDL);
writer.WriteAttributeString("restricted", "1");
writer.WriteAttributeString("id", clientId);
@@ -172,23 +163,25 @@ namespace Emby.Dlna.Didl
// refID?
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
if (item is IHasMediaSources)
var hasMediaSources = item as IHasMediaSources;
if (hasMediaSources != null)
{
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
AddAudioResource(writer, item, deviceId, filter, streamInfo);
AddAudioResource(options, writer, item, deviceId, filter, streamInfo);
}
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
{
AddVideoResource(writer, item, deviceId, filter, streamInfo);
AddVideoResource(options, writer, item, deviceId, filter, streamInfo);
}
}
AddCover(item, null, writer);
AddCover(item, context, null, writer);
writer.WriteFullEndElement();
}
private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
if (streamInfo == null)
{
@@ -207,8 +200,7 @@ namespace Emby.Dlna.Didl
var targetWidth = streamInfo.TargetWidth;
var targetHeight = streamInfo.TargetHeight;
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(
streamInfo.Container,
var contentFeatureList = new ContentFeatureBuilder(_profile).BuildVideoHeader(streamInfo.Container,
streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioCodec.FirstOrDefault(),
targetWidth,
@@ -233,7 +225,7 @@ namespace Emby.Dlna.Didl
foreach (var contentFeature in contentFeatureList)
{
AddVideoResource(writer, filter, contentFeature, streamInfo);
AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
}
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
@@ -280,7 +272,7 @@ namespace Emby.Dlna.Didl
}
else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase))
{
writer.WriteStartElement(string.Empty, "res", NsDidl);
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
writer.WriteAttributeString("protocolInfo", "http-get:*:smi/caption:*");
@@ -289,11 +281,8 @@ namespace Emby.Dlna.Didl
}
else
{
writer.WriteStartElement(string.Empty, "res", NsDidl);
var protocolInfo = string.Format(
CultureInfo.InvariantCulture,
"http-get:*:text/{0}:*",
info.Format.ToLowerInvariant());
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant());
writer.WriteAttributeString("protocolInfo", protocolInfo);
writer.WriteString(info.Url);
@@ -303,9 +292,9 @@ namespace Emby.Dlna.Didl
return true;
}
private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo)
private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
{
writer.WriteStartElement(string.Empty, "res", NsDidl);
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
var url = NormalizeDlnaMediaUrl(streamInfo.ToUrl(_serverAddress, _accessToken));
@@ -345,13 +334,7 @@ namespace Emby.Dlna.Didl
{
if (targetWidth.HasValue && targetHeight.HasValue)
{
writer.WriteAttributeString(
"resolution",
string.Format(
CultureInfo.InvariantCulture,
"{0}x{1}",
targetWidth.Value,
targetHeight.Value));
writer.WriteAttributeString("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
}
}
@@ -365,8 +348,7 @@ namespace Emby.Dlna.Didl
writer.WriteAttributeString("bitrate", totalBitrate.Value.ToString(_usCulture));
}
var mediaProfile = _profile.GetVideoMediaProfile(
streamInfo.Container,
var mediaProfile = _profile.GetVideoMediaProfile(streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
streamInfo.TargetVideoCodec.FirstOrDefault(),
streamInfo.TargetAudioBitrate,
@@ -386,19 +368,17 @@ namespace Emby.Dlna.Didl
streamInfo.TargetVideoCodecTag,
streamInfo.IsTargetAVC);
var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
var filename = url.Substring(0, url.IndexOf('?'));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
writer.WriteAttributeString(
"protocolInfo",
string.Format(
CultureInfo.InvariantCulture,
"http-get:*:{0}:{1}",
mimeType,
contentFeatures));
writer.WriteAttributeString("protocolInfo", string.Format(
"http-get:*:{0}:{1}",
mimeType,
contentFeatures
));
writer.WriteString(url);
@@ -411,123 +391,56 @@ namespace Emby.Dlna.Didl
{
switch (itemStubType.Value)
{
case StubType.Latest: return _localization.GetLocalizedString("Latest");
case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
case StubType.Albums: return _localization.GetLocalizedString("Albums");
case StubType.Artists: return _localization.GetLocalizedString("Artists");
case StubType.Songs: return _localization.GetLocalizedString("Songs");
case StubType.Genres: return _localization.GetLocalizedString("Genres");
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
case StubType.Latest: return _localization.GetLocalizedString("Latest");
case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
case StubType.Albums: return _localization.GetLocalizedString("Albums");
case StubType.Artists: return _localization.GetLocalizedString("Artists");
case StubType.Songs: return _localization.GetLocalizedString("Songs");
case StubType.Genres: return _localization.GetLocalizedString("Genres");
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
case StubType.Movies: return _localization.GetLocalizedString("Movies");
case StubType.Collections: return _localization.GetLocalizedString("Collections");
case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
case StubType.Movies: return _localization.GetLocalizedString("Movies");
case StubType.Collections: return _localization.GetLocalizedString("Collections");
case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
case StubType.Series: return _localization.GetLocalizedString("Shows");
case StubType.Series: return _localization.GetLocalizedString("Shows");
default: break;
}
}
return item is Episode episode
? GetEpisodeDisplayName(episode, context)
: item.Name;
}
/// <summary>
/// Gets episode display name appropriate for the given context.
/// </summary>
/// <remarks>
/// If context is a season, this will return a string containing just episode number and name.
/// Otherwise the result will include series nams and season number.
/// </remarks>
/// <param name="episode">The episode.</param>
/// <param name="context">Current context.</param>
/// <returns>Formatted name of the episode.</returns>
private string GetEpisodeDisplayName(Episode episode, BaseItem context)
{
string[] components;
if (context is Season season)
if (item is Episode episode && context is Season season)
{
// This is a special embedded within a season
if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
{
return string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ValueSpecialEpisodeName"),
episode.Name);
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
}
// inside a season use simple format (ex. '12 - Episode Name')
var epNumberName = GetEpisodeIndexFullName(episode);
components = new[] { epNumberName, episode.Name };
}
else
{
// outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name')
var epNumberName = GetEpisodeNumberDisplayName(episode);
components = new[] { episode.SeriesName, epNumberName, episode.Name };
}
return string.Join(" - ", components.Where(NotNullOrWhiteSpace));
}
/// <summary>
/// Gets complete episode number.
/// </summary>
/// <param name="episode">The episode.</param>
/// <returns>For single episodes returns just the number. For double episodes - current and ending numbers.</returns>
private string GetEpisodeIndexFullName(Episode episode)
{
var name = string.Empty;
if (episode.IndexNumber.HasValue)
{
name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
if (episode.IndexNumberEnd.HasValue)
if (item.IndexNumber.HasValue)
{
name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
var number = item.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
if (episode.IndexNumberEnd.HasValue)
{
number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
}
return number + " - " + item.Name;
}
}
return name;
return item.Name;
}
/// <summary>
/// Gets episode number formatted as 'S##E##'.
/// </summary>
/// <param name="episode">The episode.</param>
/// <returns>Formatted episode number.</returns>
private string GetEpisodeNumberDisplayName(Episode episode)
private void AddAudioResource(DlnaOptions options, XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
var name = string.Empty;
var seasonNumber = episode.Season?.IndexNumber;
if (seasonNumber.HasValue)
{
name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
}
var indexName = GetEpisodeIndexFullName(episode);
if (!string.IsNullOrWhiteSpace(indexName))
{
name += "E" + indexName;
}
return name;
}
private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
writer.WriteStartElement(string.Empty, "res", NsDidl);
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
if (streamInfo == null)
{
@@ -584,22 +497,20 @@ namespace Emby.Dlna.Didl
writer.WriteAttributeString("bitrate", targetAudioBitrate.Value.ToString(_usCulture));
}
var mediaProfile = _profile.GetAudioMediaProfile(
streamInfo.Container,
var mediaProfile = _profile.GetAudioMediaProfile(streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
targetChannels,
targetAudioBitrate,
targetSampleRate,
targetAudioBitDepth);
var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
var filename = url.Substring(0, url.IndexOf('?'));
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
? MimeTypes.GetMimeType(filename)
: mediaProfile.MimeType;
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(
streamInfo.Container,
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
streamInfo.TargetAudioCodec.FirstOrDefault(),
targetAudioBitrate,
targetSampleRate,
@@ -609,13 +520,11 @@ namespace Emby.Dlna.Didl
streamInfo.RunTimeTicks ?? 0,
streamInfo.TranscodeSeekInfo);
writer.WriteAttributeString(
"protocolInfo",
string.Format(
CultureInfo.InvariantCulture,
"http-get:*:{0}:{1}",
mimeType,
contentFeatures));
writer.WriteAttributeString("protocolInfo", string.Format(
"http-get:*:{0}:{1}",
mimeType,
contentFeatures
));
writer.WriteString(url);
@@ -630,7 +539,7 @@ namespace Emby.Dlna.Didl
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
{
writer.WriteStartElement(string.Empty, "container", NsDidl);
writer.WriteStartElement(string.Empty, "container", NS_DIDL);
writer.WriteAttributeString("restricted", "1");
writer.WriteAttributeString("searchable", "1");
@@ -638,7 +547,7 @@ namespace Emby.Dlna.Didl
var clientId = GetClientId(folder, stubType);
if (string.Equals(requestedId, "0", StringComparison.Ordinal))
if (string.Equals(requestedId, "0"))
{
writer.WriteAttributeString("id", "0");
writer.WriteAttributeString("parentID", "-1");
@@ -667,7 +576,7 @@ namespace Emby.Dlna.Didl
AddGeneralProperties(folder, stubType, context, writer, filter);
AddCover(folder, stubType, writer);
AddCover(folder, context, stubType, writer);
writer.WriteFullEndElement();
}
@@ -679,7 +588,7 @@ namespace Emby.Dlna.Didl
return;
}
XmlAttribute secAttribute = null;
MediaBrowser.Model.Dlna.XmlAttribute secAttribute = null;
foreach (var attribute in _profile.XmlRootAttributes)
{
if (string.Equals(attribute.Name, "xmlns:sec", StringComparison.OrdinalIgnoreCase))
@@ -700,24 +609,21 @@ namespace Emby.Dlna.Didl
if (playbackPositionTicks > 0)
{
var elementValue = string.Format(
CultureInfo.InvariantCulture,
"BM={0}",
Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds));
var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds).ToString(_usCulture));
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
}
}
/// <summary>
/// Adds fields used by both items and folders.
/// Adds fields used by both items and folders
/// </summary>
private void AddCommonFields(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
{
// Don't filter on dc:title because not all devices will include it in the filter
// MediaMonkey for example won't display content without a title
// if (filter.Contains("dc:title"))
//if (filter.Contains("dc:title"))
{
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NsDc);
AddValue(writer, "dc", "title", GetDisplayName(item, itemStubType, context), NS_DC);
}
WriteObjectClass(writer, item, itemStubType);
@@ -726,7 +632,7 @@ namespace Emby.Dlna.Didl
{
if (item.PremiereDate.HasValue)
{
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NsDc);
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
}
}
@@ -734,13 +640,13 @@ namespace Emby.Dlna.Didl
{
foreach (var genre in item.Genres)
{
AddValue(writer, "upnp", "genre", genre, NsUpnp);
AddValue(writer, "upnp", "genre", genre, NS_UPNP);
}
}
foreach (var studio in item.Studios)
{
AddValue(writer, "upnp", "publisher", studio, NsUpnp);
AddValue(writer, "upnp", "publisher", studio, NS_UPNP);
}
if (!(item is Folder))
@@ -751,29 +657,27 @@ namespace Emby.Dlna.Didl
if (!string.IsNullOrWhiteSpace(desc))
{
AddValue(writer, "dc", "description", desc, NsDc);
AddValue(writer, "dc", "description", desc, NS_DC);
}
}
// if (filter.Contains("upnp:longDescription"))
// {
//if (filter.Contains("upnp:longDescription"))
//{
// if (!string.IsNullOrWhiteSpace(item.Overview))
// {
// AddValue(writer, "upnp", "longDescription", item.Overview, NsUpnp);
// AddValue(writer, "upnp", "longDescription", item.Overview, NS_UPNP);
// }
// }
//}
}
if (!string.IsNullOrEmpty(item.OfficialRating))
{
if (filter.Contains("dc:rating"))
{
AddValue(writer, "dc", "rating", item.OfficialRating, NsDc);
AddValue(writer, "dc", "rating", item.OfficialRating, NS_DC);
}
if (filter.Contains("upnp:rating"))
{
AddValue(writer, "upnp", "rating", item.OfficialRating, NsUpnp);
AddValue(writer, "upnp", "rating", item.OfficialRating, NS_UPNP);
}
}
@@ -785,7 +689,7 @@ namespace Emby.Dlna.Didl
// More types here
// http://oss.linn.co.uk/repos/Public/LibUpnpCil/DidlLite/UpnpAv/Test/TestDidlLite.cs
writer.WriteStartElement("upnp", "class", NsUpnp);
writer.WriteStartElement("upnp", "class", NS_UPNP);
if (item.IsDisplayedAsFolder || stubType.HasValue)
{
@@ -858,36 +762,37 @@ namespace Emby.Dlna.Didl
private void AddPeople(BaseItem item, XmlWriter writer)
{
if (!item.SupportsPeople)
{
return;
}
//var types = new[]
//{
// PersonType.Director,
// PersonType.Writer,
// PersonType.Producer,
// PersonType.Composer,
// "Creator"
//};
var types = new[]
{
PersonType.Director,
PersonType.Writer,
PersonType.Producer,
PersonType.Composer,
"creator"
};
//var people = _libraryManager.GetPeople(item);
// Seeing some LG models locking up due content with large lists of people
// The actual issue might just be due to processing a more metadata than it can handle
var people = _libraryManager.GetPeople(
new InternalPeopleQuery
{
ItemId = item.Id,
Limit = 6
});
//var index = 0;
foreach (var actor in people)
{
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
?? PersonType.Actor;
//// Seeing some LG models locking up due content with large lists of people
//// The actual issue might just be due to processing a more metadata than it can handle
//var limit = 6;
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NsUpnp);
}
//foreach (var actor in people)
//{
// var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
// ?? PersonType.Actor;
// AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
// index++;
// if (index >= limit)
// {
// break;
// }
//}
}
private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
@@ -900,8 +805,8 @@ namespace Emby.Dlna.Didl
{
foreach (var artist in hasArtists.Artists)
{
AddValue(writer, "upnp", "artist", artist, NsUpnp);
AddValue(writer, "dc", "creator", artist, NsDc);
AddValue(writer, "upnp", "artist", artist, NS_UPNP);
AddValue(writer, "dc", "creator", artist, NS_DC);
// If it doesn't support album artists (musicvideo), then tag as both
if (hasAlbumArtists == null)
@@ -921,16 +826,16 @@ namespace Emby.Dlna.Didl
if (!string.IsNullOrWhiteSpace(item.Album))
{
AddValue(writer, "upnp", "album", item.Album, NsUpnp);
AddValue(writer, "upnp", "album", item.Album, NS_UPNP);
}
if (item.IndexNumber.HasValue)
{
AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp);
AddValue(writer, "upnp", "originalTrackNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
if (item is Episode)
{
AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NsUpnp);
AddValue(writer, "upnp", "episodeNumber", item.IndexNumber.Value.ToString(_usCulture), NS_UPNP);
}
}
}
@@ -939,7 +844,7 @@ namespace Emby.Dlna.Didl
{
try
{
writer.WriteStartElement("upnp", "artist", NsUpnp);
writer.WriteStartElement("upnp", "artist", NS_UPNP);
writer.WriteAttributeString("role", "AlbumArtist");
writer.WriteString(name);
@@ -948,7 +853,7 @@ namespace Emby.Dlna.Didl
}
catch (XmlException ex)
{
_logger.LogError(ex, "Error adding xml value: {Value}", name);
_logger.LogError(ex, "Error adding xml value: {value}", name);
}
}
@@ -960,11 +865,11 @@ namespace Emby.Dlna.Didl
}
catch (XmlException ex)
{
_logger.LogError(ex, "Error adding xml value: {Value}", value);
_logger.LogError(ex, "Error adding xml value: {value}", value);
}
}
private void AddCover(BaseItem item, StubType? stubType, XmlWriter writer)
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
{
ImageDownloadInfo imageInfo = GetImageInfo(item);
@@ -975,14 +880,14 @@ namespace Emby.Dlna.Didl
var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg");
writer.WriteStartElement("upnp", "albumArtURI", NsUpnp);
writer.WriteAttributeString("dlna", "profileID", NsDlna, _profile.AlbumArtPn);
writer.WriteString(albumartUrlInfo.url);
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
writer.WriteString(albumartUrlInfo.Url);
writer.WriteFullEndElement();
// TOOD: Remove these default values
var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg");
writer.WriteElementString("upnp", "icon", NsUpnp, iconUrlInfo.url);
writer.WriteElementString("upnp", "icon", NS_UPNP, iconUrlInfo.Url);
if (!_profile.EnableAlbumArtInDidl)
{
@@ -1006,10 +911,20 @@ namespace Emby.Dlna.Didl
}
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
}
private void AddImageResElement(
BaseItem item,
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
{
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
writer.WriteString(_serverAddress + "/Dlna/icons/people480.jpg");
writer.WriteFullEndElement();
writer.WriteElementString("upnp", "icon", NS_UPNP, _serverAddress + "/Dlna/icons/people48.jpg");
}
private void AddImageResElement(BaseItem item,
XmlWriter writer,
int maxWidth,
int maxHeight,
@@ -1025,29 +940,25 @@ namespace Emby.Dlna.Didl
var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format);
writer.WriteStartElement(string.Empty, "res", NsDidl);
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
// Images must have a reported size or many clients (Bubble upnp), will only use the first thumbnail
// rather than using a larger one when available
var width = albumartUrlInfo.width ?? maxWidth;
var height = albumartUrlInfo.height ?? maxHeight;
var width = albumartUrlInfo.Width ?? maxWidth;
var height = albumartUrlInfo.Height ?? maxHeight;
var contentFeatures = new ContentFeatureBuilder(_profile)
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
writer.WriteAttributeString(
"protocolInfo",
string.Format(
CultureInfo.InvariantCulture,
"http-get:*:{0}:{1}",
MimeTypes.GetMimeType("file." + format),
contentFeatures));
writer.WriteAttributeString("protocolInfo", string.Format(
"http-get:*:{0}:{1}",
MimeTypes.GetMimeType("file." + format),
contentFeatures
));
writer.WriteAttributeString(
"resolution",
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
writer.WriteAttributeString("resolution", string.Format("{0}x{1}", width, height));
writer.WriteString(albumartUrlInfo.url);
writer.WriteString(albumartUrlInfo.Url);
writer.WriteFullEndElement();
}
@@ -1058,12 +969,10 @@ namespace Emby.Dlna.Didl
{
return GetImageInfo(item, ImageType.Primary);
}
if (item.HasImage(ImageType.Thumb))
{
return GetImageInfo(item, ImageType.Thumb);
}
if (item.HasImage(ImageType.Backdrop))
{
if (item is Channel)
@@ -1072,58 +981,19 @@ namespace Emby.Dlna.Didl
}
}
// For audio tracks without art use album art if available.
if (item is Audio audioItem)
{
var album = audioItem.AlbumEntity;
return album != null && album.HasImage(ImageType.Primary)
? GetImageInfo(album, ImageType.Primary)
: null;
}
item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
// Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder.
if (item is MusicAlbum || item is Playlist)
if (item != null)
{
return null;
}
// For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item.
var parentWithImage = GetFirstParentWithImageBelowUserRoot(item);
if (parentWithImage != null)
{
return GetImageInfo(parentWithImage, ImageType.Primary);
if (item.HasImage(ImageType.Primary))
{
return GetImageInfo(item, ImageType.Primary);
}
}
return null;
}
private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
{
if (item == null)
{
return null;
}
if (item.HasImage(ImageType.Primary))
{
return item;
}
var parent = item.GetParent();
if (parent is UserRootFolder)
{
return null;
}
// terminate in case we went past user root folder (unlikely?)
if (parent is Folder folder && folder.IsRoot)
{
return null;
}
return GetFirstParentWithImageBelowUserRoot(parent);
}
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
{
var imageInfo = item.GetImageInfo(type, 0);
@@ -1143,15 +1013,29 @@ namespace Emby.Dlna.Didl
if (width == 0 || height == 0)
{
//_imageProcessor.GetImageSize(item, imageInfo);
width = null;
height = null;
}
else if (width == -1 || height == -1)
{
width = null;
height = null;
}
//try
//{
// var size = _imageProcessor.GetImageSize(imageInfo);
// width = size.Width;
// height = size.Height;
//}
//catch
//{
//}
var inputFormat = (Path.GetExtension(imageInfo.Path) ?? string.Empty)
.TrimStart('.')
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
@@ -1168,6 +1052,30 @@ namespace Emby.Dlna.Didl
};
}
private class ImageDownloadInfo
{
internal Guid ItemId;
internal string ImageTag;
internal ImageType Type;
internal int? Width;
internal int? Height;
internal bool IsDirectStream;
internal string Format;
internal ItemImageInfo ItemImageInfo;
}
private class ImageUrlInfo
{
internal string Url;
internal int? Width;
internal int? Height;
}
public static string GetClientId(BaseItem item, StubType? stubType)
{
return GetClientId(item.Id, stubType);
@@ -1185,11 +1093,9 @@ namespace Emby.Dlna.Didl
return id;
}
private (string url, int? width, int? height) GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
{
var url = string.Format(
CultureInfo.InvariantCulture,
"{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
_serverAddress,
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
info.Type,
@@ -1223,26 +1129,12 @@ namespace Emby.Dlna.Didl
// just lie
info.IsDirectStream = true;
return (url, width, height);
}
private class ImageDownloadInfo
{
internal Guid ItemId { get; set; }
internal string ImageTag { get; set; }
internal ImageType Type { get; set; }
internal int? Width { get; set; }
internal int? Height { get; set; }
internal bool IsDirectStream { get; set; }
internal string Format { get; set; }
internal ItemImageInfo ItemImageInfo { get; set; }
return new ImageUrlInfo
{
Url = url,
Width = width,
Height = height
};
}
}
}

View File

@@ -1,6 +1,5 @@
#pragma warning disable CS1591
using System;
using MediaBrowser.Model.Extensions;
namespace Emby.Dlna.Didl
{
@@ -12,18 +11,21 @@ namespace Emby.Dlna.Didl
public Filter()
: this("*")
{
}
public Filter(string filter)
{
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
_all = StringHelper.EqualsIgnoreCase(filter, "*");
_fields = (filter ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries);
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
public bool Contains(string field)
{
return _all || Array.Exists(_fields, x => x.Equals(field, StringComparison.OrdinalIgnoreCase));
// 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);
}
}
}

View File

@@ -1,6 +1,3 @@
#pragma warning disable CS1591
#pragma warning disable CA1305
using System;
using System.IO;
using System.Text;
@@ -30,6 +27,7 @@ namespace Emby.Dlna.Didl
{
}
public StringWriterWithEncoding(Encoding encoding)
{
_encoding = encoding;
@@ -53,6 +51,6 @@ namespace Emby.Dlna.Didl
_encoding = encoding;
}
public override Encoding Encoding => _encoding ?? base.Encoding;
public override Encoding Encoding => (null == _encoding) ? base.Encoding : _encoding;
}
}

View File

@@ -1,24 +0,0 @@
#nullable enable
#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,5 +1,3 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -31,7 +29,7 @@ namespace Emby.Dlna
private readonly IApplicationPaths _appPaths;
private readonly IXmlSerializer _xmlSerializer;
private readonly IFileSystem _fileSystem;
private readonly ILogger<DlnaManager> _logger;
private readonly ILogger _logger;
private readonly IJsonSerializer _jsonSerializer;
private readonly IServerApplicationHost _appHost;
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
@@ -49,20 +47,16 @@ namespace Emby.Dlna
_xmlSerializer = xmlSerializer;
_fileSystem = fileSystem;
_appPaths = appPaths;
_logger = loggerFactory.CreateLogger<DlnaManager>();
_logger = loggerFactory.CreateLogger("Dlna");
_jsonSerializer = jsonSerializer;
_appHost = appHost;
}
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().ConfigureAwait(false);
await ExtractSystemProfilesAsync();
LoadProfiles();
}
catch (Exception ex)
@@ -92,6 +86,7 @@ namespace Emby.Dlna
.Select(i => i.Item2)
.ToList();
}
}
public DeviceProfile GetDefaultProfile()
@@ -126,92 +121,83 @@ namespace Emby.Dlna
var builder = new StringBuilder();
builder.AppendLine("No matching device profile found. The default will need to be used.");
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);
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));
_logger.LogInformation(builder.ToString());
}
private 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 || !IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
{
if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
{
if (deviceInfo.Manufacturer == null || !IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
{
if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
{
if (deviceInfo.ManufacturerUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
{
if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
{
if (deviceInfo.ModelDescription == null || !IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
{
if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelName))
{
if (deviceInfo.ModelName == null || !IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName))
{
if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
{
if (deviceInfo.ModelNumber == null || !IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
{
if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
{
if (deviceInfo.ModelUrl == null || !IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
{
if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
return false;
}
}
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
{
if (deviceInfo.SerialNumber == null || !IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
{
if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
return false;
}
}
return true;
}
private bool IsRegexOrSubstringMatch(string input, string pattern)
private bool IsRegexMatch(string input, string pattern)
{
try
{
return input.Contains(pattern, StringComparison.OrdinalIgnoreCase) || Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
return Regex.IsMatch(input, pattern);
}
catch (ArgumentException ex)
{
@@ -235,7 +221,7 @@ namespace Emby.Dlna
}
else
{
var headerString = string.Join(", ", headers.Select(i => string.Format(CultureInfo.InvariantCulture, "{0}={1}", i.Key, i.Value)));
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);
}
@@ -263,7 +249,7 @@ namespace Emby.Dlna
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
case HeaderMatchType.Substring:
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
// _logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
//_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);
@@ -275,6 +261,10 @@ 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
@@ -378,14 +368,14 @@ namespace Emby.Dlna
foreach (var name in _assembly.GetManifestResourceNames())
{
if (!name.StartsWith(namespaceName, StringComparison.Ordinal))
if (!name.StartsWith(namespaceName))
{
continue;
}
var path = Path.Join(
systemProfilesPath,
Path.GetFileName(name.AsSpan()).Slice(namespaceName.Length));
var filename = Path.GetFileName(name).Substring(namespaceName.Length);
var path = Path.Combine(systemProfilesPath, filename);
using (var stream = _assembly.GetManifestResourceStream(name))
{
@@ -395,9 +385,9 @@ namespace Emby.Dlna
{
Directory.CreateDirectory(systemProfilesPath);
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
{
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
await stream.CopyToAsync(fileStream);
}
}
}
@@ -447,7 +437,6 @@ namespace Emby.Dlna
{
throw new ArgumentException("Profile is missing Id");
}
if (string.IsNullOrEmpty(profile.Name))
{
throw new ArgumentException("Profile is missing Name");
@@ -473,7 +462,6 @@ namespace Emby.Dlna
{
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
}
SerializeToXml(profile, path);
}
@@ -484,10 +472,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 serialize properly to xml (different root element tag name).
/// If it's a subclass it may not serlialize properly to xml (different root element tag name)
/// </summary>
/// <param name="profile">The device profile.</param>
/// <returns>The re-serialized device profile.</returns>
/// <param name="profile"></param>
/// <returns></returns>
private DeviceProfile ReserializeProfile(DeviceProfile profile)
{
if (profile.GetType() == typeof(DeviceProfile))
@@ -500,9 +488,16 @@ namespace Emby.Dlna
return _jsonSerializer.DeserializeFromString<DeviceProfile>(json);
}
class InternalProfileInfo
{
internal DeviceProfileInfo Info { get; set; }
internal string Path { get; set; }
}
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
{
var profile = GetDefaultProfile();
var profile = GetProfile(headers) ??
GetDefaultProfile();
var serverId = _appHost.SystemId;
@@ -523,15 +518,7 @@ namespace Emby.Dlna
Stream = _assembly.GetManifestResourceStream(resource)
};
}
private class InternalProfileInfo
{
internal DeviceProfileInfo Info { get; set; }
internal string Path { get; set; }
}
}
/*
class DlnaProfileEntryPoint : IServerEntryPoint
{
@@ -577,9 +564,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,10 +1,5 @@
<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>
@@ -17,22 +12,9 @@
</ItemGroup>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
<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>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
@@ -80,7 +62,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.6" />
</ItemGroup>
</Project>

View File

@@ -1,20 +1,17 @@
#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()
{
Headers = new Dictionary<string, string>();
}
public string Content { get; set; }
public string ContentType { get; set; }
public Dictionary<string, string> Headers { get; }
}
}

View File

@@ -1,12 +1,8 @@
#pragma warning disable CS1591
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Mime;
using System.Text;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
@@ -15,45 +11,45 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.Eventing
{
public class DlnaEventManager : IDlnaEventManager
public class EventManager : IEventManager
{
private readonly ConcurrentDictionary<string, EventSubscription> _subscriptions =
new ConcurrentDictionary<string, EventSubscription>(StringComparer.OrdinalIgnoreCase);
private readonly ILogger _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IHttpClient _httpClient;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public DlnaEventManager(ILogger logger, IHttpClientFactory httpClientFactory)
public EventManager(ILogger logger, IHttpClient httpClient)
{
_httpClientFactory = httpClientFactory;
_httpClient = httpClient;
_logger = logger;
}
public EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl)
{
var subscription = GetSubscription(subscriptionId, false);
int timeoutSeconds;
// Remove logging for now because some devices are sending this very frequently
// TODO re-enable with dlna debug logging setting
//_logger.LogDebug("Renewing event subscription for {0} with timeout of {1} to {2}",
// subscription.NotificationType,
// timeout,
// subscription.CallbackUrl);
if (subscription != null)
{
subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
int timeoutSeconds = subscription.TimeoutSeconds;
timeoutSeconds = subscription.TimeoutSeconds;
subscription.SubscriptionTime = DateTime.UtcNow;
_logger.LogDebug(
"Renewing event subscription for {0} with timeout of {1} to {2}",
subscription.NotificationType,
timeoutSeconds,
subscription.CallbackUrl);
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
}
else
{
timeoutSeconds = 300;
}
return new EventSubscriptionResponse
{
Content = string.Empty,
ContentType = "text/plain"
};
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
}
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
@@ -61,19 +57,19 @@ namespace Emby.Dlna.Eventing
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
_logger.LogDebug(
"Creating event subscription for {0} with timeout of {1} to {2}",
notificationType,
timeout,
callbackUrl);
// Remove logging for now because some devices are sending this very frequently
// TODO re-enable with dlna debug logging setting
//_logger.LogDebug("Creating event subscription for {0} with timeout of {1} to {2}",
// notificationType,
// timeout,
// callbackUrl);
_subscriptions.TryAdd(id, new EventSubscription
{
Id = id,
CallbackUrl = callbackUrl,
SubscriptionTime = DateTime.UtcNow,
TimeoutSeconds = timeout,
NotificationType = notificationType
TimeoutSeconds = timeout
});
return GetEventSubscriptionResponse(id, requestedTimeoutString, timeout);
@@ -84,7 +80,7 @@ namespace Emby.Dlna.Eventing
if (!string.IsNullOrEmpty(header))
{
// Starts with SECOND-
header = header.Split('-')[^1];
header = header.Split('-').Last();
if (int.TryParse(header, NumberStyles.Integer, _usCulture, out var val))
{
@@ -99,7 +95,7 @@ namespace Emby.Dlna.Eventing
{
_logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
_subscriptions.TryRemove(subscriptionId, out _);
_subscriptions.TryRemove(subscriptionId, out EventSubscription sub);
return new EventSubscriptionResponse
{
@@ -108,6 +104,7 @@ namespace Emby.Dlna.Eventing
};
}
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
{
var response = new EventSubscriptionResponse
@@ -156,30 +153,33 @@ namespace Emby.Dlna.Eventing
builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
foreach (var key in stateVariables.Keys)
{
builder.Append("<e:property>")
.Append('<')
.Append(key)
.Append('>')
.Append(stateVariables[key])
.Append("</")
.Append(key)
.Append('>')
.Append("</e:property>");
builder.Append("<e:property>");
builder.Append("<" + key + ">");
builder.Append(stateVariables[key]);
builder.Append("</" + key + ">");
builder.Append("</e:property>");
}
builder.Append("</e:propertyset>");
using var options = new HttpRequestMessage(new HttpMethod("NOTIFY"), subscription.CallbackUrl);
options.Content = new StringContent(builder.ToString(), Encoding.UTF8, MediaTypeNames.Text.Xml);
options.Headers.TryAddWithoutValidation("NT", subscription.NotificationType);
options.Headers.TryAddWithoutValidation("NTS", "upnp:propchange");
options.Headers.TryAddWithoutValidation("SID", subscription.Id);
options.Headers.TryAddWithoutValidation("SEQ", subscription.TriggerCount.ToString(_usCulture));
var options = new HttpRequestOptions
{
RequestContent = builder.ToString(),
RequestContentType = "text/xml",
Url = subscription.CallbackUrl,
BufferContent = false
};
options.RequestHeaders.Add("NT", subscription.NotificationType);
options.RequestHeaders.Add("NTS", "upnp:propchange");
options.RequestHeaders.Add("SID", subscription.Id);
options.RequestHeaders.Add("SEQ", subscription.TriggerCount.ToString(_usCulture));
try
{
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
.SendAsync(options, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
using (await _httpClient.SendAsync(options, "NOTIFY").ConfigureAwait(false))
{
}
}
catch (OperationCanceledException)
{

View File

@@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System;
namespace Emby.Dlna.Eventing
@@ -7,19 +5,14 @@ namespace Emby.Dlna.Eventing
public class EventSubscription
{
public string Id { get; set; }
public string CallbackUrl { get; set; }
public string NotificationType { get; set; }
public DateTime SubscriptionTime { get; set; }
public int TimeoutSeconds { get; set; }
public long TriggerCount { get; set; }
public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
public void IncrementTriggerCount()
{
if (TriggerCount == long.MaxValue)
@@ -29,5 +22,7 @@ namespace Emby.Dlna.Eventing
TriggerCount++;
}
public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
}
}

View File

@@ -1,8 +1,7 @@
#pragma warning disable CS1591
namespace Emby.Dlna
{
public interface IConnectionManager : IDlnaEventManager, IUpnpService
public interface IConnectionManager : IEventManager, IUpnpService
{
}
}

View File

@@ -1,8 +1,7 @@
#pragma warning disable CS1591
namespace Emby.Dlna
{
public interface IContentDirectory : IDlnaEventManager, IUpnpService
public interface IContentDirectory : IEventManager, IUpnpService
{
}
}

View File

@@ -1,33 +1,22 @@
#pragma warning disable CS1591
namespace Emby.Dlna
{
public interface IDlnaEventManager
public interface IEventManager
{
/// <summary>
/// Cancels the event subscription.
/// </summary>
/// <param name="subscriptionId">The subscription identifier.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse CancelEventSubscription(string subscriptionId);
/// <summary>
/// Renews the event subscription.
/// </summary>
/// <param name="subscriptionId">The subscription identifier.</param>
/// <param name="notificationType">The notification type.</param>
/// <param name="requestedTimeoutString">The requested timeout as a sting.</param>
/// <param name="callbackUrl">The callback url.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse RenewEventSubscription(string subscriptionId, string notificationType, string requestedTimeoutString, string callbackUrl);
/// <summary>
/// Creates the event subscription.
/// </summary>
/// <param name="notificationType">The notification type.</param>
/// <param name="requestedTimeoutString">The requested timeout as a sting.</param>
/// <param name="callbackUrl">The callback url.</param>
/// <returns>The response.</returns>
EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl);
}
}

View File

@@ -1,8 +1,7 @@
#pragma warning disable CS1591
namespace Emby.Dlna
{
public interface IMediaReceiverRegistrar : IDlnaEventManager, IUpnpService
public interface IMediaReceiverRegistrar : IEventManager, IUpnpService
{
}
}

View File

@@ -1,7 +1,3 @@
#pragma warning disable CS1591
using System.Threading.Tasks;
namespace Emby.Dlna
{
public interface IUpnpService
@@ -17,6 +13,6 @@ namespace Emby.Dlna
/// </summary>
/// <param name="request">The request.</param>
/// <returns>ControlResponse.</returns>
Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request);
ControlResponse ProcessControlRequest(ControlRequest request);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 286 B

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