mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-12-12 20:13:01 +03:00
Compare commits
28 Commits
v10.8.0-al
...
v10.4.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76b4ba3c5e | ||
|
|
292d4b585b | ||
|
|
0dd08bbbb4 | ||
|
|
ac8572fd2d | ||
|
|
b300a4e8d4 | ||
|
|
b3fc995977 | ||
|
|
7f5a070406 | ||
|
|
7ccef6068b | ||
|
|
06aac98996 | ||
|
|
0f18482ba6 | ||
|
|
ffd7835ab5 | ||
|
|
fb6b103164 | ||
|
|
2de763eef9 | ||
|
|
46ab046c34 | ||
|
|
cf54a0e8be | ||
|
|
e73cf46e14 | ||
|
|
39c3b2f044 | ||
|
|
7a592a0f15 | ||
|
|
1fad64cd59 | ||
|
|
86e5dc4607 | ||
|
|
e98e4766f7 | ||
|
|
6e59671cf6 | ||
|
|
86a50367b2 | ||
|
|
0212c0b85f | ||
|
|
89d365122c | ||
|
|
9c0a8350d6 | ||
|
|
818d21718c | ||
|
|
0b551c0cd4 |
@@ -1,93 +0,0 @@
|
||||
parameters:
|
||||
- name: Packages
|
||||
type: object
|
||||
default: {}
|
||||
- name: LinuxImage
|
||||
type: string
|
||||
default: "ubuntu-latest"
|
||||
- name: DotNetSdkVersion
|
||||
type: string
|
||||
default: 6.0.x
|
||||
|
||||
jobs:
|
||||
- job: CompatibilityCheck
|
||||
displayName: Compatibility Check
|
||||
dependsOn: Build
|
||||
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
|
||||
|
||||
pool:
|
||||
vmImage: "${{ parameters.LinuxImage }}"
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
${{ each Package in parameters.Packages }}:
|
||||
${{ Package.key }}:
|
||||
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
||||
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
||||
maxParallel: 2
|
||||
|
||||
steps:
|
||||
- checkout: none
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: "Update DotNet"
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: ${{ parameters.DotNetSdkVersion }}
|
||||
|
||||
- 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)
|
||||
@@ -1,100 +0,0 @@
|
||||
parameters:
|
||||
LinuxImage: 'ubuntu-latest'
|
||||
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||
DotNetSdkVersion: 6.0.x
|
||||
|
||||
jobs:
|
||||
- job: Build
|
||||
displayName: Build
|
||||
strategy:
|
||||
matrix:
|
||||
Release:
|
||||
BuildConfiguration: Release
|
||||
Debug:
|
||||
BuildConfiguration: Debug
|
||||
pool:
|
||||
vmImage: '${{ parameters.LinuxImage }}'
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: true
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Web Branch'
|
||||
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
|
||||
inputs:
|
||||
path: '$(Agent.TempDirectory)'
|
||||
artifact: 'jellyfin-web-production'
|
||||
source: 'specific'
|
||||
project: 'jellyfin'
|
||||
pipeline: 'Jellyfin Web'
|
||||
runBranch: variables['Build.SourceBranch']
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Web Target'
|
||||
condition: eq(variables['Build.Reason'], 'PullRequest')
|
||||
inputs:
|
||||
path: '$(Agent.TempDirectory)'
|
||||
artifact: 'jellyfin-web-production'
|
||||
source: 'specific'
|
||||
project: 'jellyfin'
|
||||
pipeline: 'Jellyfin Web'
|
||||
runBranch: variables['System.PullRequest.TargetBranch']
|
||||
|
||||
- task: ExtractFiles@1
|
||||
displayName: 'Extract Web Client'
|
||||
inputs:
|
||||
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
|
||||
destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
|
||||
cleanDestinationFolder: false
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Update DotNet'
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: ${{ parameters.DotNetSdkVersion }}
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Publish Server'
|
||||
inputs:
|
||||
command: publish
|
||||
publishWebProjects: false
|
||||
projects: '${{ parameters.RestoreBuildProjects }}'
|
||||
arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
|
||||
zipAfterPublish: false
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish Artifact Naming'
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
|
||||
artifactName: 'Jellyfin.Naming'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish Artifact Controller'
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
|
||||
artifactName: 'Jellyfin.Controller'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish Artifact Model'
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
|
||||
artifactName: 'Jellyfin.Model'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish Artifact Common'
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
|
||||
artifactName: 'Jellyfin.Common'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish Artifact Extensions'
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Jellyfin.Extensions.dll'
|
||||
artifactName: 'Jellyfin.Extensions'
|
||||
@@ -1,261 +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
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- task: SSH@0
|
||||
displayName: 'Update Unstable Repository'
|
||||
continueOnError: true
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||
inputs:
|
||||
sshEndpoint: repository
|
||||
runOptions: 'commands'
|
||||
commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable &
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Update Stable Repository'
|
||||
continueOnError: true
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||
inputs:
|
||||
sshEndpoint: repository
|
||||
runOptions: 'commands'
|
||||
commands: nohup sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) $(Build.SourceBranch) &
|
||||
|
||||
- job: PublishNuget
|
||||
displayName: 'Publish NuGet packages'
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
variables:
|
||||
- name: JellyfinVersion
|
||||
value: $[replace(variables['Build.SourceBranch'],'refs/tags/v','')]
|
||||
|
||||
steps:
|
||||
- task: UseDotNet@2
|
||||
displayName: 'Use .NET 6.0 sdk'
|
||||
inputs:
|
||||
packageType: 'sdk'
|
||||
version: '6.0.x'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Build Stable Nuget packages'
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||
inputs:
|
||||
command: 'custom'
|
||||
projects: |
|
||||
Jellyfin.Data/Jellyfin.Data.csproj
|
||||
MediaBrowser.Common/MediaBrowser.Common.csproj
|
||||
MediaBrowser.Controller/MediaBrowser.Controller.csproj
|
||||
MediaBrowser.Model/MediaBrowser.Model.csproj
|
||||
Emby.Naming/Emby.Naming.csproj
|
||||
src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
|
||||
custom: 'pack'
|
||||
arguments: -o $(Build.ArtifactStagingDirectory) -p:Version=$(JellyfinVersion)
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Build Unstable Nuget packages'
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||
inputs:
|
||||
command: 'custom'
|
||||
projects: |
|
||||
Jellyfin.Data/Jellyfin.Data.csproj
|
||||
MediaBrowser.Common/MediaBrowser.Common.csproj
|
||||
MediaBrowser.Controller/MediaBrowser.Controller.csproj
|
||||
MediaBrowser.Model/MediaBrowser.Model.csproj
|
||||
Emby.Naming/Emby.Naming.csproj
|
||||
src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
|
||||
custom: 'pack'
|
||||
arguments: '--version-suffix $(Build.BuildNumber) -o $(Build.ArtifactStagingDirectory) -p:Stability=Unstable'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
displayName: 'Publish Nuget packages'
|
||||
inputs:
|
||||
pathToPublish: $(Build.ArtifactStagingDirectory)
|
||||
artifactName: Jellyfin Nuget Packages
|
||||
|
||||
- task: NuGetCommand@2
|
||||
displayName: 'Push Nuget packages to stable feed'
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||
inputs:
|
||||
command: 'push'
|
||||
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
|
||||
nuGetFeedType: 'external'
|
||||
publishFeedCredentials: 'NugetOrg'
|
||||
allowPackageConflicts: true # This ignores an error if the version already exists
|
||||
|
||||
- task: NuGetAuthenticate@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
|
||||
@@ -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: 6.0.x
|
||||
|
||||
jobs:
|
||||
- job: Test
|
||||
displayName: Test
|
||||
strategy:
|
||||
matrix:
|
||||
${{ each imageName in parameters.ImageNames }}:
|
||||
${{ imageName.key }}:
|
||||
ImageName: ${{ imageName.value }}
|
||||
pool:
|
||||
vmImage: "$(ImageName)"
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: false
|
||||
|
||||
# This is required for the SonarCloud analyzer
|
||||
- task: UseDotNet@2
|
||||
displayName: "Install .NET SDK 5.x"
|
||||
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: '5.x'
|
||||
|
||||
- task: UseDotNet@2
|
||||
displayName: "Update DotNet"
|
||||
inputs:
|
||||
packageType: sdk
|
||||
version: ${{ parameters.DotNetSdkVersion }}
|
||||
|
||||
- task: SonarCloudPrepare@1
|
||||
displayName: 'Prepare analysis on SonarCloud'
|
||||
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||
enabled: false
|
||||
inputs:
|
||||
SonarCloud: 'Sonarcloud for Jellyfin'
|
||||
organization: 'jellyfin'
|
||||
projectKey: 'jellyfin_jellyfin'
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: 'Run CLI Tests'
|
||||
inputs:
|
||||
command: "test"
|
||||
projects: ${{ parameters.TestProjects }}
|
||||
arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal'
|
||||
publishTestResults: true
|
||||
testRunTitle: $(Agent.JobName)
|
||||
workingDirectory: "$(Build.SourcesDirectory)"
|
||||
|
||||
- task: SonarCloudAnalyze@1
|
||||
displayName: 'Run Code Analysis'
|
||||
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||
enabled: false
|
||||
|
||||
- task: SonarCloudPublish@1
|
||||
displayName: 'Publish Quality Gate Result'
|
||||
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||
enabled: false
|
||||
|
||||
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||
displayName: 'Run ReportGenerator'
|
||||
inputs:
|
||||
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
||||
targetdir: "$(Agent.TempDirectory)/merged/"
|
||||
reporttypes: "Cobertura"
|
||||
|
||||
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
|
||||
- task: PublishCodeCoverageResults@1
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||
displayName: 'Publish Code Coverage'
|
||||
inputs:
|
||||
codeCoverageTool: "cobertura"
|
||||
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
||||
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
||||
pathToSources: $(Build.SourcesDirectory)
|
||||
failIfCoverageEmpty: true
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Publish OpenAPI Artifact'
|
||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
|
||||
inputs:
|
||||
targetPath: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json"
|
||||
artifactName: 'OpenAPI Spec'
|
||||
@@ -1,48 +1,283 @@
|
||||
name: $(Date:yyyyMMdd)$(Rev:.r)
|
||||
|
||||
variables:
|
||||
- name: TestProjects
|
||||
value: 'tests/**/*Tests.csproj'
|
||||
- name: RestoreBuildProjects
|
||||
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||
- 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
|
||||
@@ -55,10 +290,82 @@ jobs:
|
||||
Common:
|
||||
NugetPackageName: Jellyfin.Common
|
||||
AssemblyFileName: MediaBrowser.Common.dll
|
||||
Extensions:
|
||||
NugetPackageName: Jellyfin.Extensions
|
||||
AssemblyFileName: Jellyfin.Extensions.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
|
||||
|
||||
46
.ci/publish-nightly.yml
Normal file
46
.ci/publish-nightly.yml
Normal 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
48
.ci/publish-release.yml
Normal 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'
|
||||
@@ -1 +0,0 @@
|
||||
../fedora/Makefile
|
||||
59
.copr/Makefile
Normal file
59
.copr/Makefile
Normal 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)"
|
||||
30
.drone.yml
Normal file
30
.drone.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: build-debug
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init --recursive
|
||||
|
||||
- name: build
|
||||
image: microsoft/dotnet:2-sdk
|
||||
commands:
|
||||
- dotnet publish "Jellyfin.Server" --configuration Debug --output "../ci/ci-debug"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: build-release
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init --recursive
|
||||
|
||||
- name: build
|
||||
image: microsoft/dotnet:2-sdk
|
||||
commands:
|
||||
- dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"
|
||||
|
||||
@@ -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
|
||||
|
||||
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -1,4 +0,0 @@
|
||||
# Joshua must review all changes to deployment and build.sh
|
||||
.ci/* @joshuaboniface
|
||||
deployment/* @joshuaboniface
|
||||
build.sh @joshuaboniface
|
||||
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Logs**
|
||||
<!-- Please paste any log errors. -->
|
||||
|
||||
**Screenshots**
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**System (please complete the following information):**
|
||||
- OS: [e.g. Docker, Debian, Windows]
|
||||
- Browser: [e.g. Firefox, Chrome, Safari]
|
||||
- Jellyfin Version: [e.g. 10.0.1]
|
||||
- Reverse proxy: [e.g. no, nginx, apache, etc.]
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
13
.github/ISSUE_TEMPLATE/feature_request.md
vendored
13
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -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.
|
||||
106
.github/ISSUE_TEMPLATE/issue report.yml
vendored
106
.github/ISSUE_TEMPLATE/issue report.yml
vendored
@@ -1,106 +0,0 @@
|
||||
name: Issue Report
|
||||
description: File an issue report
|
||||
title: "[Issue]: "
|
||||
labels: [bug, triage]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report! Please provide as much detail as necessary, most questions may not be applicable to you. If you need real-time help, join us on [Matrix](https://matrix.to/#/#jellyfin-troubleshooting:matrix.org) or [Discord](https://discord.gg/zHBxVSXdBV).
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: Please describe your bug
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: |
|
||||
The more information that you are able to provide, the better. Did you do anything before this happened? Did you upgrade or change anything? Any screenshots or logs you can provide will be helpful.
|
||||
|
||||
This is my issue.
|
||||
|
||||
Steps to Reproduce
|
||||
1. In this environment...
|
||||
2. With this config...
|
||||
3. Run '...'
|
||||
4. See error...
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: Jellyfin Version
|
||||
description: What version of Jellyfin are you running?
|
||||
options:
|
||||
- 10.7.7
|
||||
- 10.7.z
|
||||
- 10.6.4
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version-other
|
||||
attributes:
|
||||
label: "if other:"
|
||||
placeholder: Other
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
Examples:
|
||||
- **OS**: [e.g. Debian, Windows]
|
||||
- **Virtualization**: [e.g. Docker, KVM, LXC]
|
||||
- **Clients**: [Browser, Android, Fire Stick, etc.]
|
||||
- **Browser**: [e.g. Firefox 91, Chrome 93, Safari 13]
|
||||
- **FFmpeg Version**: [e.g. 4.3.2-Jellyfin]
|
||||
- **Playback**: [Direct Play, Remux, Direct Stream, Transcode]
|
||||
- **Hardware Acceleration**: [e.g. none, VAAPI, NVENC, etc.]
|
||||
- **Installed Plugins**: [e.g. none, Fanart, Anime, etc.]
|
||||
- **Reverse Proxy**: [e.g. none, nginx, apache, etc.]
|
||||
- **Base URL**: [e.g. none, yes: /example]
|
||||
- **Networking**: [e.g. Host, Bridge/NAT]
|
||||
- **Storage**: [e.g. local, NFS, cloud]
|
||||
value: |
|
||||
- OS:
|
||||
- Virtualization:
|
||||
- Clients:
|
||||
- Browser:
|
||||
- FFmpeg Version:
|
||||
- Playback Method:
|
||||
- Hardware Acceleration:
|
||||
- Plugins:
|
||||
- Reverse Proxy:
|
||||
- Base URL:
|
||||
- Networking:
|
||||
- Storage:
|
||||
render: markdown
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Jellyfin logs
|
||||
description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs.
|
||||
placeholder: For playback issues, browser/client and FFmpeg logs may be more useful.
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: ffmpeg-logs
|
||||
attributes:
|
||||
label: FFmpeg logs
|
||||
description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs.
|
||||
placeholder: It's important to include the specific codec details. If no FFmpeg logs appear, the file was Direct Played and did not use FFmpeg.
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: browserlogs
|
||||
attributes:
|
||||
label: Please attach any browser or client logs here
|
||||
placeholder: Access browser logs by using the F12 to bring up the console. Screenshots are typically easier to read than raw logs. For clients such as Android or iOS, please see our documentation.
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Please attach any screenshots here
|
||||
placeholder: Images can be pasted directly into the textbox and will be hosted by github.
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: Code of Conduct
|
||||
description: By submitting this issue, you agree to follow our [Code of Conduct](https://jellyfin.org/docs/general/community-standards.html#code-of-conduct)
|
||||
options:
|
||||
- label: I agree to follow this project's Code of Conduct
|
||||
required: true
|
||||
6
.github/ISSUE_TEMPLATE/media_playback.md
vendored
6
.github/ISSUE_TEMPLATE/media_playback.md
vendored
@@ -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]
|
||||
|
||||
|
||||
15
.github/dependabot.yml
vendored
15
.github/dependabot.yml
vendored
@@ -1,15 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: nuget
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
time: '12:00'
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
- package-ecosystem: github-actions
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: weekly
|
||||
time: '12:00'
|
||||
open-pull-requests-limit: 10
|
||||
17
.github/stale.yml
vendored
17
.github/stale.yml
vendored
@@ -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,19 +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
|
||||
|
||||
# Disable automatic closing of pull requests
|
||||
pulls:
|
||||
daysUntilClose: false
|
||||
|
||||
76
.github/workflows/automation.yml
vendored
76
.github/workflows/automation.yml
vendored
@@ -1,76 +0,0 @@
|
||||
name: Automation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
issue_comment:
|
||||
|
||||
jobs:
|
||||
label:
|
||||
name: Labeling
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'jellyfin/jellyfin' }}
|
||||
steps:
|
||||
- name: Apply label
|
||||
uses: eps1lon/actions-label-merge-conflict@v2.0.1
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
|
||||
with:
|
||||
dirtyLabel: 'merge conflict'
|
||||
repoToken: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
project:
|
||||
name: Project board
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'jellyfin/jellyfin' }}
|
||||
steps:
|
||||
- name: Remove from 'Current Release' project
|
||||
uses: alex-page/github-project-automation-plus@v0.8.1
|
||||
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
|
||||
continue-on-error: true
|
||||
with:
|
||||
project: Current Release
|
||||
action: delete
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Add to 'Release Next' project
|
||||
uses: alex-page/github-project-automation-plus@v0.8.1
|
||||
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
|
||||
continue-on-error: true
|
||||
with:
|
||||
project: Release Next
|
||||
column: In progress
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Add to 'Current Release' project
|
||||
uses: alex-page/github-project-automation-plus@v0.8.1
|
||||
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
|
||||
continue-on-error: true
|
||||
with:
|
||||
project: Current Release
|
||||
column: In progress
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Check number of comments from the team member
|
||||
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
|
||||
id: member_comments
|
||||
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
|
||||
|
||||
- name: Move issue to needs triage
|
||||
uses: alex-page/github-project-automation-plus@v0.8.1
|
||||
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
|
||||
continue-on-error: true
|
||||
with:
|
||||
project: Issue Triage for Main Repo
|
||||
column: Needs triage
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Add issue to triage project
|
||||
uses: alex-page/github-project-automation-plus@v0.8.1
|
||||
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
|
||||
continue-on-error: true
|
||||
with:
|
||||
project: Issue Triage for Main Repo
|
||||
column: Pending response
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
37
.github/workflows/codeql-analysis.yml
vendored
37
.github/workflows/codeql-analysis.yml
vendored
@@ -1,37 +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: '6.0.x'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-extended
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
119
.github/workflows/commands.yml
vendored
119
.github/workflows/commands.yml
vendored
@@ -1,119 +0,0 @@
|
||||
name: Commands
|
||||
on:
|
||||
issue_comment:
|
||||
types:
|
||||
- created
|
||||
- edited
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
rebase:
|
||||
name: Rebase
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '@jellyfin-bot rebase') && github.event.comment.author_association == 'MEMBER'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify as seen
|
||||
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reactions: '+1'
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@1.5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
check-backport:
|
||||
name: Check Backport
|
||||
if: ${{ ( github.event.issue.pull_request && contains(github.event.comment.body, '@jellyfin-bot check backport') ) || github.event.label.name == 'stable backport' || contains(github.event.pull_request.labels.*.name, 'stable backport' ) }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify as seen
|
||||
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||
if: ${{ github.event.comment != null }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reactions: eyes
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Notify as running
|
||||
id: comment_running
|
||||
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||
if: ${{ github.event.comment != null }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Running backport tests...
|
||||
|
||||
- name: Perform test backport
|
||||
id: run_tests
|
||||
run: |
|
||||
set +o errexit
|
||||
git config --global user.name "Jellyfin Bot"
|
||||
git config --global user.email "team@jellyfin.org"
|
||||
CURRENT_BRANCH="origin/${GITHUB_HEAD_REF}"
|
||||
git checkout master
|
||||
git merge --no-ff ${CURRENT_BRANCH}
|
||||
MERGE_COMMIT_HASH=$( git log -q -1 | head -1 | awk '{ print $2 }' )
|
||||
git fetch --all
|
||||
CURRENT_STABLE=$( git branch -r | grep 'origin/release' | sort -rV | head -1 | awk -F '/' '{ print $NF }' )
|
||||
stable_branch="Current stable release branch: ${CURRENT_STABLE}"
|
||||
echo ${stable_branch}
|
||||
echo ::set-output name=branch::${stable_branch}
|
||||
git checkout -t origin/${CURRENT_STABLE} -b ${CURRENT_STABLE}
|
||||
git cherry-pick -sx -m1 ${MERGE_COMMIT_HASH} &>output.txt
|
||||
retcode=$?
|
||||
cat output.txt | grep -v 'hint:'
|
||||
output="$( grep -v 'hint:' output.txt )"
|
||||
output="${output//'%'/'%25'}"
|
||||
output="${output//$'\n'/'%0A'}"
|
||||
output="${output//$'\r'/'%0D'}"
|
||||
echo ::set-output name=output::$output
|
||||
exit ${retcode}
|
||||
|
||||
- name: Notify with result success
|
||||
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||
if: ${{ github.event.comment != null && success() }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ steps.comment_running.outputs.comment-id }}
|
||||
body: |
|
||||
${{ steps.run_tests.outputs.branch }}
|
||||
Output from `git cherry-pick`:
|
||||
|
||||
---
|
||||
|
||||
${{ steps.run_tests.outputs.output }}
|
||||
reactions: hooray
|
||||
|
||||
- name: Notify with result failure
|
||||
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||
if: ${{ github.event.comment != null && failure() }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ steps.comment_running.outputs.comment-id }}
|
||||
body: |
|
||||
${{ steps.run_tests.outputs.branch }}
|
||||
Output from `git cherry-pick`:
|
||||
|
||||
---
|
||||
|
||||
${{ steps.run_tests.outputs.output }}
|
||||
reactions: confused
|
||||
124
.github/workflows/openapi.yml
vendored
124
.github/workflows/openapi.yml
vendored
@@ -1,124 +0,0 @@
|
||||
name: OpenAPI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
|
||||
jobs:
|
||||
openapi-head:
|
||||
name: OpenAPI - HEAD
|
||||
runs-on: ubuntu-latest
|
||||
permissions: read-all
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
- name: Generate openapi.json
|
||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||
- name: Upload openapi.json
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: openapi-head
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json
|
||||
|
||||
openapi-base:
|
||||
name: OpenAPI - BASE
|
||||
if: ${{ github.base_ref != '' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions: read-all
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
- name: Setup .NET Core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: '6.0.x'
|
||||
- name: Generate openapi.json
|
||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||
- name: Upload openapi.json
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: openapi-base
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net6.0/openapi.json
|
||||
|
||||
openapi-diff:
|
||||
name: OpenAPI - Difference
|
||||
if: ${{ github.event_name == 'pull_request_target' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- openapi-head
|
||||
- openapi-base
|
||||
steps:
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
- name: Download openapi-base
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: openapi-base
|
||||
path: openapi-base
|
||||
- name: Workaround openapi-diff issue
|
||||
run: |
|
||||
sed -i 's/"allOf"/"oneOf"/g' openapi-head/openapi.json
|
||||
sed -i 's/"allOf"/"oneOf"/g' openapi-base/openapi.json
|
||||
- name: Calculate OpenAPI difference
|
||||
uses: docker://openapitools/openapi-diff
|
||||
continue-on-error: true
|
||||
with:
|
||||
args: --fail-on-changed --markdown openapi-changes.md openapi-base/openapi.json openapi-head/openapi.json
|
||||
- id: read-diff
|
||||
name: Read openapi-diff output
|
||||
run: |
|
||||
body=$(cat openapi-changes.md)
|
||||
body="${body//'%'/'%25'}"
|
||||
body="${body//$'\n'/'%0A'}"
|
||||
body="${body//$'\r'/'%0D'}"
|
||||
echo ::set-output name=body::$body
|
||||
- name: Find difference comment
|
||||
uses: peter-evans/find-comment@v1
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
direction: last
|
||||
body-includes: openapi-diff-workflow-comment
|
||||
- name: Reply or edit difference comment (changed)
|
||||
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||
if: ${{ steps.read-diff.outputs.body != '' }}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
edit-mode: replace
|
||||
body: |
|
||||
<!--openapi-diff-workflow-comment-->
|
||||
<details>
|
||||
<summary>Changes in OpenAPI specification found. Expand to see details.</summary>
|
||||
|
||||
${{ steps.read-diff.outputs.body }}
|
||||
|
||||
</details>
|
||||
- name: Edit difference comment (unchanged)
|
||||
uses: peter-evans/create-or-update-comment@v1.4.5
|
||||
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
edit-mode: replace
|
||||
body: |
|
||||
<!--openapi-diff-workflow-comment-->
|
||||
|
||||
No changes to OpenAPI specification found. See history of this comment for previous changes.
|
||||
27
.gitignore
vendored
27
.gitignore
vendored
@@ -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,16 +268,3 @@ doc/
|
||||
# Deployment artifacts
|
||||
dist
|
||||
*.exe
|
||||
*.dll
|
||||
|
||||
# BenchmarkDotNet artifacts
|
||||
BenchmarkDotNet.Artifacts
|
||||
|
||||
# Ignore web artifacts from native builds
|
||||
web/
|
||||
web-src.*
|
||||
MediaBrowser.WebDashboard/jellyfin-web
|
||||
apiclient/generated
|
||||
|
||||
# Omnisharp crash logs
|
||||
mono_crash.*.json
|
||||
|
||||
3
.npmrc
3
.npmrc
@@ -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
|
||||
14
.vscode/extensions.json
vendored
14
.vscode/extensions.json
vendored
@@ -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
34
.vscode/launch.json
vendored
@@ -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/net6.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/net6.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
19
.vscode/tasks.json
vendored
@@ -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
17
BDInfo/BDInfo.csproj
Normal 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
33
BDInfo/BDInfoSettings.cs
Normal 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
449
BDInfo/BDROM.cs
Normal 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
493
BDInfo/LanguageCodes.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
BDInfo/Properties/AssemblyInfo.cs
Normal file
21
BDInfo/Properties/AssemblyInfo.cs
Normal 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
5
BDInfo/ReadMe.txt
Normal 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
309
BDInfo/TSCodecAC3.cs
Normal 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
148
BDInfo/TSCodecAVC.cs
Normal 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
159
BDInfo/TSCodecDTS.cs
Normal 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
246
BDInfo/TSCodecDTSHD.cs
Normal 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
123
BDInfo/TSCodecLPCM.cs
Normal 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
208
BDInfo/TSCodecMPEG2.cs
Normal 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
36
BDInfo/TSCodecMVC.cs
Normal 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
186
BDInfo/TSCodecTrueHD.cs
Normal 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
131
BDInfo/TSCodecVC1.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
BDInfo/TSInterleavedFile.cs
Normal file
37
BDInfo/TSInterleavedFile.cs
Normal 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
1282
BDInfo/TSPlaylistFile.cs
Normal file
File diff suppressed because it is too large
Load Diff
780
BDInfo/TSStream.cs
Normal file
780
BDInfo/TSStream.cs
Normal 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
130
BDInfo/TSStreamBuffer.cs
Normal 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
107
BDInfo/TSStreamClip.cs
Normal 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
244
BDInfo/TSStreamClipFile.cs
Normal 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
1555
BDInfo/TSStreamFile.cs
Normal file
File diff suppressed because it is too large
Load Diff
178
CONTRIBUTORS.md
178
CONTRIBUTORS.md
@@ -1,156 +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)
|
||||
- [cocool97](https://github.com/cocool97)
|
||||
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
|
||||
- [crankdoofus](https://github.com/crankdoofus)
|
||||
- [crobibero](https://github.com/crobibero)
|
||||
- [cromefire](https://github.com/cromefire)
|
||||
- [cryptobank](https://github.com/cryptobank)
|
||||
- [cvium](https://github.com/cvium)
|
||||
- [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)
|
||||
- [GodTamIt](https://github.com/GodTamIt)
|
||||
- [grafixeyehero](https://github.com/grafixeyehero)
|
||||
- [h1nk](https://github.com/h1nk)
|
||||
- [hawken93](https://github.com/hawken93)
|
||||
- [HelloWorld017](https://github.com/HelloWorld017)
|
||||
- [ikomhoog](https://github.com/ikomhoog)
|
||||
- [jftuga](https://github.com/jftuga)
|
||||
- [joern-h](https://github.com/joern-h)
|
||||
- [joshuaboniface](https://github.com/joshuaboniface)
|
||||
- [JustAMan](https://github.com/JustAMan)
|
||||
- [justinfenn](https://github.com/justinfenn)
|
||||
- [KerryRJ](https://github.com/KerryRJ)
|
||||
- [Larvitar](https://github.com/Larvitar)
|
||||
- [LeoVerto](https://github.com/LeoVerto)
|
||||
- [Liggy](https://github.com/Liggy)
|
||||
- [lmaonator](https://github.com/lmaonator)
|
||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
|
||||
- [loli10K](https://github.com/loli10K)
|
||||
- [lostmypillow](https://github.com/lostmypillow)
|
||||
- [Lynxy](https://github.com/Lynxy)
|
||||
- [ManfredRichthofen](https://github.com/ManfredRichthofen)
|
||||
- [Marenz](https://github.com/Marenz)
|
||||
- [marius-luca-87](https://github.com/marius-luca-87)
|
||||
- [mark-monteiro](https://github.com/mark-monteiro)
|
||||
- [Matt07211](https://github.com/Matt07211)
|
||||
- [Maxr1998](https://github.com/Maxr1998)
|
||||
- [mcarlton00](https://github.com/mcarlton00)
|
||||
- [mitchfizz05](https://github.com/mitchfizz05)
|
||||
- [MrTimscampi](https://github.com/MrTimscampi)
|
||||
- [n8225](https://github.com/n8225)
|
||||
- [Narfinger](https://github.com/Narfinger)
|
||||
- [NathanPickard](https://github.com/NathanPickard)
|
||||
- [neilsb](https://github.com/neilsb)
|
||||
- [nevado](https://github.com/nevado)
|
||||
- [Nickbert7](https://github.com/Nickbert7)
|
||||
- [JoshuaBoniface](https://github.com/joshuaboniface)
|
||||
- [nvllsvm](https://github.com/nvllsvm)
|
||||
- [nyanmisaka](https://github.com/nyanmisaka)
|
||||
- [OancaAndrei](https://github.com/OancaAndrei)
|
||||
- [obradovichv](https://github.com/obradovichv)
|
||||
- [oddstr13](https://github.com/oddstr13)
|
||||
- [orryverducci](https://github.com/orryverducci)
|
||||
- [petermcneil](https://github.com/petermcneil)
|
||||
- [Phlogi](https://github.com/Phlogi)
|
||||
- [pjeanjean](https://github.com/pjeanjean)
|
||||
- [ploughpuff](https://github.com/ploughpuff)
|
||||
- [pR0Ps](https://github.com/pR0Ps)
|
||||
- [PrplHaz4](https://github.com/PrplHaz4)
|
||||
- [RazeLighter777](https://github.com/RazeLighter777)
|
||||
- [redSpoutnik](https://github.com/redSpoutnik)
|
||||
- [ringmatter](https://github.com/ringmatter)
|
||||
- [ryan-hartzell](https://github.com/ryan-hartzell)
|
||||
- [s0urcelab](https://github.com/s0urcelab)
|
||||
- [sachk](https://github.com/sachk)
|
||||
- [sammyrc34](https://github.com/sammyrc34)
|
||||
- [samuel9554](https://github.com/samuel9554)
|
||||
- [scheidleon](https://github.com/scheidleon)
|
||||
- [sebPomme](https://github.com/sebPomme)
|
||||
- [SegiH](https://github.com/SegiH)
|
||||
- [SenorSmartyPants](https://github.com/SenorSmartyPants)
|
||||
- [shemanaev](https://github.com/shemanaev)
|
||||
- [skaro13](https://github.com/skaro13)
|
||||
- [sl1288](https://github.com/sl1288)
|
||||
- [Smith00101010](https://github.com/Smith00101010)
|
||||
- [sorinyo2004](https://github.com/sorinyo2004)
|
||||
- [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)
|
||||
- [ianjazz246](https://github.com/ianjazz246)
|
||||
- [peterspenler](https://github.com/peterspenler)
|
||||
- [MBR-0001](https://github.com/MBR-0001)
|
||||
- [jonas-resch](https://github.com/jonas-resch)
|
||||
- [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
|
||||
|
||||
@@ -214,6 +93,3 @@
|
||||
- [tikuf](https://github.com/tikuf/)
|
||||
- [Tim Hobbs](https://github.com/timhobbs)
|
||||
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
|
||||
- [olsh](https://github.com/olsh)
|
||||
- [lbenini](https://github.com/lbenini)
|
||||
- [gnuyent](https://github.com/gnuyent)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<Project>
|
||||
<!-- Sets defaults for all projects in the repo -->
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
105
Dockerfile
105
Dockerfile
@@ -1,93 +1,42 @@
|
||||
# DESIGNED FOR BUILDING ON AMD64 ONLY
|
||||
#####################################
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=6.0
|
||||
ARG DOTNET_VERSION=2.2
|
||||
ARG FFMPEG_VERSION=latest
|
||||
|
||||
FROM node:lts-alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=master
|
||||
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
|
||||
FROM node:alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=v10.4.3
|
||||
RUN apk add curl \
|
||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& cd jellyfin-web-* \
|
||||
&& npm ci --no-audit --unsafe-perm \
|
||||
&& yarn install \
|
||||
&& yarn build \
|
||||
&& mv dist /dist
|
||||
|
||||
FROM debian:stable-slim as app
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
# https://github.com/intel/compute-runtime/releases
|
||||
ARG GMMLIB_VERSION=21.2.1
|
||||
ARG IGC_VERSION=1.0.8517
|
||||
ARG NEO_VERSION=21.35.20826
|
||||
ARG LEVEL_ZERO_VERSION=1.2.20826
|
||||
|
||||
# Install dependencies:
|
||||
# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
|
||||
# curl: healthcheck
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https curl \
|
||||
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
|
||||
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
mesa-va-drivers \
|
||||
jellyfin-ffmpeg \
|
||||
openssl \
|
||||
locales \
|
||||
# Intel VAAPI Tone mapping dependencies:
|
||||
# Prefer NEO to Beignet since the latter one doesn't support Comet Lake or newer for now.
|
||||
# Do not use the intel-opencl-icd package from repo since they will not build with RELEASE_WITH_REGKEYS enabled.
|
||||
&& mkdir intel-compute-runtime \
|
||||
&& cd intel-compute-runtime \
|
||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \
|
||||
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
|
||||
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
|
||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl_${NEO_VERSION}_amd64.deb \
|
||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-ocloc_${NEO_VERSION}_amd64.deb \
|
||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
|
||||
&& dpkg -i *.deb \
|
||||
&& cd .. \
|
||||
&& rm -rf intel-compute-runtime \
|
||||
&& apt-get remove gnupg wget apt-transport-https -y \
|
||||
&& apt-get clean autoclean -y \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media \
|
||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
|
||||
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
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 app
|
||||
|
||||
ENV HEALTHCHECK_URL=http://localhost:8096/health
|
||||
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
|
||||
# Install dependencies:
|
||||
# libfontconfig1: needed for Skia
|
||||
# mesa-va-drivers: needed for VAAPI
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -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
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
|
||||
CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||
--datadir /config \
|
||||
--cachedir /cache \
|
||||
--ffmpeg /usr/local/bin/ffmpeg
|
||||
|
||||
@@ -1,84 +1,44 @@
|
||||
# DESIGNED FOR BUILDING ON ARM ONLY
|
||||
#####################################
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=6.0
|
||||
ARG DOTNET_VERSION=3.0
|
||||
|
||||
|
||||
FROM node:lts-alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=master
|
||||
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
|
||||
FROM node:alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=v10.4.3
|
||||
RUN apk add curl \
|
||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& cd jellyfin-web-* \
|
||||
&& npm ci --no-audit --unsafe-perm \
|
||||
&& yarn install \
|
||||
&& yarn build \
|
||||
&& mv dist /dist
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||
FROM arm32v7/debian:stable-slim as app
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
||||
|
||||
# curl: setup & healthcheck
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
|
||||
curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \
|
||||
curl -ks https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \
|
||||
echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \
|
||||
echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
|
||||
apt-get update && \
|
||||
apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
jellyfin-ffmpeg \
|
||||
libssl-dev \
|
||||
libfontconfig1 \
|
||||
libfreetype6 \
|
||||
libomxil-bellagio0 \
|
||||
libomxil-bellagio-bin \
|
||||
libraspberrypi0 \
|
||||
vainfo \
|
||||
libva2 \
|
||||
locales \
|
||||
&& apt-get remove gnupg -y \
|
||||
&& apt-get clean autoclean -y \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media \
|
||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
|
||||
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
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 app
|
||||
|
||||
ENV HEALTHCHECK_URL=http://localhost:8096/health
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||
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 ffmpeg \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
|
||||
CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||
--datadir /config \
|
||||
--cachedir /cache \
|
||||
--ffmpeg /usr/bin/ffmpeg
|
||||
|
||||
@@ -1,75 +1,44 @@
|
||||
# DESIGNED FOR BUILDING ON ARM64 ONLY
|
||||
#####################################
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=6.0
|
||||
ARG DOTNET_VERSION=3.0
|
||||
|
||||
|
||||
FROM node:lts-alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=master
|
||||
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
|
||||
FROM node:alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=v10.4.3
|
||||
RUN apk add curl \
|
||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& cd jellyfin-web-* \
|
||||
&& npm ci --no-audit --unsafe-perm \
|
||||
&& yarn install \
|
||||
&& yarn build \
|
||||
&& mv dist /dist
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||
FROM arm64v8/debian:stable-slim as app
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
|
||||
|
||||
# curl: healcheck
|
||||
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
ffmpeg \
|
||||
libssl-dev \
|
||||
ca-certificates \
|
||||
libfontconfig1 \
|
||||
libfreetype6 \
|
||||
libomxil-bellagio0 \
|
||||
libomxil-bellagio-bin \
|
||||
locales \
|
||||
curl \
|
||||
&& apt-get clean autoclean -y \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media \
|
||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
|
||||
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
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 app
|
||||
|
||||
ENV HEALTHCHECK_URL=http://localhost:8096/health
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||
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 \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
"--ffmpeg", "/usr/bin/ffmpeg"]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
|
||||
CMD curl -Lk "${HEALTHCHECK_URL}" || exit 1
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||
--datadir /config \
|
||||
--cachedir /cache \
|
||||
--ffmpeg /usr/bin/ffmpeg
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +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>net6.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<AnalysisMode>AllDisabledByDefault</AnalysisMode>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.IO;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
@@ -15,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));
|
||||
@@ -32,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);
|
||||
@@ -41,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))
|
||||
{
|
||||
@@ -75,9 +76,9 @@ namespace DvdLib.Ifo
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles)
|
||||
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
|
||||
{
|
||||
var filename = string.Format(CultureInfo.InvariantCulture, "VTS_{0:00}_0.IFO", vtsNum);
|
||||
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
||||
|
||||
var vtsPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, filename, StringComparison.OrdinalIgnoreCase)) ??
|
||||
allFiles.FirstOrDefault(i => string.Equals(i.Name, Path.ChangeExtension(filename, ".bup"), StringComparison.OrdinalIgnoreCase));
|
||||
@@ -94,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))
|
||||
{
|
||||
@@ -118,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));
|
||||
@@ -155,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
|
||||
325
Emby.Dlna/Api/DlnaServerService.cs
Normal file
325
Emby.Dlna/Api/DlnaServerService.cs
Normal file
@@ -0,0 +1,325 @@
|
||||
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.Configuration;
|
||||
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 const string XMLContentType = "text/xml; charset=UTF-8";
|
||||
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
private readonly IHttpResultFactory _resultFactory;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
|
||||
public IRequest Request { get; set; }
|
||||
|
||||
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
|
||||
|
||||
private IConnectionManager ConnectionManager => DlnaEntryPoint.Current.ConnectionManager;
|
||||
|
||||
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
|
||||
|
||||
public DlnaServerService(
|
||||
IDlnaManager dlnaManager,
|
||||
IHttpResultFactory httpResultFactory,
|
||||
IServerConfigurationManager configurationManager)
|
||||
{
|
||||
_dlnaManager = dlnaManager;
|
||||
_resultFactory = httpResultFactory;
|
||||
_configurationManager = configurationManager;
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
string baseUrl = _configurationManager.Configuration.BaseUrl;
|
||||
|
||||
// backwards compatibility
|
||||
if (baseUrl.Length == 0)
|
||||
{
|
||||
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(first, baseUrl.Remove(0, 1)))
|
||||
{
|
||||
index++;
|
||||
var second = pathInfo[1];
|
||||
if (string.Equals(second, "mediabrowser", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(second, "emby", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return pathInfo[index];
|
||||
}
|
||||
|
||||
private static 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).ToArray();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
Emby.Dlna/Api/DlnaService.cs
Normal file
83
Emby.Dlna/Api/DlnaService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Configuration;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
||||
@@ -12,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)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
Emby.Dlna/ConnectionManager/ConnectionManager.cs
Normal file
36
Emby.Dlna/ConnectionManager/ConnectionManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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, IReadOnlyDictionary<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
143
Emby.Dlna/ContentDirectory/ContentDirectory.cs
Normal file
143
Emby.Dlna/ContentDirectory/ContentDirectory.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.TV;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
public class ContentDirectory : BaseService, IContentDirectory
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly IDlnaManager _dlna;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IUserViewManager _userViewManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ITVSeriesManager _tvSeriesManager;
|
||||
|
||||
public ContentDirectory(IDlnaManager dlna,
|
||||
IUserDataManager userDataManager,
|
||||
IImageProcessor imageProcessor,
|
||||
ILibraryManager libraryManager,
|
||||
IServerConfigurationManager config,
|
||||
IUserManager userManager,
|
||||
ILogger logger,
|
||||
IHttpClient httpClient,
|
||||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IUserViewManager userViewManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
ITVSeriesManager tvSeriesManager)
|
||||
: base(logger, httpClient)
|
||||
{
|
||||
_dlna = dlna;
|
||||
_userDataManager = userDataManager;
|
||||
_imageProcessor = imageProcessor;
|
||||
_libraryManager = libraryManager;
|
||||
_config = config;
|
||||
_userManager = userManager;
|
||||
_localization = localization;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_userViewManager = userViewManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_tvSeriesManager = tvSeriesManager;
|
||||
}
|
||||
|
||||
private int SystemUpdateId
|
||||
{
|
||||
get
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
return now.Year + now.DayOfYear + now.Hour;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new ContentDirectoryXmlBuilder().GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
{
|
||||
var profile = _dlna.GetProfile(request.Headers) ??
|
||||
_dlna.GetDefaultProfile();
|
||||
|
||||
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var user = GetUser(profile);
|
||||
|
||||
return new ControlHandler(
|
||||
Logger,
|
||||
_libraryManager,
|
||||
profile,
|
||||
serverAddress,
|
||||
null,
|
||||
_imageProcessor,
|
||||
_userDataManager,
|
||||
user,
|
||||
SystemUpdateId,
|
||||
_config,
|
||||
_localization,
|
||||
_mediaSourceManager,
|
||||
_userViewManager,
|
||||
_mediaEncoder,
|
||||
_tvSeriesManager)
|
||||
.ProcessControlRequest(request);
|
||||
}
|
||||
|
||||
private User GetUser(DeviceProfile profile)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(profile.UserId))
|
||||
{
|
||||
var user = _userManager.GetUserById(profile.UserId);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
var userId = _config.GetDlnaConfiguration().DefaultUserId;
|
||||
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
var user = _userManager.GetUserById(userId);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var user in _userManager.Users)
|
||||
{
|
||||
if (user.Policy.IsAdministrator)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var user in _userManager.Users)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Service;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.TV;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="ContentDirectoryService" />.
|
||||
/// </summary>
|
||||
public class ContentDirectoryService : BaseService, IContentDirectory
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly IDlnaManager _dlna;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IUserViewManager _userViewManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ITVSeriesManager _tvSeriesManager;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ContentDirectoryService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dlna">The <see cref="IDlnaManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="userDataManager">The <see cref="IUserDataManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="imageProcessor">The <see cref="IImageProcessor"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="libraryManager">The <see cref="ILibraryManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="config">The <see cref="IServerConfigurationManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="userManager">The <see cref="IUserManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="logger">The <see cref="ILogger{ContentDirectoryService}"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="httpClient">The <see cref="IHttpClientFactory"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="localization">The <see cref="ILocalizationManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="mediaSourceManager">The <see cref="IMediaSourceManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="userViewManager">The <see cref="IUserViewManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="mediaEncoder">The <see cref="IMediaEncoder"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
/// <param name="tvSeriesManager">The <see cref="ITVSeriesManager"/> to use in the <see cref="ContentDirectoryService"/> instance.</param>
|
||||
public ContentDirectoryService(
|
||||
IDlnaManager dlna,
|
||||
IUserDataManager userDataManager,
|
||||
IImageProcessor imageProcessor,
|
||||
ILibraryManager libraryManager,
|
||||
IServerConfigurationManager config,
|
||||
IUserManager userManager,
|
||||
ILogger<ContentDirectoryService> logger,
|
||||
IHttpClientFactory httpClient,
|
||||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IUserViewManager userViewManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
ITVSeriesManager tvSeriesManager)
|
||||
: base(logger, httpClient)
|
||||
{
|
||||
_dlna = dlna;
|
||||
_userDataManager = userDataManager;
|
||||
_imageProcessor = imageProcessor;
|
||||
_libraryManager = libraryManager;
|
||||
_config = config;
|
||||
_userManager = userManager;
|
||||
_localization = localization;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_userViewManager = userViewManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_tvSeriesManager = tvSeriesManager;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the system id. (A unique id which changes on when our definition changes.)
|
||||
/// </summary>
|
||||
private static int SystemUpdateId
|
||||
{
|
||||
get
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
return now.Year + now.DayOfYear + now.Hour;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return ContentDirectoryXmlBuilder.GetXml();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
}
|
||||
|
||||
var profile = _dlna.GetProfile(request.Headers) ?? _dlna.GetDefaultProfile();
|
||||
|
||||
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var user = GetUser(profile);
|
||||
|
||||
return new ControlHandler(
|
||||
Logger,
|
||||
_libraryManager,
|
||||
profile,
|
||||
serverAddress,
|
||||
null,
|
||||
_imageProcessor,
|
||||
_userDataManager,
|
||||
user,
|
||||
SystemUpdateId,
|
||||
_config,
|
||||
_localization,
|
||||
_mediaSourceManager,
|
||||
_userViewManager,
|
||||
_mediaEncoder,
|
||||
_tvSeriesManager)
|
||||
.ProcessControlRequestAsync(request);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user stored in the device profile.
|
||||
/// </summary>
|
||||
/// <param name="profile">The <see cref="DeviceProfile"/>.</param>
|
||||
/// <returns>The <see cref="User"/>.</returns>
|
||||
private User? GetUser(DeviceProfile profile)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(profile.UserId))
|
||||
{
|
||||
var user = _userManager.GetUserById(Guid.Parse(profile.UserId));
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
var userId = _config.GetDlnaConfiguration().DefaultUserId;
|
||||
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
var user = _userManager.GetUserById(Guid.Parse(userId));
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var user in _userManager.Users)
|
||||
{
|
||||
if (user.HasPermission(PermissionKind.IsAdministrator))
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
return _userManager.Users.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -1,39 +0,0 @@
|
||||
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>
|
||||
/// <param name="stubType">The stub type.</param>
|
||||
public ServerItem(BaseItem item, StubType? stubType)
|
||||
{
|
||||
Item = item;
|
||||
|
||||
if (stubType.HasValue)
|
||||
{
|
||||
StubType = stubType;
|
||||
}
|
||||
else if (item is IItemByName and not Folder)
|
||||
{
|
||||
StubType = Dlna.ContentDirectory.StubType.Folder;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the underlying base item.
|
||||
/// </summary>
|
||||
public BaseItem Item { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the DLNA item type.
|
||||
/// </summary>
|
||||
public StubType? StubType { get; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the DLNA item types.
|
||||
/// </summary>
|
||||
public enum StubType
|
||||
{
|
||||
Folder = 0,
|
||||
Latest = 2,
|
||||
Playlists = 3,
|
||||
Albums = 4,
|
||||
AlbumArtists = 5,
|
||||
Artists = 6,
|
||||
Songs = 7,
|
||||
Genres = 8,
|
||||
FavoriteSongs = 9,
|
||||
FavoriteArtists = 10,
|
||||
FavoriteAlbums = 11,
|
||||
ContinueWatching = 12,
|
||||
Movies = 13,
|
||||
Collections = 14,
|
||||
Favorites = 15,
|
||||
NextUp = 16,
|
||||
Series = 17,
|
||||
FavoriteSeries = 18,
|
||||
FavoriteEpisodes = 19
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
@@ -9,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public class ControlResponse
|
||||
{
|
||||
public ControlResponse(string xml, bool isSuccessful)
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
Xml = xml;
|
||||
IsSuccessful = isSuccessful;
|
||||
}
|
||||
|
||||
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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CA1305
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
@@ -9,7 +6,7 @@ namespace Emby.Dlna.Didl
|
||||
{
|
||||
public class StringWriterWithEncoding : StringWriter
|
||||
{
|
||||
private readonly Encoding? _encoding;
|
||||
private readonly Encoding _encoding;
|
||||
|
||||
public StringWriterWithEncoding()
|
||||
{
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
#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)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
#pragma warning disable CS1591
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
@@ -6,12 +5,10 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Profiles;
|
||||
using Emby.Dlna.Server;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller;
|
||||
@@ -32,10 +29,10 @@ 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;
|
||||
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
|
||||
|
||||
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
|
||||
|
||||
@@ -44,24 +41,22 @@ namespace Emby.Dlna
|
||||
IFileSystem fileSystem,
|
||||
IApplicationPaths appPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IServerApplicationHost appHost)
|
||||
{
|
||||
_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)
|
||||
@@ -91,16 +86,15 @@ namespace Emby.Dlna
|
||||
.Select(i => i.Item2)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DeviceProfile GetDefaultProfile()
|
||||
{
|
||||
return new DefaultProfile();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DeviceProfile? GetProfile(DeviceIdentification deviceInfo)
|
||||
public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
|
||||
{
|
||||
if (deviceInfo == null)
|
||||
{
|
||||
@@ -110,57 +104,100 @@ namespace Emby.Dlna
|
||||
var profile = GetProfiles()
|
||||
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
|
||||
|
||||
if (profile == null)
|
||||
if (profile != null)
|
||||
{
|
||||
_logger.LogInformation("No matching device profile found. The default will need to be used. \n{@Profile}", deviceInfo);
|
||||
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Found matching device profile: {ProfileName}", profile.Name);
|
||||
LogUnmatchedProfile(deviceInfo);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to match a device with a profile.
|
||||
/// Rules:
|
||||
/// - If the profile field has no value, the field matches irregardless of its contents.
|
||||
/// - the profile field can be an exact match, or a reg exp.
|
||||
/// </summary>
|
||||
/// <param name="deviceInfo">The <see cref="DeviceIdentification"/> of the device.</param>
|
||||
/// <param name="profileInfo">The <see cref="DeviceIdentification"/> of the profile.</param>
|
||||
/// <returns><b>True</b> if they match.</returns>
|
||||
public bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
|
||||
private void LogUnmatchedProfile(DeviceIdentification profile)
|
||||
{
|
||||
return IsRegexOrSubstringMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName)
|
||||
&& IsRegexOrSubstringMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer)
|
||||
&& IsRegexOrSubstringMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl)
|
||||
&& IsRegexOrSubstringMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription)
|
||||
&& IsRegexOrSubstringMatch(deviceInfo.ModelName, profileInfo.ModelName)
|
||||
&& IsRegexOrSubstringMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber)
|
||||
&& IsRegexOrSubstringMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl)
|
||||
&& IsRegexOrSubstringMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber);
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendLine("No matching device profile found. The default will need to be used.");
|
||||
builder.AppendLine(string.Format("DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty));
|
||||
builder.AppendLine(string.Format("FriendlyName:{0}", profile.FriendlyName ?? string.Empty));
|
||||
builder.AppendLine(string.Format("Manufacturer:{0}", profile.Manufacturer ?? string.Empty));
|
||||
builder.AppendLine(string.Format("ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty));
|
||||
builder.AppendLine(string.Format("ModelDescription:{0}", profile.ModelDescription ?? string.Empty));
|
||||
builder.AppendLine(string.Format("ModelName:{0}", profile.ModelName ?? string.Empty));
|
||||
builder.AppendLine(string.Format("ModelNumber:{0}", profile.ModelNumber ?? string.Empty));
|
||||
builder.AppendLine(string.Format("ModelUrl:{0}", profile.ModelUrl ?? string.Empty));
|
||||
builder.AppendLine(string.Format("SerialNumber:{0}", profile.SerialNumber ?? string.Empty));
|
||||
|
||||
_logger.LogInformation(builder.ToString());
|
||||
}
|
||||
|
||||
private bool IsRegexOrSubstringMatch(string input, string pattern)
|
||||
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(pattern))
|
||||
if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
|
||||
{
|
||||
// In profile identification: An empty pattern matches anything.
|
||||
return true;
|
||||
if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
|
||||
{
|
||||
// The profile contains a value, and the device doesn't.
|
||||
return false;
|
||||
if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
|
||||
{
|
||||
if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
|
||||
{
|
||||
if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
|
||||
{
|
||||
if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.ModelName))
|
||||
{
|
||||
if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
|
||||
{
|
||||
if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
|
||||
{
|
||||
if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
|
||||
{
|
||||
if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsRegexMatch(string input, string pattern)
|
||||
{
|
||||
try
|
||||
{
|
||||
return input.Equals(pattern, StringComparison.OrdinalIgnoreCase)
|
||||
|| Regex.IsMatch(input, pattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
return Regex.IsMatch(input, pattern);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
@@ -169,8 +206,7 @@ namespace Emby.Dlna
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DeviceProfile? GetProfile(IHeaderDictionary headers)
|
||||
public DeviceProfile GetProfile(IHeaderDictionary headers)
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
@@ -178,13 +214,15 @@ namespace Emby.Dlna
|
||||
}
|
||||
|
||||
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
|
||||
if (profile == null)
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
_logger.LogDebug("No matching device profile found. {@Headers}", headers);
|
||||
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
||||
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);
|
||||
}
|
||||
|
||||
return profile;
|
||||
@@ -211,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);
|
||||
@@ -223,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
|
||||
@@ -234,19 +276,19 @@ namespace Emby.Dlna
|
||||
return xmlFies
|
||||
.Select(i => ParseProfileFile(i, type))
|
||||
.Where(i => i != null)
|
||||
.ToList()!; // We just filtered out all the nulls
|
||||
.ToList();
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return Array.Empty<DeviceProfile>();
|
||||
return new List<DeviceProfile>();
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceProfile? ParseProfileFile(string path, DeviceProfileType type)
|
||||
private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
|
||||
{
|
||||
lock (_profiles)
|
||||
{
|
||||
if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile>? profileTuple))
|
||||
if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile> profileTuple))
|
||||
{
|
||||
return profileTuple.Item2;
|
||||
}
|
||||
@@ -274,20 +316,14 @@ namespace Emby.Dlna
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DeviceProfile? GetProfile(string id)
|
||||
public DeviceProfile GetProfile(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
|
||||
var info = GetProfileInfosInternal().FirstOrDefault(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (info == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return ParseProfileFile(info.Path, info.Info.Type);
|
||||
}
|
||||
@@ -304,7 +340,6 @@ namespace Emby.Dlna
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<DeviceProfileInfo> GetProfileInfos()
|
||||
{
|
||||
return GetProfileInfosInternal().Select(i => i.Info);
|
||||
@@ -312,14 +347,17 @@ namespace Emby.Dlna
|
||||
|
||||
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
|
||||
{
|
||||
return new InternalProfileInfo(
|
||||
new DeviceProfileInfo
|
||||
return new InternalProfileInfo
|
||||
{
|
||||
Path = file.FullName,
|
||||
|
||||
Info = new DeviceProfileInfo
|
||||
{
|
||||
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
||||
Type = type
|
||||
},
|
||||
file.FullName);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ExtractSystemProfilesAsync()
|
||||
@@ -330,31 +368,26 @@ 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);
|
||||
|
||||
// The stream should exist as we just got its name from GetManifestResourceNames
|
||||
using (var stream = _assembly.GetManifestResourceStream(name)!)
|
||||
var path = Path.Combine(systemProfilesPath, filename);
|
||||
|
||||
using (var stream = _assembly.GetManifestResourceStream(name))
|
||||
{
|
||||
var length = stream.Length;
|
||||
var fileInfo = _fileSystem.GetFileInfo(path);
|
||||
|
||||
if (!fileInfo.Exists || fileInfo.Length != length)
|
||||
if (!fileInfo.Exists || fileInfo.Length != stream.Length)
|
||||
{
|
||||
Directory.CreateDirectory(systemProfilesPath);
|
||||
|
||||
var fileOptions = AsyncFile.WriteOptions;
|
||||
fileOptions.Mode = FileMode.Create;
|
||||
fileOptions.PreallocationSize = length;
|
||||
using (var fileStream = new FileStream(path, fileOptions))
|
||||
using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
{
|
||||
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -364,7 +397,6 @@ namespace Emby.Dlna
|
||||
Directory.CreateDirectory(UserProfilesPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void DeleteProfile(string id)
|
||||
{
|
||||
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
@@ -382,7 +414,6 @@ namespace Emby.Dlna
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateProfile(DeviceProfile profile)
|
||||
{
|
||||
profile = ReserializeProfile(profile);
|
||||
@@ -398,8 +429,7 @@ namespace Emby.Dlna
|
||||
SaveProfile(profile, path, DeviceProfileType.User);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void UpdateProfile(string profileId, DeviceProfile profile)
|
||||
public void UpdateProfile(DeviceProfile profile)
|
||||
{
|
||||
profile = ReserializeProfile(profile);
|
||||
|
||||
@@ -407,13 +437,12 @@ namespace Emby.Dlna
|
||||
{
|
||||
throw new ArgumentException("Profile is missing Id");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(profile.Name))
|
||||
{
|
||||
throw new ArgumentException("Profile is missing Name");
|
||||
}
|
||||
|
||||
var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profileId, StringComparison.OrdinalIgnoreCase));
|
||||
var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
|
||||
var path = Path.Combine(UserProfilesPath, newFilename);
|
||||
@@ -433,7 +462,6 @@ namespace Emby.Dlna
|
||||
{
|
||||
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
||||
}
|
||||
|
||||
SerializeToXml(profile, path);
|
||||
}
|
||||
|
||||
@@ -444,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))
|
||||
@@ -455,56 +483,42 @@ namespace Emby.Dlna
|
||||
return profile;
|
||||
}
|
||||
|
||||
var json = JsonSerializer.Serialize(profile, _jsonOptions);
|
||||
var json = _jsonSerializer.SerializeToString(profile);
|
||||
|
||||
// Output can't be null if the input isn't null
|
||||
return JsonSerializer.Deserialize<DeviceProfile>(json, _jsonOptions)!;
|
||||
return _jsonSerializer.DeserializeFromString<DeviceProfile>(json);
|
||||
}
|
||||
|
||||
class InternalProfileInfo
|
||||
{
|
||||
internal DeviceProfileInfo Info { get; set; }
|
||||
internal string Path { get; set; }
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
||||
{
|
||||
var profile = GetDefaultProfile();
|
||||
var profile = GetProfile(headers) ??
|
||||
GetDefaultProfile();
|
||||
|
||||
var serverId = _appHost.SystemId;
|
||||
|
||||
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ImageStream? GetIcon(string filename)
|
||||
public ImageStream GetIcon(string filename)
|
||||
{
|
||||
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
||||
? ImageFormat.Png
|
||||
: ImageFormat.Jpg;
|
||||
|
||||
var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant();
|
||||
var stream = _assembly.GetManifestResourceStream(resource);
|
||||
if (stream == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ImageStream(stream)
|
||||
return new ImageStream
|
||||
{
|
||||
Format = format
|
||||
Format = format,
|
||||
Stream = _assembly.GetManifestResourceStream(resource)
|
||||
};
|
||||
}
|
||||
|
||||
private class InternalProfileInfo
|
||||
{
|
||||
internal InternalProfileInfo(DeviceProfileInfo info, string path)
|
||||
{
|
||||
Info = info;
|
||||
Path = path;
|
||||
}
|
||||
|
||||
internal DeviceProfileInfo Info { get; }
|
||||
|
||||
internal string Path { get; }
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class DlnaProfileEntryPoint : IServerEntryPoint
|
||||
{
|
||||
@@ -526,7 +540,7 @@ namespace Emby.Dlna
|
||||
|
||||
private void DumpProfiles()
|
||||
{
|
||||
DeviceProfile[] list = new[]
|
||||
DeviceProfile[] list = new []
|
||||
{
|
||||
new SamsungSmartTvProfile(),
|
||||
new XboxOneProfile(),
|
||||
@@ -550,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(),
|
||||
|
||||
@@ -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,18 +12,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Code Analyzers-->
|
||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Images\logo120.jpg" />
|
||||
<EmbeddedResource Include="Images\logo120.png" />
|
||||
@@ -72,7 +60,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public class EventSubscriptionResponse
|
||||
{
|
||||
public EventSubscriptionResponse(string content, string contentType)
|
||||
{
|
||||
Content = content;
|
||||
ContentType = contentType;
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
|
||||
public string Content { get; set; }
|
||||
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public Dictionary<string, string> Headers { get; }
|
||||
public Dictionary<string, string> Headers { get; set; }
|
||||
|
||||
public EventSubscriptionResponse()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +1,55 @@
|
||||
#nullable disable
|
||||
|
||||
#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 Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
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;
|
||||
|
||||
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(string.Empty, "text/plain");
|
||||
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
|
||||
}
|
||||
|
||||
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
|
||||
@@ -58,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);
|
||||
@@ -81,7 +80,9 @@ namespace Emby.Dlna.Eventing
|
||||
if (!string.IsNullOrEmpty(header))
|
||||
{
|
||||
// Starts with SECOND-
|
||||
if (int.TryParse(header.AsSpan().RightPart('-'), NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
||||
header = header.Split('-').Last();
|
||||
|
||||
if (int.TryParse(header, NumberStyles.Integer, _usCulture, out var val))
|
||||
{
|
||||
return val;
|
||||
}
|
||||
@@ -94,17 +95,26 @@ namespace Emby.Dlna.Eventing
|
||||
{
|
||||
_logger.LogDebug("Cancelling event subscription {0}", subscriptionId);
|
||||
|
||||
_subscriptions.TryRemove(subscriptionId, out _);
|
||||
_subscriptions.TryRemove(subscriptionId, out EventSubscription sub);
|
||||
|
||||
return new EventSubscriptionResponse(string.Empty, "text/plain");
|
||||
return new EventSubscriptionResponse
|
||||
{
|
||||
Content = string.Empty,
|
||||
ContentType = "text/plain"
|
||||
};
|
||||
}
|
||||
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
private EventSubscriptionResponse GetEventSubscriptionResponse(string subscriptionId, string requestedTimeoutString, int timeoutSeconds)
|
||||
{
|
||||
var response = new EventSubscriptionResponse(string.Empty, "text/plain");
|
||||
var response = new EventSubscriptionResponse
|
||||
{
|
||||
Content = string.Empty,
|
||||
ContentType = "text/plain"
|
||||
};
|
||||
|
||||
response.Headers["SID"] = subscriptionId;
|
||||
response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(CultureInfo.InvariantCulture)) : requestedTimeoutString;
|
||||
response.Headers["TIMEOUT"] = string.IsNullOrEmpty(requestedTimeoutString) ? ("SECOND-" + timeoutSeconds.ToString(_usCulture)) : requestedTimeoutString;
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -143,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(CultureInfo.InvariantCulture));
|
||||
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)
|
||||
{
|
||||
@@ -1,7 +1,3 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
|
||||
namespace Emby.Dlna.Eventing
|
||||
@@ -9,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)
|
||||
@@ -31,5 +22,7 @@ namespace Emby.Dlna.Eventing
|
||||
|
||||
TriggerCount++;
|
||||
}
|
||||
|
||||
public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public interface IConnectionManager : IDlnaEventManager, IUpnpService
|
||||
public interface IConnectionManager : IEventManager, IUpnpService
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user